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

このページではバージョン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をカラム名に持ち、シリアライズされた変更データがカラム値として格納されます。

操作

クライアントからの操作

  • カラムファミリもしくはキースペースを追加する
  • カラムファミリもしくはキースペースを削除する
  • カラムファミリもしくはキースペースの名前を変更する

これらはすべて 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_CF}}と{{MIGRATIONS_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つのノードを選び、そのノードからのみマイグレーションを生成させることです。

https://c.statcounter.com/9397521/0/fe557aad/1/|stats

  • No labels