Swiftの機能

処理フローと結果整合性(EventualConsistency)

実際にSwiftに対してオブジェクト操作が行われた時の内部の動きを追いかけてみましょう。

【オブジェクトのGET】

1. プロキシは、クライアントからのGET要求を受け付ける。

2. プロキシは、アカウントサーバにアカウントの存在を問い合わせる。

3. アカウントサーバは、アカウント情報を返却。

4. プロキシは、コンテナサーバにコンテナ情報を問い合わせる。

5. コンテナサーバは、コンテナ情報を返却する。ACL情報も含まれる。

6. アクセス権があれば、プロキシはオブジェクトサーバに、オブジェクト取得を要求する。

7. オブジェクトサーバは、オブジェクトの中身を読みだし、プロキシに返却する。

8. プロキシは、クライアントに、オブジェクトの中身を返却する。

アカウント、コンテナ、オブジェクトのアクセスのいずれかが失敗することがあります。その場合、プロキシは同じデータの複製を持つサーバを選びリトライを行います。
データの複製をもつサーバすべてが障害を起こしていない限り、目的のオブジェクトを取り出すことができます。

【オブジェクトのPUT】

オブジェクトへの書き込み時は、データの複製全てを更新しなければなりません。
3冗長設定の場合、3つのオブジェクトの複製、3つのコンテナの複製、3つのアカウントの複製すべてを更新する必要があります。

1. プロキシは、クライアントからのPUT要求を受け付ける。

2. プロキシは、アカウントサーバにアカウントの存在を問い合わせる。

3. アカウントサーバは、アカウント情報を返却。

4. プロキシは、コンテナサーバにコンテナ情報を問い合わせる。

5. コンテナサーバは、コンテナ情報を返却する。ACL情報も含まれる。

6. アクセス権があれば、プロキシは複数のオブジェクトサーバに、オブジェクト書き込みを要求する。

7. 各オブジェクトサーバは、受け取ったデータのオブジェクトへの書き込みが完了すると、コンテナサーバにオブジェクト登録を要求する。どのコンテナサーバに通知するかは、プロキシサーバにより予め決められている。

8. 各コンテナサーバは、コンテナの更新が完了すると、アカウントサーバにコンテナ情報に更新があったことを通知する。アカウントサーバは、受け取った情報を元に、アカウントの統計情報を更新する。

9. 各オブジェクトサーバは、プロキシにオブジェクト書き込み処理完了を通知する。

10. プロキシは、クライアントに、オブジェクトの中身を返却する。

ネットワーク障害やサーバダウンなどにより、オプジェクトのPUT処理の一部が失敗することがあります。
後から修復可能なエラーであれば、多少のエラーがあっても、クライアントには正常終了したものとして返事をします。

a. 選択されたオブジェクトサーバのひとつにネットワーク接続できない場合
プロキシサーバは代替のオブジェクトサーバを選びます。
ネットワーク障害が解消された後、レプリケータによる修復処理が行われ、本来あるべきオブジェクトサーバにオブジェクトがコピーされます。

b. あるオブジェクトサーバが、プロキシにエラーを返して来た場合
オブジェクトの書き込みを依頼しているオブジェクトサーバの半数以上が、オブジェクト書き込み処理に成功したと返事した場合、クライアントに対しては成功と返事します(多数決原理)。
障害が解消されれば、レプリケータによる修復処理が行われ、本来あるべきオブジェクトサーバにオブジェクトがコピーされます。もし、すぐに障害が解消されない場合は、代替のオブジェクトサーバが選択され、そこにオブジェクトの複製が置かれます。

c. オブジェクトサーバからコンテナサーバへの通信が失敗した場合
オブジェクトサーバの処理は成功したものとしてクライアントに通知します。
この場合、オブジェクトサーバと対になって動作するアップデータが、成功するまで通信のリトライを繰り返し行い、最終的にはコンテナサーバの情報が更新されます。
レプリケータのリトライ処理が成功する前に運悪くこのオブジェクトサーバがダウンした場合でも、コンテナサーバのレプリケータにより、最終的にコンテナ情報の同期が行われます。

Swiftは、ネットワーク障害などが発生している時でも応答性を確保できるように、ネットワーク通信のタイムアウト値を非常に短く設定しています。そのため、システムに一時的に負荷が掛かった時などにも、比較的容易にこれらの状態が発生します。

【結果整合性(Eventual Consistency)】

オブジェクトのPUT処理においてエラーが発生した場合でも、最終的にはレプリケータやアップデータの働きにより、全てのオブジェクトサーバ、コンテナサーバ、アカウントサーバの情報が最新状態に揃うことが保証されます。
では、クライアントが、PUTしたばかりのオブジェクトに対して、GET処理を行った場合、どのような結果になるでしょうか?

 

  • オブジェクト一覧にアップロードしたオブジェクトが無いことがある
    オブジェクトサーバからコンテナサーバへの通信に失敗したものがある場合、コンテナサーバの一部は古いコンテナ情報をのままになっています。
    このタイミングでこのコンテナにアクセスした場合、運が悪いとオブジェクトが登録される前の古いコンテナ情報を参照してしまうことがあります。
  • 古いオブジェクトデータを読み出してしまうことがある
    オブジェクトの上書きを行った場合、あるオブジェクトサーバ上のオブジェクトの更新に失敗していても、オブジェクトのPUT処理は成功します。
    直後のGET処理時に、更新の失敗したオブジェクトサーバが選択されると、一世代前のオブジェクトにアクセスすることになります。
    実は、オブジェクトの上書きを行った場合は問題になりますが、オブジェクトの新規アップロードの場合には、問題になりません。オブジェクトGET時にオブジェクトのアップロードに失敗したオブジェクトサーバが選択されても、GET処理そのものが失敗し、次のオブジェクトサーバに対してオブジェクトのGET処理がリトライされるためです。

Swiftがこのような実装方針を採用している理由は、スケーラビリティ確保です。
Swiftは、整合性とスケーラビリティを天秤に掛けた結果、スケーラビリティの方を選択しています。

最終的には全てのサーバのデータが最新状態に揃うことが保証されますが、それでは問題のあるアプリケーションのために、Swiftはオブジェクトのアップロード直後から最新のオブジェクトへのアクセスを保証できるようにするための仕組みも提供しています。

クライアントがGETリクエストを送る時、「X-Newestヘッダ」を含めることにより、プロキシの動作を変えることができます。この場合プロキシは、全てのオブジェクトサーバに接続し、それらの中で最も新しいオブジェクを確認して取り出します。

障害発生と復旧

レプリケータは、本来データの複製があるべきところに、最新データを配置しようとします。各レプリケータは、自マシン上のデータと本来あるべきマシン上の複製とを照合し、それら全てが最新のデータを持つようにします。複数あるデータの複製のひとつが掛けてしまった場合、または古いデータが残っている場合、サーバ故障などにより代替のサーバ上にデータが置かれている場合など、障害の原因が取り除かれるとただちに、自動的にあるべきデータ配置に修復されることになります。

レプリケーション処理は、データを持っているマシン上のレプリケータが行います。自マシンのデータ障害を修復するために、正しいデータをもつマシンを探して、そこからデータをコピーして来るという動きはしません。データ障害をもつマシン上のレプリケータは単に口を空けて待つことが仕事です。口を空けていれば、最新のデータを持つ別のマシンのレプリケータから、データ転送が行われることになります。

オブジェクトのレプリケータの場合、もともとこの異なるマシン間のデータの同期ずれの検出とデータの転送を、全てrsync機能に任せていました。しかし、この処理は非常にCPU時間を消費するため現在は少しチューニングが施されています。一度同期が確認された箇所は、その箇所に何か変更が発生する、または障害が発生するまでは、rsync処理の対象から外すように工夫しています。

何らかの障害からの復旧後にレプリケータが動作するときには、システム上に新旧のデータが混在している可能性があります。ネットワーク障害やサーバダウンが発生中にオブジェクトが更新されると、新旧世代のオブジェクトが同時に存在する状態になります。障害からの復旧後、互いに自分の持っているデータを相手に送り合います。その結果、一つのオブジェクトサーバ配下に、一つのオブジェクトに対して複数バージョンのオブジェクトの複製が集まることがあります。オブジェクトサーバ上に保存されるオブジェクトは更新のたびに新しいタイムスタンプを付与するので、rsyncでコピーして一箇所に集めても、オブジェクトが上書きされることなく、タイムスタンプが異なるオブジェクトの複製が複数共存する状態になります。

このような状態になったとしても、オブジェクトサーバは一番新しいタイムスタンプが付いているオブジェクトの複製を有効として扱うため、古い世代のオブジェクトがアクセスされることはありません。また、ここでオブジェクト削除時に残すtombstoneファイルが重要な役目を果たします。tombstoneファイルを残すことにより、オブジェクト削除状態もrsync機能により他のマシンに伝搬させることが可能となります。Tombstoneの仕組みが無ければ、削除したはずのオブジェクトが復活してしまうという現象を目にするかもしれません。

レプリケータは、ゴミ掃除の仕事も担当しています。一つのオブジェクトに対して複数世代のオブジェクトの複製が残っている場合、古い世代のものは削除します。また、tombstoneも十分長い時間が経過した後には削除してしまいます。

オブジェクトのデータの同期にはrsyncを利用すると説明しましたが、コンテナやアカウントのレプリケータは、更新のあったレコードの情報のみを送り合う仕組みも併用しています。前回の同期処理を行った後に更新されたレコードより後に更新されたレコードの整合性確認のみを行います。

ハードウェアの交換とリング再構成

Swiftの運用を続けていると、マシンを追加してシステムの能力を増強したり、調子の良くないマシンを交換することが必要になってきます。

このような場合Swiftでは、リングを新しいハードウェア構成に合わせて作り直し、全てのサーバに配布し直す必要があります。新しいリングの更新処理は以下のことを考慮して行われます。

 

  • システムが止まらないこと。Swift上のオブジェクトやコンテナが常にアクセスできる状態にあること。
  • オーバヘッドが小さいこと。

リングの再構成は、オフラインでリングビルダという専用ツールを用いて行います。
リングビルダは、パーティションとディスクの対応づけ情報を変更します。前回リングを作成した時の情報を、リング作成時に作られる中間ファイル(ビルダファイル)から取り出し、マシンの追加削除情報と組み合わせて新しいリングを作成します。このとき、以下の事項を考慮してパーティションの再配置を行います。

 

  • 同じzoneにはパーティションの複製を置かない。
  • あるパーティションに対応する複製のうち、複数を同時に移動させない(配置場所を変更しない)。
  • 最近移動を行ったばかりのパーティションは、移動対象から外す。
  • 移動しなければならないパーティションの複製の数をなるべく少なくする。
  • 可能な限り、全てのディスクに偏り無くパーティションを配置する。

リングビルダは、何度もパーティション配置を計算し直し、ベストに近い配置方法を求めます。

新しいリングができたら、全てのサーバに配布します。サーバは一定時間毎にリングを監視しており、リングが更新されたことを検知するとリングの情報をサーバのメモリ内に読み込みます。
各サーバにリング配布する時刻およびリングの情報がメモリに読み込まれるタイミングは、サーバ毎にズレが生じます。

これが意味するところは、リングの更新時には古いリングの情報で動作しているサーバと、新しいリングの情報で動作しているサーバが混在しているということです。
混在していたとしても、既にあるオブジェクトのデータは読み出せますし、新しいオブジェクトにデータを書き込むこともできます。
古いリングの情報に示されたサーバにデータが書き込まれてしまうかもしれません。しかし、最終的には全てのリングが最新になり、レプリケータがその新しいリングに従ってデータの移動を行い、全てのデータの整合性が保たれた状態になります。

認証機能

Swiftの認証システムは任意のものを組み込み可能です。
現在、以下の3つから選択して利用できます。

 

  • Swauth
  • OpenStack Identity – Keystone
  • tempauth

Swift単体で運用する場合は、Swift専用の認証システムswauthを選ぶと良いでしょう。swauthは、認証データ自体もオブジェクトしてSwift上に保存しています。これにより、認証データの冗長化とアクセス時の負荷分散を実現しています。

OpenStack Compute – Nova と組み合わせて利用する場合は、OpenStackプロジェクト共通の認証モジュールIdentity(Keystone)を利用します。

Swiftの認証を、既存システムの認証システムと連係させたい場合には tempauth を利用します。Swiftプロジェクトが提供しているtempauthは移植用のサンプルコードであり、このコードを改造して既存システムと連係させることが求められます。

いずれの認証システムを選択した場合でも、認証の仕組みは同一です。ユーザクライアントは、Swiftを利用するにあたり、認証システムにアカウント名、ユーザ名、パスワードを渡し、トークンを発行してもらう必要があります。認証システムはアカウント名、ユーザ名、パスワードの有効性を確認し、クライアントにトークンを返却します。
以降、ユーザクライアントはSwiftのAPIを呼び出す時は必ずこのトークンをリクエストヘッダに埋め込まねばなりません。認証システムは、API呼び出し毎にトークンの有効性をチェックし、またトークン情報を基にアクセス権を制御します。

Swiftはトークン情報をmemcachedにキャッシュし、認証処理のスケーラビリティを確保しています。このトークンには有効期限があり、この期限を過ぎてトークンを利用しようとすると、認証システムは認証エラーを返します。クライアントは、一定時間毎にトークンの再発行手続きを行うことが求められます。