2015年6月1日月曜日

DBアクセスライブラリ

Spark SQL 1.3の登場を機にプラットフォームのバッチ処理基盤の刷新を考えています。前回「JSONライブラリ性能比較」では、JSONライブラリについて考えました。

今回はDBアクセスライブラリがテーマです。

バッチ処理基盤として観点からDBアクセスライブラリに対して大きく以下の4つの要件を考えています。

  • CRUDの範囲の操作はcase classで定義したレコードで簡単に処理したい。
  • 細かい問合せ処理はSQLを直接使いたい。
  • SQLの組み立てを部品化したい。
  • Dockerコンテナ上でカスタマイズ可能にしたい。

逆に通常ORMに求められる以下の機能はあまり重要視していません。

  • 関連の自動制御
  • データのマイグレーション

「関連の自動制御」ですが関連の取り扱いは必要機能、性能要件、メモリ要件、キャッシュ要件がORMが提供する機能と合致するのかを熟知するのが大変なので、SQLを生で使う方式を採用しているためです。もちろん、うまく合致する場合は使ったほうが楽なので、あるに越したことはありません。

「データのマイグレーション」はアジャイルにカジュアルに行う運用ではなく、システム結合のタイミングで手動で行っているので今のところ本番運用では使う可能性がないためです。

制約

バッチ処理基盤を実現する上で考慮しなければならない制約は以下のものです。

  • バッチ処理はDockerクラスタまたはSparkクラスタ上で動作。
  • case classのパラメタ数制約22個が解消されるのはScala 2.11から。
  • プラットフォームのほとんどの主要DBテーブルのカラム数は22個超なのでScala 2.10ではレコードの表現にcase classは事実上難しい。
  • プラットフォームの主要機能がScala 2.10ベースなのでScala 2.11を全面的に採用するわけにはいかない。
  • Spark SQLがScala 2.11で安定的に動作するか不明(現在使用しているDockerイメージsequenceiq/sparkはScala 2.10みたい)。

上記の制約があるため、以下のような運用を想定しています。

  • プラットフォームとバッチ処理基盤の共通ライブラリはScala 2.10と2.11のクロスビルド。
  • Scala 2.10バッチとScala 2.11バッチを併用。
  • SparkジョブはScala 2.10バッチ。
  • 非SparkジョブはScala 2.11バッチ(case classを活用したいため)。
  • 非SparkジョブはDockerクラスタ上で実行する。
  • Sparkジョブでも小さなものはDockerクラスタ上で実行する。

クロスビルドの問題はかなり重たくて以下のような問題が出ています。

  • Play Frameworkの2.11サポートは2.3からのようだが、AnormのAPIが非互換みたい。
  • finagle-httpは2.10と2.11で別物になる気配。(詳細未調査。Netty 4対応?)

このため共通ライブラリからAnormとFinagleを外すことにしました。

アーキテクチャ選定

DockerでSpark SQL」で述べたようにSpark SQL 1.3をバッチ処理基盤の中軸に据え、1つのSparkジョブプログラムを用途ごとにSparkクラスタとDockerクラスタで実行しわける、というのが今回のバッチ処理基盤刷新の基本アイデアです。

現時点でもこの方向を軸に検討を進めています。

ただ以下のようなニーズもあるので普通のDB操作のためのライブラリも併用する形を考えています。

  • Sparkを使うまでもない小さなジョブの場合Sparkを使わず直接SQLで操作したい。
  • 開発をスケールさせるためSparkの知識なしでもバッチを記述できる方式は担保しておきたい。
  • Sparkで性能要件や機能要件が満たせないケースの回避策は担保しておきたい。
  • Spark処理の場合でもSQLによる入出力が併用できると便利かもしれない。
問題点

プラットフォームのエンジン部では現在はAnormとSquerylを併用しています。SQLを直接使いたいというニーズとORM的なCRUD処理を併用するため、このアーキテクチャを採用しました。

2012年当時の選択としてはよかったと思うのですが2015年新規開発のバッチ処理基盤として引き続き採用するのが望ましいのかという点が論点です。

AnormとSquerylとも非常に便利な機能なのですが経験上以下の問題が判明しています。

まずAnormです。

  • Play 2.2系と2.3系で非互換があるようでクロスビルドは難しそう。
  • ORM的な使い方ができないのでORMを併用する必要がある。

次にSquerylです。

  • 開発が止まっている感じ。
  • テーブル名を完全修飾名(スキーマ名.テーブル名)で指定すると動かない。
  • やりたいことをSquerylのDSLで実現する方法のノウハウが必要。
  • やりたいことがSquerylのDSLでサポートしていないことが判明した場合は対応が大変。
  • SQL組み立ての部品化にノウハウが必要。

AnormとSquerylを併用する際の問題点です。

  • コネクション管理の共通化のノウハウが必要。
  • AnormとSquerylの相互運用は難しい。

上記の問題があるのと有力な代替策があるのでバッチ処理基盤での採用は保留にしました。

Slick

Slickは実際に使ったことはないので資料からの推測ですが、レコードをcase classで表現した上で、関数型的なコレクション/モナド系の抽象操作でDB入出力を可能にしたライブラリ、と理解しています。

大変便利そうですし、関数型的にも筋がよさそうなのですが以下の懸念点があるので今回は採用を見送りました。

  • Scala 2.10系ではcase classの22個制限で事実上使用できない。
  • SQL直接操作ができないと細かい処理の記述が難しい。
  • Anormと併用する場合の親和性が不明。(コネクション管理の共通化など)
  • ORM的な関連の取り扱い方法など、新しいアプローチだけに仕様を調査するのが大変。
Scalikejdbc&Skinny-ORM

以上、色々と検討してきましたがSparkと併用するDB入出力ライブラリとしてはScalikejdbcとSkinny-ORMを採用する方向で考えています。

引き続きAnorm&Squerylをベースに考えても悪くはなさそうですが、Scalikejdbc&Skinny-ORMの方がより適切と判断しました。

ScalikejdbcはSQLベースのDB入出力ライブラリです。Skinny-ORMはScalikejdbc上に構築されたORMです。実用上はScalikejdbcとSkinny-ORMの併用を前提に考えるのがよいのではないかと思います。

Scalikejdbc&Skinny-ORMの採用を考えている理由は以下のものです。

  • Scala 2.10と2.11のクロスビルドで問題がなさそう。
  • SQLでの操作がAnormより若干使いやすそう。
  • Squerylは完全修飾名の問題があるため採用しづらい。
  • ScalikejdbcによるSQL入出力とSkinny-ORMによるORMがシームレスに連携できそう。
  • テーブルのメタデータからのプログラムの自動生成が便利。
  • Typesafe Configを用いたJDBC設定の管理メカニズムを持っている。
  • Joda-timeをサポートしている。

現在判明している問題点としては以下のものがあります。

  • case classの22個制限があるのでSkinny-ORMは2.10系では用途が限定される。
  • scalaz-streamと接続するためには工夫が要りそう。

後者のscalaz-streamの問題については引き続き解決策を探していく予定です。

まとめ

バッチ処理基盤におけるSQLライブラリですが、検討の結果Spark SQLを基本にScalikejdbc&Skinny-ORMを併用する形を基本線で考えていくことにしました。

DBアクセスライブラリのような基本機能は一度製品に採用するとなかなか切替は難しいですが、今回はバッチ処理基盤を刷新するタイミングがあったのでゼロベースで調べなおしてみました。

プロジェクトを開始した2012年当時とはかなり状況も変わってきていてキャッチアップが大変ですが、調度よいタイミングで棚卸しができたと思います。

0 件のコメント:

コメントを投稿