2013-03-29

Java の BlockingQueue 実装の性能を比較してみた

巷では「ArrayBlockingQueue よりも LinkedBlockingQueue の方がスループット性能がいいよ」なんてまことしやかに言われているけど、どうにも気になったので検証してみたら、実は ArrayBlockingQueue の方が性能いいんじゃない? という結論に至った話です。

Producer-Consumer デザインパターンと BlockingQueue

Java で Producer-Consumer デザインパターン を実現するときによくお世話になる BlockingQueue インタフェース。このインタフェースには ArrayBlockingQueue クラスLinkedBlockingQueue クラス の二つの実装が標準 API として提供されています。

高い処理性能を要求されるプログラムを Java で書こうとしたときに、CPU のすべてのコアをフル稼動させることを狙って Producer-Consumer パターンによって処理フローをパイプライン的に構成することがあります。そのときにいつも気になっていたのが、「ArrayBlockingQueue と LinkedBlockingQueue、果たしてどちらを利用した方がスループットが高くなるのだろうか?」ということ。

この疑問に対して、海外でも 議論 されていたり、検証 されていたりされているようで、それらの 結論 としては概ね「LinkedBlockingQueue の方が ArrayBlockingQueue よりも高スループットだよ」というものになっています。

しかし、ArrayBlockingQueue と LinkedBlockingQueue の双方の実装やデータ構造の特性を考えると「LinkedBlockingQueue の方が高スループット」という結論がにわかに信じ難かったので、実際に検証をしてみることにしました。

双方のデータ構造と実装の特性

検証を始める前にまず、それぞれのデータ構造および実装の特性を確認してみることにします。参考にした実装は Oracle の JDK 1.7 (update 17) です。

ArrayBlockingQueue クラス

  • 配列をベースとしたキュー実装で、配列をリングバッファとして扱います。
    • エンキュー (add() / offer() / put()) 操作、デキュー (remove() / poll() / take()) 操作をする配列上のインデックスを、それぞれ別々に保持しています (putIndex フィールドと takeIndex フィールド)。
  • キューの大きさは固定長で、その大きさを超える要素数を保持することはできません。
  • エンキュー操作において、余計なオブジェクトが生成されることはありません。
  • エンキュー/デキュー操作では、一つのロックオブジェクト (lock フィールドの ReentrantLock オブジェクト) を共用して排他制御をします。
    • エンキューとデキュー操作のメソッド呼び出しが重なると、一方の処理にてロック解除待ちが生じてしまいます。

LinkedBlockingQueue クラス

  • 片方向の連結リストをベースとしたキュー実装です。
    • 要素の数に応じて、リストが伸長・縮退します。
  • 連結リストの先頭 (head フィールド) より要素をデキューし、末尾 (last フィールド) に要素をエンキューします。
  • 保持できる要素数は最大で Integer.MAX_VALUE になります。
    • コンストラクタで capacity を指定することで、保持する要素数を制限することもできます。
  • 要素のエンキュー操作ごとに、連結リストの Node オブジェクトの生成操作が発生します。
    • 1 つの要素につき 16 バイトほどのヒープを余計に消費します。
  • エンキュー/デキュー操作の排他制御はそれぞれ別々のロックオブジェクト (putLock フィールド、takeLock フィールド) を利用します。
    • エンキューとデキュー操作のメソッド呼び出しが重なったとしても、ロック解除待ちは発生せず並列に処理することができます。

これら上記の双方の特性を比較すると、

  • ArrayBlockingQueue は処理が単純な分、エンキュー/デキュー操作の処理コストが小さく、一方で LinkedBlockingQueue はオブジェクト生成が必要となるために特にエンキュー操作の処理コストが高い。
  • ArrayBlockingQueue は、エンキュー/デキュー操作においてロックオブジェクトを共有するために Producer / Consumer の並列性が低くなるが、LinkedBlockingQueue は別々のロックオブジェクトが用意されているため、Producer / Consumer の並列性が高まる。

と言うことができそうです。 LinkedBlockingQueue の方がスループットが高い、というのは後者の並列性が根拠になっていると思われますね。

性能検証する

続いて、実際に両者の BlockingQueue 実装を利用したプログラムを作成し、各種性能を測定してみました。

測定方法

検証用プログラム を用いて、エンキュー操作、デキュー操作を独立して 1,600 万回ほど行ったときの処理時間、およびエンキュー/デキュー操作を複数の別スレッド (Producer / Consumer) で同時並列に 1,600 万回実施したときのスループットを ArrayBlockingQueue, LinkedBlockingQueue それぞれの実装ごとに測定してみました。

また測定を進めていく上で、Java VM の 32 bit と 64 bit の違いや、Java コンパイラ (JDK / Eclipse)、ハードウェア・OS (Windows / Mac) の違いが性能に影響を与えることが明らかになったため、これらの構成の組み合わせも合わせて検証してみることにしました。

検証環境

  • ハードウェア・OS
    • Windows PC
      • Windows 7 (64 bit)
      • Core i5-3450 (3.10GHz)
      • 8GB Memory
    • Mac
      • Mac OS X 10.8.2
      • 1.7GHz Core i5-3317U
      • 4GB Memory
  • Java
    • Java VM (オプション : Xms1200m)
      • Java 7 Update 17 (32 bit & 64 bit)
    • Java コンパイラ
      • JDK 1.7 Update 17
      • Eclipse Compiler for Java 3.8.2

測定項目

  • エンキュー/デキュー操作を行う各種メソッドの呼び出しにかかる時間 [ミリ秒]
    • エンキュー
      • add()
      • offer()
      • put()
    • デキュー
      • remove()
      • poll()
      • take()
  • スループット [メッセージ/秒]
    • Producer x 1 スレッド, Consumer x 1 スレッド
    • Producer x 1 スレッド, Consumer x 2 スレッド
    • Producer x 2 スレッド, Consumer x 1 スレッド
    • Producer x 2 スレッド, Consumer x 2 スレッド

測定結果と考察

上記の内容で測定した結果を Google ドキュメントで公開 しています。数値に対するカラースケールは行(横一列)のグループに対して設定されており、緑色が良い性能であることを、黄色がほどほどの性能を、赤色が性能が悪いことを示します。

Java VM (64 bit / 32 bit) の違い

さてこの結果を見てすぐに分かることは、ArrayBlockingQueue と LinkedBlockingQueue のどちらについても、32 bit Java VM よりも 64 bit の方が性能がよい、ということになりますね。メソッド単体の呼び出し性能もスループットも、どちらも下は 1.5x から上は 4x ぐらいまでの性能が 64 bit VM では出ています。

メソッドの呼び出し性能に着目すると、32 bit VM ではメソッド間で性能差が大きく開きバラつきがあり、ArrayBlockingQueue も LinkedBlockingQueue もそれぞれの性能特徴がよく分かる結果となっています。一方で 64 bit VM では、メソッド間の性能差は 32 bit ほどの開きは見られず、安定した性能が出せていることがうかがえます。

スループットについては、64 bit VM を使うことで ArrayBlockingQueue の性能が 32 bit VM のときより大幅に性能向上することが見て取れます。LinkedBlockingQueue も 64 bit VM による性能劣化はなく、ArrayBlockingQueue ほどではないものの多少の性能向上がうかがえます。

Java VM の違いにおいて ArrayBlockingQueue と LinkedBlockingQueue の結果を比較すると、

  • 32 bit VM では LinkedBlockingQueue の方が性能がよい。
  • 64 bit VM では ArrayBlockingQueue の方が性能がよい。

と言えるでしょう。

Java コンパイラ (JDK / Eclipse) の違い

コンパイラの違いについては、今回の測定結果では優劣が明確になるほどの性能差は出ませんでした。

ハードウェア・OS の違い

Mac の場合、ArrayBlockingQueue と比べて LinkedBlockingQueue の性能が全体的に悪いことがわかります。メソッドの呼び出しについては、特にエンキュー操作の性能が極端に悪いですね。

スループットも、エンキュー操作メソッドの性能に引きづられてか、LinkedBlockingQueue の性能は芳しくありません。エンキュー操作が競合しやすい Producer x 2 の構成での落ち込みが大きいですね。

Mac では、 ArrayBlockingQueue 択一 と言ってしまっていいと思います。

まとめ

ArrayBlockingQueue を使うべきか、それとも LinkedBlockingQueue を使うべきかの判断は Java VM 次第、すなわち

  • (Mac を含む) 64 bit の Java VM を利用するなら ArrayBlockingQueue がよい。
  • 32 bit の Java VM なら LinkedBlockingQueue がよい。

と言ってしまっていいでしょう。ただ今後のことを考えれば 64 bit VM の利用が多くなっていくと考えられるため、開発時点では 32 bit の VM 利用を想定していても、ArrayBlockingQueue を採用しておくのが無難じゃないかと思います。はい。

だいじなこと

今回は Windows と Mac とで性能検証をしてきたのですが、実際に Java アプリが利用される環境は Linux など Unix 系の OS が多いかと思います。今回の検証でなんとなく傾向はつかめた (64 bit なら ArrayBlockingQueue) のですが、これがそのまま Linux でも通じるかどうかはまた別の話、つまりは要検証、ということです。

2:22168 comments

2013-02-07

楽して Markdown ファイルをリアルタイムプレビューできる仕組みを作ったった

ざっくり要約すると、お好きなテキストエディタとブラウザさえあれば OK な、ちょっと便利な Markdown リアルタイムレンダリング&プレビューツールを 車輪再発明 の再発明したよ、ということです。

Markdown のリアルタイムプレビューの現状

動機は言わずもがなですが改めて書くと、github で公開するプロダクトの README など、Markdown のフォーマットでドキュメントを書く機会がここ最近増えてきています。ですが、Markdown の書式に慣れないうちは、HTML レンダリング結果をブラウザで確認しながら編集したいもので、実際にプレビューを確認しながら書くのとそうでないのとでは生産性に結構な差が生じることは、経験者の多くの方々に頷いてもらえるかと思います。

このような欲求があれば当然、それを解決するツールが生み出されるのが世の常なわけでして、すでに Markdown のリアルタイムレンダリングツールの類はいくつか世に出ています。

Showdown は、外部サービスを利用せず JS で実装された Markdown レンダリングエンジンを利用してプレビューを実現するツールで、ブラウザだけで完結するのが強み(でもあり弱みでもある)となっています。一方で Jxck 先生の markup とたむたむ先生の rdoc-view は、いずれもローカル環境に AP サーバを立てて、そのサーバ経由でローカルのファイルシステム上にある Markdown ファイルのプレビューを提供します。この方式の強みはなんといっても、「好きなエディタを使って Markdown の編集ができる」ということに尽きます。

markup にしても rdoc-view にしても、利用するために必要となる実行コマンドは2つだけですが、その前に Node.js の環境やら Ruby の環境やらを整えなくてはいけないという、私みたいなものぐさ人間にはそれなりの高さのハードルがあります。

Markdown Previewer

そんなわけで、「自分の好きなエディタ」を利用できて、かつ「面倒なインストール作業も要らない」Markdown リアルタイムプレビューのサービス (?) を作りました。

Markdown Previewer

利用方法は「これでもか!」というぐらいに簡単(なはず)で、上記サイトを表示して、"Drop your markdown file here" と大きな文字で表示されている領域にローカルにある Markdown ファイルをドラッグ&ドロップするだけです。後はお好きなエディタでそのファイルを編集するだけ。編集内容を保存すると、その内容がすぐにプレビューに反映されます。

お手軽リアルタイムプレビューを実現している仕組み

エンジニアの方であれば上記の説明から仕組みが簡単に想像できちゃうかと思いますが、JS の File API を利用することで、ローカルの Markdown ファイルの読み込み&更新検知を実現し、また先の Showdown を利用して Markdown のレンダリングプレビューを実現しています。オリジナルなコードは高々数十行です。

詳しくは markdown-previewer の github リポジトリをご覧ください。

制限事項と今後の展開

今回は自分だけが使えればいいや、ということでブラウザ/OS は Google Chrome 24 (Mac OS X / Windows) のみをターゲットとしています。(2013.2.8 追記) Firefox (Mac/Ubuntu/Windows), Safari (Mac), Chrome (Ubuntu) に追加対応しました。 ですが、できるならば Firefox や Safari などでも使えるように対応していく予定でいます (IE は知らん)。

また、Markdown だけではなくて RDoc などの他の記法にも対応していく予定でいます。

10:46No comments

2012-12-14

AROW を Ruby で実装してみた

Machine Learning Advent Calendar 2012 の 14 日目の記事になります。気まぐれでこのカレンダー登録してみたものの、この日に至るまでの数々の記事のガチな高レベルさに登録してしまったことをかなり後悔している @komiya_atsushi です。

この記事では、オンライン学習アルゴリズムの AROW を Ruby で実装した (& gem として公開した) という、どこにでもよくある話を書いています。主に機械学習に馴染みのない or 取り組み始めたばかりの(自分を含めた)エンジニア向けの記事となっています。

AROW とオンライン学習

まずはじめに、この記事で取り上げている AROW とその前提となる知識について軽く説明していきます。

AROW (Adaptive Regularization of Weight Vectors) というのは、「オンライン学習」ができる「教師あり」の「クラス分類(二値分類)」アルゴリズムです。ではこの AROW を使って何ができるのか? というと、例えば「海賊船からかっぱらってきた金貨のようなものを、事前に明らかになっている数十枚のコインの真贋の情報をもとに、その体積と重さから真贋を見分ける」ことができるようになります。

AROW は 2009 年に提案されたアルゴリズムであるものの、後からよりよい手法が幾つか提案されているので state of the art な手法ではないですが、言い換えれば安定して使える「枯れた技術」として使うには十分ではないでしょうか。

オンライン学習

「オンライン学習」については、今回の Advent Calendar でも記事を書かれている kisa12012 さんの オンライン学習による線形識別器 などがありますので、詳しく知りたい方はこちらをご参照いただくのがいいかと思います。ここではざっくりな概要の説明に留めるとして、クラス分類での説明を簡単(雑?)にすると「次々と与えられるデータの『特徴』とそのデータに対するクラスを示す『ラベル』(Yes / No みたいなもの)をもとに、それらをラベルに従って分類してくれるモデルを作る・更新する(=学習する)」となるでしょう。

オンライン学習の特徴としては、以下に示すものがあります。

  • 大量のデータをもとにモデルを作る必要があっても、全データを同時にすべて取り扱う必要がない(主記憶装置にやさしい)。
  • モデルの更新に利用したデータを不要となれば破棄することもできる(二次記憶装置にやさしい)。
  • 1回1回のモデルの更新にかかる計算量が小さい(演算装置にやさしい)。

教師あり学習

次に「教師あり学習」について。教師あり学習というのは「正解の状態が明らかになっているデータ(訓練データ)をもとにモデルを作成する」学習になります。具体例でいうと今回取り上げている「クラス分類」がそれにあたります。対する用語として「教師なし学習」がありますが、こちらは「クラスタリング」などが例に挙げられます。

クラス分類

最後は「クラス分類」について。これは「データの『特徴』とそのデータに対するクラスを示す『ラベル』(Yes / No みたいなもの)をもとに、それらをラベルに従って分類してくれるモデルを作る・更新する。またラベルが未知のデータを予測して分類してくれる」お仕事と表現できるかと思います。

AROW の論文を読んでみる

続いて、AROW のアルゴリズムを実装する観点で、AROW の 論文 を読んでいきましょう。…とは言え、私自身、数式がそれなりにふんだんに使われた論文をすらすらと読めるほど数学リテラシーが高いわけではないので、重要な部分だけをピックアップして軽く説明します。

AROW を実装するに当たって一番重要なのは、p.4 のページ上にある Fig.1 です。(下図参照)

ここにある式を頑張って実装すればいいわけです。Σ が summation としてではなく共分散行列を表す変数として使われていることと、ベクトル x が列ベクトルであることに注意をすれば比較的容易に数式を読み解けると思います。

実際にこのコア部分を Ruby で実装すると、高々 50 行弱のコードになります。とってもシンプルですね! 1

なお既存の他言語での実装を見てみると、Σ の更新式が、論文中の (9) 式ではなく、(8) 式をもとにした実装がほとんどになっているようです。tsubosaka 先生の Java 実装 や C++ 実装の AROWPP などなど。なぜみんな (8) 式を利用しているのか理由が分からなかったのと、(9) 式の方がより効率よく計算できて精度も向上するようなので、今回は (9) 式での実装をしました。 2

gem 'arow'

今回は上記実装をもとに gem 化したものを RubyGems.org で公開 しています。ソースコードは github.com/komiya-atsushi/arow にて公開しています。突貫工事的に gem 化したので、テストケースはないしマニュアルはないし README からして適当と粗が目立ちますが、カジュアルに使えるクラス分類器を自分自身が欲していたので、(お仕事が落ち着いたら)もうちょっと使いやすくできるようにメンテナンスをしていく予定です。

利用例がないのもアレなので、先に挙げた Code IQ の問題に対する適用例を示します… としたかったのですが、12/14 現在、まだ挑戦者受付中とのことらしいので念の為にしばらくはコード公開を控えておきます。また後日で。 3

2012.12.21 追記:公開しました。

おわりに・感想

久々に数式たっぷりな論文を読んで、改めて己の数学力の低さを悔やみました。数学力を鍛え直したい…

この Machine Learning Advent Calendar の企画を開催してくださいました @naoya_t さん、ありがとうございました!


1 : 今回の実装では、μ (@means) と Σ (@covariances) は時間・空間計算量を減らすために行列の対角要素をとったもので表現しています。既存の各種実装をみても、いずれもこの対角要素を利用する方式をとっているようです。
2 : 理由を御存知の方がいらしたら是非、教えていただけると幸いです。
3 : Advent Calendar に登録した当初は 12/14 時点で受付終了している予定でしたが、終了が延長されてしまったため、このタイミングでは公開できず、と。ぐぬぬ…
10:13No comments

2012-11-29

Play framework (1.2.5) のアプリで Twitter4J を利用する

難しい手順ではないのだけれど、ちょっとはまったことがあったので未来の自分のためにメモを残しておきます。

手順

  1. $ play new で Play framework のアプリを作成する
  2. ./conf/dependencies.yml に Twitter4J を利用する旨を記述する
  3. $ play dependencies で依存ライブラリを ./lib に配置する
  4. $ play eclipsify で Eclipse 上で開発可能な状態にする
  5. javaee-api-5.0-x.jar をビルドパスから取り除く

「2. ./conf/dependencies.yml に Twitter4J を利用する旨を記述する」について

以下のように、Twitter4J に対する依存を記述すれば OK。Twitter4J の jar ファイルをダウンロードして特定ディレクトリに配置… みたいなことをする必要はありません。

# Application dependencies

require:
    - play
    - org.twitter4j -> twitter4j-core [3.0,)

「5. javaee-api-5.0-x.jar をビルドパスから取り除く」について

これをし忘れてちょっとはまりました。

Twitter4J を dependencies.yml に記述して $ play dependencies を実行すると、twitter4j-code-3.0.x.jar とともに javaee-api-5.0-x.jar というファイルも ./lib のディレクトリに配置されることになります。この jar ファイルをビルドパスに含めたまま Play アプリを起動してページアクセスすると、以下のエラーが発生することがあります。

play.exceptions.UnexpectedException: Unexpected Error
 at play.Invoker$Invocation.onException(Invoker.java:244)
 at play.Invoker$Invocation.run(Invoker.java:286)
 at Invocation.HTTP Request(Play!)
Caused by: java.lang.NoSuchMethodError: javax.persistence.EntityManager.setProperty(Ljava/lang/String;Ljava/lang/Object;)V
 at play.db.jpa.JPAPlugin.startTx(JPAPlugin.java:375)
 at play.db.jpa.JPAPlugin.beforeInvocation(JPAPlugin.java:345)
 at play.plugins.PluginCollection.beforeInvocation(PluginCollection.java:473)
 at play.Invoker$Invocation.before(Invoker.java:217)
 at play.Invoker$Invocation.run(Invoker.java:277)
 ... 1 more

どうやら javaee-api-5.0-x.jar に含まれる javax.persistence.EntityManager クラスの定義が Play が参照しているそれとかち合ってしまうようなので、思い切って javaee の方をビルドパスから除去してあげることで解消できます。

14:56No comments

2012-11-17

UTF-8 エンコードされた絵文字を MySQL / JDBC で取り扱うには?

2013.06.08 追記:JDBC 接続文字列で characterEncoding / connectionCollation を指定すると思った通りの挙動をしてくれないようなので、別の方法 (多分これが正しい方法) を記載しました。

Unicode における、こんな 感じの絵文字、いわゆる Unicode の 追加面 の文字、Java で言えばサロゲートペアでの表現が必要となる文字を、JDBC 経由で UTF-8 エンコーディングして MySQL のテーブル・カラムに格納しようとすると、以下の例外が発生することがあります。

Caused by: java.sql.SQLException: Incorrect string value: '\xF0\x9F\x98\x81 h...' for column 'col_name' at row 1
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1074)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4096)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4028)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2490)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2651)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2734)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2155)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2458)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2375)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2359)

Twitter API 叩いて得たツイートを MySQL に入れて蓄積したい、みたいなことをしようとして、毎回このスタックトレースと格闘しているので、対応方法を忘れないようにメモしておきます。

確認した環境は以下のとおり。

  • MySQL 5.5.25a
  • MySQL Connector/J 5.1.22
  • Java 1.7.0_07-b10

すべきこと

  • 強く推奨される対処方法
    1. my.cnf (Windows の場合は my.ini) の [mysqld] セクションで character-set-server に utf8mb4 を設定する
    2. 既存のデータベース / テーブル / カラムの CHARSET を確認する
  • いかんともし難い理由により my.cnf を変更できない場合の代替手段
    1. データベース / テーブルの CHARSET に utf8mb4 を指定する
    2. Java のプログラムから具体的な SQL を発行する前に SET NAMES utf8mb4 を発行する

ざっくりとまとめると上記のとおり、2 つの手段があります。以下でそれぞれ説明します。

強く推奨される対処方法

my.cnf (my.ini) を変更することができるならば、こちらの手段での対処を強くおすすめします。こちらの方法であれば、Java プログラム側は特別な対処をする必要がありません (JDBC 接続文字列での characterEncoding や connectionCollation の設定も不要です)。

1. my.cnf の [mysqld] セクションで character-set-server に utf8mb4 を設定する

以下のように、character-set-server の値を utf8mb4 に書き換えるだけです。

(略)

# SERVER SECTION
# ----------------------------------------------------------------------
#
# The following options will be read by the MySQL Server. Make sure that
# you have installed the server correctly (see above) so it reads this 
# file.
#
# server_type=3
[mysqld]

(略)

# The default character set that will be used when a new schema or table is
# created and no character set is defined
character-set-server=utf8mb4  # ← ここを utf8mb4 にする

2. 既存のテーブル / カラムの CHARSET を確認す

続いて、すでに存在するテーブルやカラムの CHARSET を以下のクエリで、テーブルごとに確認します。

SHOW CREATE TABLE テーブル名

このクエリを実行した結果、たとえば、

CREATE TABLE `tbl_name` (
  `col_name` varchar(8) CHARACTER SET utf8 DEFAULT NULL
) ENGINE=InnoDB DEFAUTL CHARSET=utf8bm4;

このような結果が得られた場合は、tbl_name CHARSET は utf8mb4 だけれども、col_name は utf8 であることが分かります。このテーブルのカラムをすべて utf8mb4 の CHARSET にしたい場合は、

ALTER TABLE tbl_name CONVERT TO CHARACTER SET utf8mb4;

というクエリを発行します。この確認 (と CHARSET 変更) を、追加面の文字が格納される可能性のあるすべてのテーブルに対して実施します (確認が面倒どうであれば、すべてのテーブルに対して無差別に ALTER TABLE ~ してしまってもいいかもしれませんね)。

いかんともし難い理由により my.cnf を変更できない場合の代替手段

こちらは my.cnf を変更出来ない場合の対処方法です。理由は後述しますが、こちらの対応方法はあまりおすすめできません。

1. データベース / テーブルの CHARSET に utf8mb4 を指定する

この対応方法はまず、データベース or テーブルで文字列を取り扱う際の CHARSET に utf8mb4 を指定することから始まります。

データベース全体の CHARSET を設定するのであれば、CREATE DATABASE でデータベースを作成する際に以下のように指定するとよいでしょう。

CREATE DATABASE
    db_name
  DEFAULT CHARSET
    utf8mb4;

また、テーブルを作成するときに、テーブル単位 or カラム単位で CHARSET を指定することもできます。追加面の文字が入るテーブル / カラムのみ変更したい場合は、こちらの指定がよいでしょう。

/* テーブル単位で指定する場合 */
CREATE TABLE tbl_name (
  col_name VARCHAR(100)
) ENGINE 
    InnoDB
  CHARSET
    utf8mb4;

/* カラム単位で指定する場合 */
CREATE TABLE tbl_name (
  col_name VARCHAR(100) CHARSET utf8mb4
);

2. Java のプログラムから具体的な SQL を発行する前に SET NAMES utf8mb4 を発行する

続いて Java プログラム側の話になります。

INSERT / UPDATE などの具体的な SQL を発行する前に、「コネクション単位で」SET NAMES utf8mb4 のクエリを発行するようにします。つまり以下のプログラムのように、java.sql.Connection オブジェクトを取得した直後に java.sql.PreparedStatement オブジェクトなどを利用して SET NAMES utf8mb4 を発行するようにします。

try (Connection conn = 
         DriverManager.getConnection("jdbc:mysql://localhost/dbname?user=u&password=p")) {
    try (PreparedStatement pstmt = conn.prepareStatement("SET NAMES utf8mb4");
         ResultSet rset = pstmt.executeQuery()) {
    }

    // この後に具体的な SQL を発行する...
}

SET NAMES utf8mb4 の発行結果はコネクションをまたいで引き継がれるわけではないので、コネクションを取得し直す度に毎回 発行し直す必要があります (逆を言えば、同一のコネクション内であれば、再発行する必要はありません)。

この対応方法がおすすめ出来ない理由

MySQL Connector/J のマニュアル 22.3.5.4. Using Character Sets and Unicode を読み進めて見ると分かりますが、

上記のキャプチャのように、「SET NAMES を JDBC のコネクション上で発行すること自体すべきでない」と明記されています。 加えて O/R マッパーを利用する場合、java.sql.Connection オブジェクトの管理が O/R マッパー任せとなるので、いつ SET NAMES utf8mb4 を発行すればよいか、そのタイミングを図るのが非常に難しくなります。

特別な理由がない限りは、my.cnf の設定を変更する方法を利用し、「どうしても my.cnf は触れない/触りたくないんだ…!」というときのみ、こちらの利用をするとよいかと思います。

2013.06.08 削除:以下の対処は不要で、文字数分の大きさの VARCHAR カラムをすれば十分でした。

2. Unicode 追加面 が格納される可能性のある VARCHAR のカラムを、文字数の4倍に設定する

Unicode 追加面の文字を VARCHAR のカラムに格納すると、同文字1文字あたり VARCHAR 4 文字分を消費するようです。そのため、例えば 100 文字格納できるカラムが合った場合に、すべての文字が Unicode 追加面の文字である 100 文字の文字列を格納することを想定して、VARCHAR のカラムの文字数を 400 に設定する必要があります。

つまり、

CREATE TABLE tbl_name (
  col_name VARCHAR(100)
)
のようなテーブルがあったとして、col_name のカラムに実際に 100 文字格納したいんだけど、Unicode 追加面の文字が混じる可能性があるのであれば、
CREATE TABLE tbl_name (
  col_name VARCHAR(400)
)
のようにしましょう、ということです。

2013.06.08 削除:この対処も不要で、特に接続文字列に何かを付け加える必要はありません。

3. JDBC の接続文字列で characterEncoding / connectionCollation を指定する

MySQL のサーバ側の設定などは上記までで、残すは JDBC 周りとなります。JDBC 周りの設定に手を入れない限りは、最初に示したスタックトレースとおさらばすることはできません。

JDBC 周りの設定内容は以下のとおりです。

  • characterEncoding ... UTF-8
  • connectionCollation ... utf8mb4_general_ci

JDBC の接続文字列での指定例は以下になります

jdbc:mysql://host:3306/db_name?characterEncoding=UTF-8&connectionCollation=utf8mb4_general_ci

以上。

15:271 comment

2012-09-30

第3回 データ構造と情報検索と言語処理勉強会 #DSIRNLP でトークしてきました

2012/9/30(日)に開催された 第3回 データ構造と情報検索と言語処理勉強会 DSIRNLP で、最近(個人的に)注目の圧縮ライブラリ、LZ4 に関してトークしてきました。

LT という時間枠でしたので、テーマを絞って LZ4 の速さの秘密について話しましたが、秘密が盛りだくさんで話し切れず。時間をオーバーしてご迷惑をおかけしてしまいました。すみません…

発表資料は以下になります。

私の発表はアレですが、LZ4 を知らない方が結構多い集まりでしたので、今回の発表を機に、もっと利用が広まるといいなあと思っています。

以下、個人的な反省点です。

  • LT で 90 ページ超とか無理があり過ぎるのでやめよう。
  • もっと簡潔に話そう。
  • テーマを絞り込もう。
  • いい加減、発表直前まで資料作成をするのはやめよう。

18:14No comments