稼働中のクラスタのスキーマを変更する

このページではバージョン0.7で利用可能になる機能について議論します(現時点でsvn trunkでのみ利用可能です)。

スキーマアップデートの中身

新しく導入されたdefinitionsシステムテーブルが2つのデータの追跡をします: キースペースの定義(SCHEMA_CF)とキースペースの変更(MIGRATIONS_CF)です。変更をスキーマとマッチさせるために全体的にTimeUUIDが利用されています。

キースペースの定義 (SCHEMA_CF)

現在のキースペース定義は、1行のデータとして格納されます。1つのキースペースがカラムに格納され、TimeUUIDを行のキーとして(それはバージョンを識別するのにも利用されます)、キースペースの名前がカラム名となり、定義をシリアライズしたものがカラムの値に入ります。"Last Migration"というキーをもつ特別な行があり、現在のスキーマのバージョンのUUIDを保持します。これは現在のバージョンを探しやすくするためです。

マイグレーション (MIGRATIONS_CF)

MIGRATIONS_CFはスキーマに対する個々の変更点(追加、削除、名前変更)を追跡します。 "Migrations Key"というキーを持つ単一行で構成され、1つの変更が一つのカラムに格納されます。 個々のカラムは変更のバージョンを管理するUUIDをカラム名に持ち、シリアライズされた変更データがカラム値として格納されます。

migrations.gif

操作

クライアントからの操作

これらはすべて Thrift API を通して実行されます。セキュリティ機能を利用している場合は、ALLのアクセス権限を持っていることが前提となります。名前変更もしくは削除を行う場合、関連するすべてのファイルが名前変更もしくは削除されるまで、クライアントはブロックされます。

サーバ側のマイグレーションプロセス

マイグレーションの適用は次のステップで実行されます:

  1. 新しいバージョンのUUIDを含むマイグレーションの生成。
    1. DROP時のみ: 削除対象のデータのスナップショットを作成。

  2. SCHEMA_CFに新しいスキーマの行を追加して更新。

  3. MIGRATION_CFにマイグレーションカラムを追加。

  4. SCHEMA_CF"Last Migration"を更新。

  5. 定義テーブルをフラッシュ。
  6. 実行時のデータ構造を更新(ディレクトリの作成、削除の実行、など)。

失敗時の挙動

更新のプロセスの中のどのステップにおいても、変更の失敗が発生する可能性があります。更新プロセスの中の個々のステップ後に障害が発生した場合、何が起こるのかを詳しく見ていきます(上記のサーバ側のマイグレーションプロセスを参照してください)。

  1. 何も適用されない。更新はすぐに失敗する。
    1. 同じ. ただしスナップショットが残る。
  2. SCHEMA_CFに余分なデータが残るが、"Last Migration"の行が更新されないため無視される。
  3. SCHEMA_CFとMIGRATION_CFに余分なデータが残るが、"Last Migration"の行が更新されないため無視される。
  4. 故障: 再起動時にスキーマがロードされた *後* までコミットログは再生されません。つまり"Last Migration"は読み込まれますが、ロードして適用することができません。

  5. 正常に起動する。
  6. 正常に起動する。

マイグレーション中にノード障害が発生した場合、手動でクリーンアップしないといけない場合があります。例えば、DROP時のステップ4もしくはステップ5の後にノード障害が発生した場合、手動でデータファイルを削除する必要があります(削除しなくても特に害をなしませんが、後にADDを利用して同じCFを再作成した場合に問題が起こります。その場合すでにデータベースができあがってしまっています)。

起動

ノードの起動時、SCHEMA_CFをチェックして一番最新のスキーマのバージョンを探します。 何もみつからなければ (新規にクラスタを構築した場合)、何も読み込まれずに警告ログが出力されます。それ以外ではUUIDを利用してSCHEMA_CFから正しい行を読み込みます。その行は1つ以上のキースペース定義にデシリアライズされます。それらは以前使われていたload-from-xmlに似たアプローチでロードされます。

それと同時に、ノードは自身のスキーマのバージョンのUUIDを他のノードへ送るゴシップダイジェストへ含めます。このノードが最新のスキーマ定義情報を持たないケースがありえます(ネットワークパーティショニング、新しいノードのブートストラップなどの理由によって)。もしバージョンに不一致がある場合、次で説明する定義の伝搬メカニズムが実行されます。

定義情報の伝播

定義情報の伝播は2つのフェーズから構成されます: アナウンスプッシュです。 アナウンスは、ノードAがノードBに対して”これが自分の持っているスキーマのバージョンだ”と宣言する方法です。もしバージョンが一致する場合、メッセージは無視されます。 もしAが新しい場合、BはアナウンスをAに返します(これは更新リクエストのように機能します)。もしAが古い場合、BはAが持っていないBのマイグレーションデータのすべてとともにプッシュを返します。

クライアント(Thrift)からスキーマ更新が行われた場合、ゴシップ伝播はバイパスされ、マイグレーションを他のノードに伝えるのにこのアナウンス-アナウンス-プッシュアプローチが利用されます。

新規のクラスタ(バージョン0.7で構成)

新規クラスタの場合、1つのノードから始めて、Thriftを利用して必要なスキーマを定義していくことが、一番うまくいきます。その後新しいノードを起動すると、自動的に最初のノードから情報を取得します(または大きいクラスタでは相互に行います)。

もしくはこの時点で最初のノードを停止して、クラスタ内の他のノードにSCHEMA_CFMIGRATIONS_CFを手動でコピーすることができます。

スキーマ変更の簡単な適用方法は、bin/cassandra-cliを利用することです。インタラクティブモードで実行することもできますし、コマンドを記述したファイルを用意してバッチモードで適用することもできます(helpもしくはhelp <command>で利用可能なコマンドを見ることができます)。例えば:

$ cat schema.txt
/* キースペースを作成 */
create keyspace Keyspace1 with replication_factor = 3 and placement_strategy = 'org.apache.cassandra.locator.RackUnawareStrategy'

/* 作成したキースペースへ移動 */
use Keyspace1

/* カラムファミリを作成 */
create column family Standard1 with column_type = 'Standard' and comparator = 'BytesType'
create column family Standard2 with column_type = 'Standard' and comparator = 'UTF8Type' and rows_cached = 10000
$ bin/cassandra-cli --host localhost --batch < schema.txt

既存のクラスタ (0.6からのアップグレード)

後方互換性を維持するため、storage-conf.xmlから手動でスキーマ定義をロードできるJMXのメソッドをStorageServiceMBeanに提供します。これは一度限りの操作です。まだマイグレーション情報がないシステムでのみ動作します。クラスタをアップグレードする場合、シードとなっている1つのノードだけに適用すればよいはずです。他のノードには、それらが起動した際にゴシップを通じて変更が伝播されます。

(私のように)どのように行えばいいか分からない人へ:

ps aux | grep cassandra # cassandraのpidを取得
jconsole PID

MBeans -> org.apache.cassandra.db -> StorageService -> Operations -> loadSchemaFromYAML

最後に、上記のJMXの場所を覚えることなく同じことが実行できるシステムツールがあります。

bin/schematool HOST PORT import

並列性

あるノードが複数のノードからマイグレーション情報を受信することは十分考えられます。 このことから、すべての変更の摘要はシングルスレッドのステージで行われます。そして、全体を通してバージョンをチェックし、変更が2回適用されていないかをチェックします。同期なしに変更は適用されません。

すべての変更は次に適用される変更のバージョンUUIDを知っています。もしノードにマイグレーションの適用が指示され、その現在のバージョンUUIDが最後のマイグレーションのバージョンUUIDをマッチしない場合、そのマイグレーションは破棄されます。

このモデルの弱点の1つは、他の更新がすべてのライブノードに伝播してくる前に新しい更新が始まる場合に弱いことです--ただ一つのマイグレーションしかクラスタ内に存在できません。これを回避する一つの方法は、1つのノードを選び、そのノードからのみマイグレーションを生成させることです。

stats

LiveSchemaUpdates_JP (last edited 2013-11-14 21:58:53 by GehrigKunz)