- AppleはiCloudとCloudKitのためにCassandraとFoundationDBを使用
- これらのデータベースは、極端なマルチテナンシー・アーキテクチャで数十億のデータベースを保存
時代を超える現実世界の教訓
- MetaとAppleはいずれも、ユーザー向け機能を円滑にするために非同期処理を使用
- 両社ともステートレス(stateless)アーキテクチャを採用し、スケーラビリティの課題を解決
- リソースを論理的に分離して信頼性と可用性を確保
- 多様な要件をシンプルに処理
- 開発者体験を改善するために抽象化レイヤーを構築
- ユーザーを理解し、各レイヤー、API、設計を決定
Cassandra
- Cassandraは広範なカラム指向のNoSQLデータベース管理システム
- もともとはFacebookでFacebook受信箱検索機能のために開発
- 興味深いことに、Meta自身はCassandra利用の多くをZippyDB ZippyDBに置き換え
- iCloudは部分的にCassandraを使用しており、Appleは世界最大級のCassandraデプロイの1つを運用
- 30万を超えるインスタンス/ノード
- 数百ペタバイトのデータ
- クラスターあたり2ペタバイト超
- 毎秒数百万件のクエリ
- 数千のアプリケーション
- CassandraはAppleで今も活発に改善されている
- しかしCloudKit + Cassandraはスケーラビリティの限界に突き当たり、FoundationDBを採用
FoundationDB
- AppleはFoundationDBを公に利用しており、2015年に買収
- FoundationDBは大規模データ処理向けに設計されたオープンソースの分散トランザクション型キー・バリュー・ストア
- 読み書き混在ワークロードにも書き込み集中型ワークロードにも適している
- AppleはCloudKitでFoundationDB Record Layerを広範に使用
- FoundationDB Record Layerは、構造化データ保存のためのJava APIを提供
- Record Layerは極端なマルチテナンシーをサポート
FoundationDBでRecord Layerを使う理由
- FoundationDBは分散システムと並行性制御の処理を担う。
- Record Layerは、FoundationDBを使いやすくするリレーショナルデータベースのような役割を果たす。
- CloudKitは最上位レイヤーにあり、アプリケーション開発者向けの機能とAPIを提供。
- Record Layerを通じてAppleは大規模なマルチテナンシーを実現
- 各アプリケーションの各ユーザーに独立したレコードストアを提供する極端なマルチテナンシーに利用
- 数千のスキーマを共有する数十億の独立したデータベースをホスト
CloudKitがFoundationDBとRecord Layerを使う方法
- CloudKitでは、アプリケーションは定義済みスキーマに従う「論理コンテナ」として表現される
- このスキーマは、効率的なデータ取得とクエリのために必要なレコード型、フィールド、インデックスを概説
- アプリケーションはCloudKit内でデータを「ゾーン」として構成し、レコードを論理的にグループ化してクライアントデバイスと選択的に同期できる
- 各ユーザーにはFoundationDB内で一意のサブスペースが割り当てられ、ユーザーがやり取りする各アプリケーション向けのレコードストアが作成される
- 基本的にCloudKitは、ユーザー数にアプリケーション数を掛けた数に相当する膨大な数の論理データベースを管理
- 各データベースには独自のレコード、インデックス、メタデータのセットが含まれ、その総数は数十億に達する
- CloudKitはクライアントデバイスからリクエストを受けると、ロードバランシングによってそのリクエストを利用可能なCloudKitサービスプロセスに転送
- 各プロセスは特定のRecord Layerレコードストアとやり取りしてリクエストを処理
- CloudKitは定義済みアプリケーションスキーマを、別個のメタデータストアに保存されるRecord Layer内のメタデータ定義へ変換
- このメタデータは、レコード作成・更新時刻やレコードが保存されたゾーンを追跡するCloudKit専用のシステムフィールドによって補強される
- 各ゾーン内のレコードへ効率的にアクセスできるよう、主キーにはゾーン名の接頭辞が付与される
- ユーザー定義インデックスに加え、CloudKitはレコードの型別サイズを追跡するインデックスなど、ストレージクォータ管理のような内部用途向けの「システムインデックス」も管理
FoundationDBとRecord Layerを組み合わせることで、Cassandra単体では解決できなかったAppleの4つの主要課題を解決
1. パーソナライズされた全文検索の問題を解決
- FoundationDBは、ユーザーが自分のデータに高速アクセスできるよう、パーソナライズされた全文検索をサポート
- FoundationDBのキー順序を活用することで、テキスト先頭部分の高速検索(前方一致)だけでなく、追加オーバーヘッドなしでより複雑な検索(近接検索やフレーズ検索のように、互いに近い位置や特定順序にある単語を探す検索)も可能
- 従来の検索システムでは、検索インデックスを最新に保つためにバックグラウンドで追加プロセスを走らせる必要があることが多いが、Appleのシステムはすべてをリアルタイムで処理するため、データ変更と同時に検索インデックスも即時更新され、追加ステップが不要
2. 高並行ゾーンの問題を解決
- CloudKitはFoundationDBを使って、同時発生する大量の更新を円滑に処理
- 以前Cassandraを使っていた際、CloudKitは複数デバイス間でデータを同期するため、各ゾーンの変更を追跡する特別なインデックスに依存
- デバイスがデータ更新時にこのインデックスを確認して新しい内容を把握
- ただし、複数の更新が同時に起こると競合が発生しうる欠点があった
- FoundationDBを使うと、CloudKitは競合を起こさず各変更の正確な順序を追跡する特殊な種類のインデックスを利用できる
- これは、すべての変更に一意の「バージョン」を割り当て、同期が必要な際にCloudKitがそのバージョンを確認して、デバイスが見逃した更新を特定することで実現
- 負荷をより均等に分散するため、CloudKitが複数のストレージクラスター間でデータを移動する必要がある場合、各クラスターのバージョン番号が一致しないため状況は複雑になる
- この問題を解決するため、CloudKitは各ユーザーのデータに「移動回数」(「incarnation」と呼ばれる)を付与し、データが新しいクラスターへ移されるたびに増加させる
- 各レコード更新にはユーザーの現在の「incarnation」番号が含まれるため、移動後もCloudKitはincarnationとバージョン番号の両方を確認して正しい更新順序を判断できる
- 新システムへ移行した際、CloudKitはこうしたバージョン番号を持たない旧データを処理する問題に直面
- しかし旧システムの過去更新を新システムより前に並べる特別な機能を用いて、この問題を巧みに克服
- そのおかげでアプリを複雑に変更したり古いコードを残したりする必要がなかった
- 正しい履歴順序を保つため、incarnation、バージョン、旧更新カウンター値を考慮
3. 高レイテンシクエリの問題を解決
- FoundationDBは低レイテンシではなく高い並行性のために設計されている。つまり、個々の処理速度よりも多数の処理を同時にこなすことに重点を置く
- この設計を最大限活用するため、Record Layerは多くの処理を「非同期」に実行
- 将来完了する処理をキューに積み、その間に別の作業を進められるようにする
- このアプローチは、そうした処理中に生じうる遅延を吸収するのに役立つ
- しかしFoundationDBがデータベースとの通信に使うツールは、ネットワーク処理に単一スレッドを使い、一度に1つの作業しか行わない設計
- 以前のバージョンでは、この構成のためにすべての作業がこのネットワークスレッドで順番待ちとなり、システムでトラフィックの詰まりが発生
- Record Layerもこの単一スレッド方式を使っていたため、ボトルネックになっていた
- これを改善するため、Appleはこのネットワークスレッドの作業負荷を軽減
- これにより、システムはキューを作らずに複数の側面から同時にデータベースを扱えるようになり、複雑な処理が高速化
- こうして、システムは1つの処理完了を待ってから別の処理を始める必要がなくなり、レイテンシや見かけ上の遅さが隠蔽される
4. 競合するトランザクションの問題を解決
- FoundationDBでは、あるトランザクションが特定キーを読み取っている最中に、別のトランザクションが同じキーを変更すると「トランザクション競合」が発生
- FoundationDBは、読み取りまたは書き込み時にこうした競合を引き起こしうるキー集合を制御する機能を提供し、競合を精密に管理できる
- 不要な競合を避ける一般的な方法は、競合を発生させない特殊な読み取り、すなわち「スナップショット」読み取りをさまざまなキーに対して行うこと
- この読み取りで重要なキーが見つかった場合、トランザクションは範囲全体ではなく、潜在的に競合する特定キーにだけフラグを立てる
- これにより、トランザクションは実際に結果へ重要な影響を与える変更だけの影響を受けるようにできる
- Record Layerはこの戦略を使って、ランキングインデックスシステムの一部であるスキップリストという構造を効率的に管理
- ただし、この競合範囲を手動で設定するのは難しく、とくにアプリケーションの主要ロジックと混在している場合、特定しづらいバグにつながる可能性がある
- そのためFoundationDB上に構築するシステムでは、こうしたパターンを扱うために、カスタムインデックスのような高水準ツールを作るのが望ましい
- このアプローチは、競合ルール緩和の責任を各クライアントアプリケーションに委ねてミスや不整合を招く状況を防ぐのに役立つ
1件のコメント
Hacker Newsの意見
あるHacker Newsユーザーは、Appleで働いていたときにデータベースとファイルシステムの違いについての洞察を共有している。データベースとファイルシステムは本質的に同じ機能を果たしており、特定の問題を解決するための最適化にすぎないと述べている。たとえば、iCloudはデータベースを基盤としてファイルシステムを定義する方法を示している。このユーザーは、動画保存のためにCassandraを使った経験も共有している。
別のユーザーは、以前の会社でFoundationDBとRecordLayerを使ってトランザクショナルなカタログシステムを構築した経験に触れている。このシステムは非常に効果的で、gRPCとProtobufを使うのも自然だったという。しかし、FoundationDBを大規模運用するための参入障壁が高いという欠点を指摘している。
あるユーザーは、Apple Notesの同期機能はMarkdownベースのノートアプリケーションよりも競合の処理が優れていると評価している。その結果、最終的にApple Notesへ移行したと述べている。
FoundationDBに関する過去の投稿にも言及されている。そこには、FoundationDBの分散キーバリューストア、Record Layer、Appleによる買収、そしてFoundationDBの仕組みや特徴に関するリンクが含まれている。
クラウドベースのストレージとコラボレーションへ段階的に移行していくネイティブデスクトップソフトウェアのアーキテクチャについて、興味深い点が挙げられている。スキーマ変更やバージョン移行をうまく処理することが重要であり、しかもそれは管理者の介入なしに大規模で発生する。
あるユーザーは、iCloudがTime Machineバックアップを保存できるようになってほしいと望んでいる。
FoundationDBがSQLiteを基盤にしていることから、HCTreeエンジンがFoundationDBに適用される可能性への疑問が呈されている。HCTreeには、SQLiteの読み書き性能を10倍向上させられる可能性がある。
iCloudがユーザーのファイルをどのように管理するかについての不満もある。iCloudが最近使ったファイル、アプリ、写真を自動的にクラウドへ移して容量を確保する動作が、問題になることがある。
あるユーザーは、過去に銀行で働いていたときに使っていたHyperionというレポーティングシステムを回想している。このシステムはレポートごとに新しいデータベースを作成しており、当時は奇妙に思えたが、今にして思えば時代を先取りしたやり方だったと述べている。