Cache for ThunderGate

この文書では、ThunderGateのキャッシュの使い方を概説します。

キャッシュ利用の準備

キャッシュを利用するには、ThunderGateが利用するデータベース上に、次のシステムテーブルが追加で必要になります。

CREATE  TABLE __TG_CACHE_INFO (
    CACHE_ID VARCHAR(128) NOT NULL,
    CACHE_TIMESTAMP DATETIME NOT NULL,
    BUILT_TIMESTAMP DATETIME NOT NULL,
    TABLE_NAME VARCHAR(64) NOT NULL,
    REMOTE_PATH VARCHAR(255) NOT NULL,
    ACTIVE BOOL NOT NULL,
    PRIMARY KEY (CACHE_ID),
    INDEX I_CACHE_INFO_TABLE_NAME (TABLE_NAME)
) ENGINE=InnoDB;

CREATE  TABLE __TG_CACHE_LOCK (
    CACHE_ID VARCHAR(128) NOT NULL,
    EXECUTION_ID VARCHAR(128) NOT NULL,
    ACQUIRED DATETIME NOT NULL,
    PRIMARY KEY (CACHE_ID),
    INDEX I_CACHE_LOCK_EXECUTION_ID (EXECUTION_ID)
) ENGINE=InnoDB;

前者の __TG_CACHE_INFO は作成したキャッシュの情報を管理するテーブルです。 また、後者の __TG_CACHE_LOCK はキャッシュに対するロックを管理するテーブルです。 いずれも初期データは不要で、何らかの理由でキャッシュが破損した場合には、上記テーブルの内容を全て削除することで初期化できます。

これらのシステムテーブルはThunderGateのシステムテーブル作成用スクリプト [1] を実行することで生成されます。

[1]開発環境ではビルド時に自動的に生成されます。運用環境についてはシステムテーブル作成スクリプトを実行します。詳しくは ThunderGateユーザーガイド を参照してください。

Attention

補助インポーター ( Asakusa DSLとThunderGateの連携 を参照) でキャッシュを利用する場合、 そこにも上記のテーブルを用意してください。

キャッシュの仕組み

ThunderGateのキャッシュ機能を有効にした場合、テーブルのインポート処理が行われた後にもそのインポートしたデータをHadoopクラスター上に保存しておきます。

次回に同じテーブルをインポートする際に、ThunderGateは前回インポートしたデータと今回インポートするデータの差分を検出し、変更がない部分については前回保存したデータを最利用します。 このため、変更頻度が低い巨大なテーブルでキャッシュを利用すると、ThunderGateのインポート時間を大幅に削減できます。

Attention

現在、ThunderGateのキャッシュが有効なのはインポート時のみです。 エクスポートによってデータの大半が書き換えられてしまうようなケースでは、あまり効果がありません。

差分更新

ThunderGateでは、差分データを「最終更新時刻」を元に計算します。 これにはThunderGateのシステムカラム UPDT_DATETIME を利用しており、最後にキャッシュを作成した時刻以降に変更のあったデータのみを差分データとして抽出します。

抽出した差分データをHadoopクラスター上に転送したのち、前回インポートしたデータと作成した差分データを MapReduce でマージ処理します。 この処理によって、前回のインポートからの変更を反映したデータを高速に作成します。

また、ここで作成したデータは、次回の差分更新時に「前回のインポート結果」として再利用します。 つまり、キャッシュを利用すると、最初のインポート時にすべてのデータを転送した後は、毎回のインポートに前回からの差分データのみを転送することになります。

Attention

キャッシュを正しく利用するため、ThunderGateの外部からデータベースの内容を変更する際には、 必ず UPDT_DATETIME カラムにデータベース上の現在時刻 ( NOW() ) を指定しなければなりません。

削除フラグ

インポート対象したレコードに「削除フラグ」が設定されている場合、そのレコードはキャッシュ上で自動的に削除されます。 現在のキャッシュ機構では、レコードの削除を行う前に必ず削除フラグを設定してキャッシュに反映させる必要があります。

削除フラグの指定方法については、 論理削除をサポートするデータモデル を参照してください。

Warning

削除フラグを設定せずにレコードを削除した場合、そのレコードはキャッシュ上に残り続けてしまいます。 その場合、 キャッシュ管理情報の破棄 などの方法で、作成されたキャッシュを一度無効化する必要があります。

キャッシュデータの格納先

キャッシュのデータはファイルシステム上の次の位置に保存されます。

  • HDFS
    • /user/<ユーザー名>/thundergate/cache/<ターゲット名>/<テーブル名>/<キャッシュID>
  • ローカルファイルシステム (スタンドアロンモード時)
    • ~/thundergate/cache/<ターゲット名>/<テーブル名>/<キャッシュID>

キャッシュはさらに、上記ディレクトリの以下に配置されます。

キャッシュディレクトリ内の内容
パス 内容
HEAD/cache.properties キャッシュの管理情報
HEAD/part-* キャッシュされたテーブルデータ

キャッシュID

それぞれのキャッシュには「キャッシュID」が与えられていて、その情報を元に利用するキャッシュデータを特定しています。

このIDはコンパイラがインポート対象ごとに自動的に計算します。この計算には、次の設定値を利用します。

  • ターゲット名 (ThunderGateが利用するデータベースの設定情報)
  • データモデルクラス名
  • インポートする対象のテーブル名
  • インポートする対象のカラム名一覧 (順不同)

上記の項目が変更された場合、キャッシュIDが変わってしまうため、それまで利用していたキャッシュデータは無効化されます。

Note

現在のキャッシュIDの算出方法は、可能な限り同じ意味のデータに対してキャッシュを共有できるようにしています。 ただし、2つ以上の処理が同時にひとつのキャッシュを利用できないため、キャッシュIDが衝突してしまう場合には手動で設定してください。 手動での設定方法は キャッシュ運用上の注意 を参照してください。

キャッシュの利用

ThunderGateのキャッシュを利用する方法は簡単です。

まず、データベースを解析してデータモデルを作成する際に、 キャッシュをサポートするデータモデル として作成します。 つぎに、キャッシュを利用したいインポート処理に対して、 キャッシュ利用の宣言 を行います。

この2つで、ThunderGateは自動的にキャッシュを利用したインポートを行います。

キャッシュをサポートするデータモデル

バージョン0.2.3以降を利用しているプロジェクトの場合、データベースのテーブル情報を元に生成されるデータモデルには自動的にキャッシュをサポートする情報が付加 [2] されます。 そのようなDMDLをコマンドから生成する場合には、 DMDLとThunderGateの連携 を参照してください。

Attention

古いAsakusa Frameworkのバージョンを利用している場合、生成されるデータモデルはキャッシュをサポートしていません。 キャッシュをサポートするように変換する場合、 開発環境マイグレーションガイド を参照してください。

[2]implements com.asakusafw.thundergate.runtime.cache.ThunderGateCacheSupport

論理削除をサポートするデータモデル

キャッシュをサポートするデータモデルに、さらに削除フラグを利用した論理削除をサポートさせるには、 テーブルからデータモデルを生成する際のオプションを変更します。

Gradleプロジェクトを利用する場合、 build.gradle の asakusafw ブロック内の thundergate プロパティに次の内容を設定します。

論理削除のサポート
項目 内容
deleteColumn 削除フラグのカラム名
deleteValue 削除フラグが成立する値

削除フラグのカラムに利用できる型は以下に限られています。 それぞれの値は、整数、ダブルクウォートした文字列、または大文字の論理値で指定します。

利用できる型と値
型 値の例
CHAR, VARCHAR "1", "T", "D", など
TINYINT 1, 0, など
BOOLEAN TRUE, FALSE

上記の情報は、データベースに対して1組のみ指定できます。 テーブルに削除フラグのカラムが定義されていない場合には、それに対応するデータモデルが削除をサポートしません。

Attention

データベース内で削除フラグの構造が異なる場合については現在サポートしていません。

DMDLを生成するコマンドで上記を指定する場合には、 DMDLとThunderGateの連携 を参照してください。

キャッシュ利用の宣言

インポート時にキャッシュを利用するには、 DbImporterDescription [3] クラスの isCacheEnabled() メソッドをオーバーライドし、 true を返すようにします。

public class SomeImporter extends DbImporterDescription {

    @Override public Class<?> getModelType() {
        return SomeDataModel.class;
    }

    @Override public String getTargetName() {
        return "asakusa";
    }

    @Override public LockType getLockType() {
        return LockType.UNUSED;
    }

    @Override
    public DataSize getDataSize() {
        return DataSize.LARGE;
    }

    @Override public boolean isCacheEnabled() {
        return true;
    }
}

上記の他に、 computeCacheId() をオーバーライドすることで、キャッシュIDに好きな値を利用できます。

なお、キャッシュを利用する際には次の制約があります。

  • getModelType() に指定できるのは キャッシュをサポートするデータモデル のみ
  • getWhere() は指定できない ( null を返す必要がある)
  • getLockType() に指定できるのは UNUSED, TABLE, CHECK のみ
  • getDataSize() に指定できるのは UNKNOWN, LARGE のみ

Note

この制約は今後緩和される可能性があります。

[3]com.asakusafw.vocabulary.bulkloader.DbImporterDescription

キャッシュ運用上の注意

ThunderGateのキャッシュを運用するにあたって、以下の点に注意する必要があります。

  • 同一のキャッシュIDを利用するジョブは、同時に2つ以上動作させられません
    • 動作させようとした場合、ThunderGateがエラー終了します
    • DbImporterDescription.computeCacheId() をオーバーライドしてキャッシュIDを書き換えることで対処できます [4]
  • キャッシュを利用するテーブルのレコードを削除する前に、削除フラグをキャッシュに伝搬させる必要があります
  • キャッシュが壊れている場合、差分転送ではなく全データの転送を行います
    • データベースやHadoopクラスターが障害から復旧した際などに破損している場合があります
    • 正しく動作しない場合には キャッシュのメンテナンス を参照してください
[4]ただし、キャッシュデータが2重に作られるようになるため、Hadoopクラスターのディスク容量を余計に必要とします。 また、キャッシュIDの算出方法については キャッシュID を参照してください。

キャッシュデータの手動ビルド

テーブルのスキーマを変更したり、テーブルの内容を大幅に変更するなどした場合、次回のキャッシュ生成時に長い時間がかかる場合があります。 その場合、あらかじめ手動でキャッシュをビルドしておくことにより、次回のキャッシュ生成時のコストを削減できます。

キャッシュビルドを行うには、 $ASAKUSA_HOME/bulkloader/bin/build-cache.sh コマンドを利用します。 このコマンドには次の引数を指定してください。

キャッシュ手動ビルドツールの引数
位置 内容
1 ターゲット名
2 バッチID
3 フローID
4 テーブル名

上記のコマンドを指定すると、対象のバッチの対象のジョブフローに含まれる、対象のテーブルに対するキャッシュをビルドします。 この時、次のようなことに注意してください。

  • テーブルに対するロックの設定は全て無視されます (ただし、キャッシュロックは取得します)
  • 対象のターゲット名に含まれないテーブルは指定できません
  • 対象のジョブフローに含まれないテーブルは指定できません

なお、キャッシュデータの手動ビルドに失敗した場合、安全のためキャッシュロックを取得したままコマンドが終了します。 このキャッシュロックを解除するには「 キャッシュロックの解除 」の手順に従ってください。

キャッシュのメンテナンス

キャッシュ機能を利用する場合、ThunderGateは「状態」を持ってしまうことになります。 何らかの不整合が発生した場合の対処方法について紹介します。

キャッシュロックの解除

ThunderGateのキャッシュ機構は、ThunderGate本体とは別の方法でロックの処理を行なっています。 このロックはインポート処理の手前で取得され、エクスポート処理後に解放されます。

何らかの理由でキャッシュのロックが解放されなかった場合、次のいずれかの方法で開放できます。

  • $ASAKUSA_HOME/bulkloader/bin/release-cache-lock.sh コマンドを利用する
  • $ASAKUSA_HOME/bulkloader/bin/dbcleaner.sh コマンドを利用する

前者はターゲット名と実行IDを指定して、そのジョブフローに関する最低限のロックを開放します。 また、実行IDを指定しなかった場合には、すべてのキャッシュロックを開放します。

後者はThunderGateのあらゆる管理情報を初期化します。 その処理の過程で、キャッシュのロックも全て開放します。

レコードの物理削除

キャッシュの対象となったテーブルのレコードを実際に削除するには、その前に「削除フラグ」を設定してインポートし、キャッシュに削除を反映させておく必要があります。 そのため、削除フラグを設定して、すべてのキャッシュにそのフラグを伝搬されるまで、レコードを削除してはいけません。

それぞれのテーブルに対して、キャッシュが反映されている時刻を調べるには、次のような問い合わせを行います。

SELECT TABLE_NAME, MIN(BUILT_TIMESTAMP) FROM __TG_CACHE_INFO GROUP BY TABLE_NAME

キャッシュ管理情報の破棄

キャッシュが何らかの理由で破損してしまった場合や、キャッシュIDの変更により利用されなくなった場合には、キャッシュの管理情報を破棄できます。 キャッシュの削除は、 $ASAKUSA_HOME/bulkloader/bin/delete-cache-info.sh コマンドを利用します。

キャッシュ管理情報削除ツールの引数
サブコマンド 残りの引数 内容
cache target-name cache-id 指定したキャッシュIDのキャッシュのみを破棄します
table target-name table-name 指定したテーブルに関するキャッシュをすべて破棄します
all target-name すべてのキャッシュを破棄します

安全のため、この操作ではキャッシュ管理情報の無効化のみを行います。 実際にキャッシュデータを削除する場合には、 キャッシュ領域の開放 を実行してください。

キャッシュ領域の開放

キャッシュ管理情報の破棄 を行った場合、そのキャッシュは「無効なキャッシュ」としてマークされます。 この状態で同じキャッシュIDに対してキャッシュが作成された場合、そのキャッシュに使用していた領域が再利用されます。

対象のキャッシュが今後利用されない場合、 $ASAKUSA_HOME/bulkloader/bin/gc-cache-storage.sh コマンドを利用してキャッシュ用の領域を開放します。 このコマンドには、引数として対象のターゲット名を指定します。

Note

キャッシュ領域を解放すると、キャッシュ管理情報とキャッシュデータが完全に削除されます。 キャッシュ管理情報を先に削除してしまうとキャッシュデータはゴミとして残ってしまうため、 その場合には キャッシュデータの手動削除 の方法で削除してください。

Attention

キャッシュ領域の開放を行う際、開放中のキャッシュ領域が再利用されることを防ぐためにキャッシュのロックを取得しようとします。 キャッシュのロックが衝突して開放がうまくいかない場合、 キャッシュロックの解除 を実行してください。

キャッシュデータの手動削除

キャッシュデータを手動で削除するには、以下のディレクトリ以下をファイルシステム上から削除します。

  • HDFS
    • /user/<ユーザー名>/thundergate/cache/<ターゲット名>/<テーブル名>/<キャッシュID>
  • ローカルファイルシステム (スタンドアロンモード時)
    • ~/thundergate/cache/<ターゲット名>/<テーブル名>/<キャッシュID>

キャッシュデータが削除されている場合、次回のインポート時に差分転送ではなく全データの転送を行います。