Scalaでgree/auroraを使って複数DB

この記事はScala Advent Calendar 2014の19日目です(遅れてしまってスミマセン…)。18日は ysksuzuki さんの Apache SparkのScala shellを試す - Qiita でした。

はじめに

先日 Rails複数DB Casual Talks - connpass が開かれるなど、複数DBの機運が高まっております。そこで今回はauroraというライブラリを使ってMySQLをシャーディングする例について書こうと思います。

auroraについて

gree/aurora · GitHub

GREEが公開しているJava/Scala向けのシャーディングライブラリです。Scala Matsuriで紹介されていました。 [ScalaMatsuri] グリー初のscalaプロダクト!チャットサービス公開までの苦労と工夫

設定ファイルに接続先のDBサーバの情報を書いておき、アプリケーション側からヒントを渡すことでそれに応じたデータソース/テーブル名を解決することが出来ます。

テーブル分割してみる

https://github.com/gree/aurora/blob/develop/README.md が充実しており、データソースをシャーディングする場合についてはそちらを参照してもらうのが早いかと思います。そのためここではREADMEで言及されていなかったテーブルをシャーディングする場合について見て行きたいと思います。

前提

以下のようにテーブルが準備されていることを想定します。ユーザIDの10で割った余剰に基づきテーブルを分割し、それぞれ異なるDB上に定義されています。

DBは2つ用意しており、DB1が 192.168.33.10:3306DB2192.168.33.10:3307 で稼働しています。

  • DB構成
DBサーバ DB名 テーブル名
DB1 user user_0
DB1 user user_1
DB1 user user_2
DB1 user user_3
DB1 user user_4
DB2 user user_5
DB2 user user_6
DB2 user user_7
DB2 user user_8
DB2 user user_9
  • 各user_xテーブルの定義
mysql> desc user_0;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int(11)     | NO   | PRI | NULL    |       |
| name  | varchar(45) | NO   |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
  • 投入されているデータ
INSERT INTO user.user_0 VALUES (1000, 'user_1000');
INSERT INTO user.user_5 VALUES (1055, 'user_1055');

設定ファイルの定義

テーブル用の定義は以下のようになります。

https://github.com/takashabe/aurora-sample/blob/master/conf/application.conf

  • 抜粋
aurora {
  table-name-configs {
    patterns = [
      "user_[0-9]"
    ]
  }
}

リゾルバの追加

https://github.com/takashabe/aurora-sample/blob/master/src/main/scala/com/example/Main.scala#L37

テーブル名を解決するためのリゾルバです。これに目的のuser_idを渡せば、そのデータが入っているテーブル名を取得出来ます。

private val tableNameResolver = new AbstractTableNameResolver[Int]("user") {
  override protected def getSuffixName(userIdHint: Int): String = {
    "_" + "%d".format(userIdHint % 10)
  }
}
private val auroraTableNameService = AuroraTableNameService(tableNameResolver, new File("./conf/application.conf"))

リゾルバの利用

https://github.com/takashabe/aurora-sample/blob/master/src/main/scala/com/example/Main.scala#L46

val table = auroraTableNameService.resolveByHint(userId).get

リゾルバで取得したテーブルを println すると以下の様な感じです。

TableName(name = user_1)

ユーザを引いてみる

https://github.com/takashabe/aurora-sample/blob/master/src/main/scala/com/example/Main.scala#L84

println(findByUserId(1000))
println(findByUserId(1055))
println(findByUserId(9999))

// 結果
Success(User(1000,user_1000))
Success(User(1055,user_1055))
Failure(java.io.IOException: entity is not found.)

まとめ

  • 普段仕事でauroraの元?となったPHP向けの gree/cascade · GitHub を使っていますが、大体似たような感じで使えそう
  • ライブラリ自体ではデータソースとテーブル名の解決を行っているだけなので、他のフレームワークやライブラリと併用する場合も楽そう
  • ある程度以上の規模で利用する場合にはリゾルバ用のレイヤを1つ追加して、抽象化してあげると楽かなと思いました
  • スケーラビリティは置いといて、複数DBしないのが一番楽だと思います

20日目は taketon_ さんの Finagleでサーバープログラミング です。