📝 BLOG.IK.AM

@making's memo
(🗃 Categories 🏷 Tags)

Oracle Coherenceを使うときに特にアーキテクトの人が気をつけるべきこと

🗃 {Middleware/DistributedSystem/InMemoryDataGrid/Coherence}

🗓 Updated at 2013-08-21T23:44:22+09:00 by Toshiaki Maki  🗓 Created at 2013-07-22T15:32:14+09:00 by Toshiaki Maki  {✒️️ Edit  ⏰ History}


💣 Danger: This content is obsolete. You should avoid reading. If you really want to read, please click .

本記事の内容を発表しました。発表資料

Coherenceを利用するときに気をつけること #OracleCoherence from makingx


Oracle Coherenceに関するメモ。整理するために書いた。Coherenceに限らず同様な分散データグリッド製品(InfinispanとかeXtreme ScaleとかGemfireとか)にも言えると思う。

アーキテクトとして開発の方針を決め、統制を効かせ、安定して開発を進めるためのノウハウメモ。
"安定"と書くといろいろな意味にとられかねないので、ここでは余計なトラブルを起こさずスムーズに進められることくらいの意味にとどめておく。

Coherenceに関して検討すべきことはアプリ面、インフラ面で両方あるがアプリ面について説明する。

途中で書き疲れたので散文失礼。

用途の決定

Coherence設計ポイントは用途によって大きく異なる

  • DBのフロントのキャシュ用途なのか
  • パフォーマンスを上げるためにメモリサーバーだけの運用するのか
  • CEP的なやつなのか

ここでは2つ目の用途について説明する。

1つめはJPAなどO/RマッパーのL2キャッシュとして使えばいいのでは。

Coherenceを使う目的をはっきり決めて方針を決めることが重要。ここが曖昧になると、安直に「とりあえずCoherenceにつっこめばいいんじゃね?」っていう流れになってしまう。これは食い止めねばならない。

前提知識

Cache方式

  • Distributed Cache
    • クラスタ上のどこか2つのノードにキャッシュを置く。
    • 書き込みは2ホップ、読み込みは1ホップ。
    • 十分速く、普通はこちらを使う。
  • Replicated Cache
    • 全てのクラスタ上の全ノードにキャッシュを置く。
    • 書き込みはNホップかかるので遅いが、読み込みは0ホップで超高速(同一JVM上のlocalキャッシュと同等)。
    • データサイズはスケールしないので、頻繁に読み込む必要がある少量のデータ用途。後述のread throughが使えない点に注意。
  • Near Cache
    • Distributed Cache + Local Cache。
    • 書き込みはLocal Cache→Distributed Cacheに行う。読み込み時にLocal CacheになかったらDistributed Cacheを探す。
    • 読み込み速度は2回目以降は超高速
    • キャッシュ削除の伝播が非同期であることに注意。

DB連携方式

  • read through
    • キャッシュ上になかったらDBから取り出しキャッシュに乗せる
  • write through
    • キャッシュに書き込んだ後、同期してDBに書き込む
  • write behind
    • キャッシュに書き込んだ後、非同期でDBに書き込む

CacheStoreを登録することで実現する。

設計編

キャッシュデータの種類

  • メモリ上にのみ存在してDBにはないもの
  • メモリにもDBにも存在し同期が必要なもの

同期が必要な方が要注意

メモリとDBの同期

DB->メモリ

更新時にDBトランザクションとの整合性が重要

DBの一部がキャッシュ上にある場合
(例:Accountテーブルのうちのユーザー名とパスワードのみキャッシュ上にある場合)

  1. DB更新(キャッシュ上のフィールド以外)+write through
  2. DB更新(全フィールド)+キャッシュ書き込み
  3. DB更新(全フィールド)+キャッシュ削除+read through

1.は実装が複雑なうえにDB更新トランザクションとwrite throughトランザクションは別なので片方DB更新が失敗したら整合性がとれない。
2.は実装は簡単になるが1.同様トランザクションが別。
3.はDBトランザクション中にキャッシュ削除。DB失敗すればキャッシュ更新しないし、キャッシュ削除に失敗したらDBロールバック。削除後キャッシュアクセスすれば最新のデータをDBから取ってこれる。

3が一番安全。
念のためトランザクションの外でもキャッシュ削除&キャッシュアクセスでリフレッシュする。

→ 更新方式を3に統一。

この前提を置いたとき、更新が必要なデータにはReplicated Cacheは使わない。
NearとDistibutedを以下のように使い分ける。

  • データ量が少なく、頻繁にアクセスするもの→Near Cache
  • それ以外→Distributed Cache

TopLinkを使っていたらまた違ったのかな。

メモリ->DB

write behindを利用して
更新が遅れるフィールドについてビジネス的に問題ないかお客さんに確認

またwrite behindでDBに書き込む前にマスターノードとレプリカノードが落ちたらデータ欠損。欠損しても良いデータかどうか。

データの検索

基本はKey-Value形式。キーアクセスでしか速くない。じゃあキー以外の項目で検索したいときどうするか?以下の2通りのやり方がある。

転置インデックス

  • メリット ネットワークホップ数が1回増えるだけなので性能は良い。
  • デメリット 転置インデックス作成、更新、削除の分、実装が複雑になる。

Filter

検索条件(where句みたいなもの)を与えて、各ノードに送り込みパラレル処理で検索する。自動indexing対応。

  • メリット 実装が容易。
  • デメリット パラレル処理=全てのキャッシュサーバーに負荷をかける。高トランザクション下では不向き。

コーディング編

安全なキャッシュアクセス

Mapとして使ってはならない。かならずTypeSafeにすること。仕様変更が入ると死ぬ。まじで死ぬ。

AccountCacheRepository extends CacheRepository<K, V>みたいなインタフェースを作ってsave(K key, V value)V find(K key)をタイプセーフにすべし。絶対すべし。

JCacheはタイプセーフな仕様になって良かった。

透過的なキャッシュアクセス

基本的にはアプリ開発者に意識させない。Coherenceのことを全員が知る必要はない(全員が好き勝手使うとカオス確定)。
AOPで透過的にキャッシュアクセスし主処理に注力してもらう。
必要なところ(=高性能が要求される箇所)のみキャッシュアクセスAPIをたたく。

Springの@Cacheableとか@CacheEvictが使えるなら使うべし。ただし、転置インデックスとか作っていると単純なキャッシュ削除、取得にはならないので自前で作る必要あり。自前で作っても透過アクセスさせることオススメ。

モックの利用

開発中からCoherenceとインテグレートしているとCoherence絡みの予期せぬトラブルで遭うことがしばしばある。出来る限り開発時はCoherenceを切り離すことで開発を安定させることをお勧めする。

自分はSpringを使っていたので、Cache Abstractionのインタフェースを利用し、開発時はMockのCache実装を使うようにした。Springのprofile機能を使って開発時とテスト時を切り替えるようにした。

プロジェクト構成の工夫

coherence-cache-config.xmlとかpof-config.xmlとかtangosol-coherence-override.xmlとかそれぞれ役割を持った設定ファイルをどこにどう置くと多重管理せずかつビルドもしやすくできるかについて検討する必要がある。今度続き書く。

サイジング編

メモリじゃぶじゃぶ青天井に使えるんなら何も気にしなくてもいいが、
不幸なことにインフラ設計だけが先に進んでいてメモリの上限がある場合は、
必ずピーク時のデータ数でサイジングすること。1 byteちりつもである。

サイジング方法はまた今度。

チューニング編

1ホップ (1キャッシュアクセス) = 1msecの見積もりでOK。
Coherenceにおけるチューニングとはネットワークホップ数を減らすテクニックになる。

データアフィニティ

関連するキャッシュオブジェクトが同じノード上にくるようにする仕組み。これにより物理的にホップ数を1減らせるようにする。
ユーザーIDにひもづくAオブジェクトとBオブジェクトを特定の業務処理を実装する際に、AとBが同じノード上に配置されるように関連するフィールド(この場合はユーザーID)を指定する。

EntryProcessor

DBでいうところのStoredProcedureに相当するもの。キャッシュノード上に処理を実装したオブジェクト(EntryProcessor)を配置してその場で実行する。↑のデータアフィニティと組み合わせ、AとBと同じノード上に配置することでEntryProcessorからA, Bはローカルアクセスすることができ爆速になる。
ただし、実装コスト(他の処理と違うやり方をしないといけないというコンテキストスイッチコスト含む)とデプロイコストを考えると何でもかんでもEntryProcessorにすればいいというものではない。どうしても性能を上げたいところだけに使用すべき。

チューニング方法はいろいろあるし、Oracleのコンサルさんも色々勧めてくると思うが、重要なのは必要になるまでチューニングしないこと 。Coherenceを採用している時点である程度のスケーラビリティは保証されている。性能テストで計測してどうしても要件を満たせなかったらテクニックを使用しよう。

ただし、超高性能を要求されるようなケースでは予めEntryProcessorに変更しうる設計を意識しておいた方が良い。

Advanced Topic

DB上の大量データ(数億件)の初期ロード方法

InvocationServiceを使って並行分散ローディングを実装。書くの疲れたので気が向いたら今度書く。


ここまで書いて何だが、Coherenceを導入する上で一番重要なことはOracleコンサルのべったりサポートを受けることである。高額だが必要経費だと思った方がいい。そういう製品なのである。
未経験な状態で設計するのは危険。カタログスペックを信用してはいけない。コンサルの人たちはその辺の善し悪しをちゃんと知っていて教えてくれる。コンサルトとよく相談して方針の妥当性についてお墨付きをもらうこと。自分はこれで目標を達成できた。正しく使えばとても強力な製品だと思う。

Coherenceの本は↓があるけど、最新バージョンでは改善されているところ(特に設定ファイルの書き方とか)も多いので鵜呑みにせず、コンサルの人に確認するのがよいと思う。

Oracle Coherence入門