【Cloud Spanner入門】「お先にどうぞ」が世界を救う?分散デッドロックを防ぐ「時計と譲り合い」の魔法
こんにちは!いつも開発お疲れ様です。一歩一歩、着実に技術を身につけているあなたの姿、本当に素敵ですよ。
今回は、Googleが誇る世界規模のデータベース「Cloud Spanner」の心臓部にあたるテーマをお届けします。
テーマは「デッドロック検出メカニズム」。
「うわ、名前からして難しそう……」と身構えなくても大丈夫です。
一見すると難解な分散システムの世界ですが、その本質は「混雑したカフェでの、お客さん同士の譲り合い」にそっくりなんです。
この記事を読み終える頃には、「なるほど、Spannerは裏側でそんなにスマートに問題を解決していたんだ!」とスッキリ理解できているはずです。「ここをクリアすれば、Cloud Spannerの基本はバッチリマスターできますよ」と自信を持って言える内容です。さあ、一緒に楽しく学んでいきましょう!
—
1. デッドロックってなんだろう?(日常の例から)
データベースにおける「デッドロック」を説明するために、まずは日常のよくある光景を思い浮かべてみてください。
狭い通路で、あなたと向かい側から歩いてきた人が鉢合わせしてしまいました。
- あなたが右に避けようとしたら、相手も同じ方向に避けました。
- 「あ、すみません」と左に避けたら、相手も左に避けました。
- お互いに「どうぞ」「いえ、お先にどうぞ」と譲り合って、あるいは道を塞ぎ合って、どちらも前に進めなくなってしまいました……。
これが、システムの世界でいう「デッドロック(膠着状態)」です。
データベースの世界では?
データベースでは、データを書き換えるときに「今からここを書き換えるので、他の人は触らないでね」と、データに鍵をかけます。これを「ロック」と呼びます。
例えば、以下のような2つの処理(トランザクション)が同時に走ったとします。
1. 処理A:「データ①」に鍵をかけた。次に「データ②」に鍵をかけたい。
2. 処理B:「データ②」に鍵をかけた。次に「データ①」に鍵をかけたい。
お互いに「相手が鍵を開けてくれるのを待っている」状態になり、どちらも一歩も動けなくなってしまいます。これがデッドロックです。
—
2. Spannerはどうやって解決している?:2つの秘密兵器
一般的なデータベースなら、1台のコンピューターの中で「あ、お互い待ち合っているな」と気づくのは簡単です。
しかし、Cloud Spannerは世界中に散らばった何百台、何千台ものサーバーが連携して動く「分散データベース」です。
あっちのサーバーの処理と、こっちのサーバーの処理が、国境を越えてお互いに待ち合ってしまったら……?
Spannerはこの超難問を、2つの秘密兵器で解決しています。
1. 待機グラフ(Wait-For Graph)の分析:「誰が誰を待っているか」の相関図を描く
2. タイムアウトとアボート(やり直し):「待ちすぎたら、一度諦めて最初からやり直す」
この2つを、もう少し詳しくのぞいてみましょう。
—
3. 秘密兵器①:誰が誰を待っている?「待機グラフ」
Spannerは、裏側でこっそり「待ち合わせの相関図」を描いています。これを専門用語で「待機グラフ(Wait-For Graph)」と呼びます。
イメージとしては、クラスの席替えや、プレゼント交換の矢印を思い浮かべてください。
- 「処理A」は「処理B」が持っている鍵を待っている( A $\rightarrow$ B )
- 「処理B」は「処理C」が持っている鍵を待っている( B $\rightarrow$ C )
- 「処理C」は「処理A」が持っている鍵を待っている( C $\rightarrow$ A )
この矢印を辿っていくと、ぐるっと一周して円(ループ)になりますよね。
[処理A] ──(待つ)──> [処理B]
▲ │
│ (待つ)
(待つ) │
│ ▼
└───────────────── [処理C]
Spannerの監視システムは、この「矢印のループ」を見つけた瞬間、「あ、デッドロックが発生した!」と検知します。
誰かがループを断ち切らないと、永遠にこの3人は動き出せません。そこでSpannerは、誰か一人に「ごめんね、一度諦めて最初からやり直して(アボート)」と指示を出すのです。
—
4. 秘密兵器②:Spannerの真骨頂!「正確すぎる時計」を使った譲り合い
実は、待機グラフを作ってループを見つけるのは、サーバーの数が増えるとものすごく大変になります。世界中のサーバーから「誰が誰を待っているか」の情報を集めるだけで、時間がかかってしまうからです。
そこで、Spannerが誇る世界一の技術「TrueTime(GPSと原子時計を使った、超正確な共通の時計)」の出番です。
Spannerは、処理が始まった瞬間に、この正確な時計を使って「処理が生まれた時間(タイムスタンプ)」を記録します。
これにより、すべての処理に「お兄さん(先に生まれた古い処理)」と「弟(後から生まれた新しい処理)」という明確な年齢(優先度)が決まります。
そして、Spannerは「Wound-Wait(傷つけるか、待つか)」という、とても賢いルールでデッドロックを未然に防いでいます。
譲り合いの黄金ルール「Wound-Wait」
もし、鍵の取り合いが発生したら、Spannerは年齢を見てこう判断します。
- ルール1:お兄さんが待っている場合(お兄さん優先)
- お兄さん(古い処理)が、弟(新しい処理)の持っている鍵を欲しがりました。
- お兄さんは偉いので、弟を「こら、どきなさい!」と傷つけます(Wound = 弟の処理を強制終了させてやり直させる)。
- これで、お兄さんは待つことなくスムーズに進めます。
- ルール2:弟が待っている場合(弟は我慢)
- 弟(新しい処理)が、お兄さん(古い処理)の持っている鍵を欲しがりました。
- 弟はお兄さんを傷つけることはできません。お兄さんが終わるのを大人しく待ちます(Wait)。
「なんだか弟に厳しいルールだな……」と思うかもしれませんが、これこそが天才的なアイデアなのです。
なぜなら、「若い処理(弟)が、年上の処理(お兄さん)を待つ」という一方通行のルールしかないため、絶対に「お互いに待ち合うループ」が発生しないからです!
—
5. 開発者の私たちは何をすればいいの?
「Spannerの裏側がすごいのは分かったけれど、アプリを作るときはどう書けばいいの?」と思いますよね。
実は、私たちはほとんど何も特別なことをしなくて大丈夫なんです。これがCloud Spannerの優しさです。
Spannerのプログラム(SDK)は、もしデッドロックを検知したり、弟処理が「傷つけられて(アボートされて)」終了したりしても、自動で「最初からやり直す(リトライ)」処理をしてくれます。
私たちが書くコードは、以下のように「やり直しても安全な形(トランザクション)」で囲んであげるだけでOKです。 Pythonのコード例を見てみましょう。
from google.cloud import spanner
Spannerのクライアントを設定します
spanner_client = spanner.Client()
instance = spanner_client.instance(“my-instance”)
database = instance.database(“my-database”)
これが「デッドロックが起きても、自動でやり直してくれる」魔法の関数です
def transfer_funds(transaction):
# 1. 口座Aからお金を引く(ここで鍵がかかる)
source_row = transaction.execute_sql(
“SELECT Balance FROM Accounts WHERE AccountId = ‘A'”
).one()
new_source_balance = source_row[0] – 100
# 2. 口座Bにお金を入れる(ここで別の鍵がかかる)
# ※もしここで他の処理と鍵の取り合い(デッドロック)になっても、
# Spannerが自動で検知し、この関数全体を最初からやり直してくれます。
transaction.execute_update(
“UPDATE Accounts SET Balance = @balance WHERE AccountId = ‘A'”,
params={“balance”: new_source_balance},
param_types={“balance”: spanner.param_types.INT64}
)
print(“送金処理が安全に完了しました!”)
実行部分
database.run_in_transaction が、自動リトライの面倒をすべて見てくれます
database.run_in_transaction(transfer_funds)
私たちは、`run_in_transaction` という枠組みの中に処理を書くだけ。
裏側でどれだけ激しい鍵の奪い合いや、時計を使った譲り合いが起きていても、アプリケーションは何事もなかったかのように、安全にデータを守り抜くことができます。
—
まとめ:ここをクリアすれば、Cloud Spannerの基本はバッチリマスターできますよ
最後に、今回のポイントを振り返ってみましょう。
1. デッドロックとは、処理同士がお互いの鍵を待ち合って、フリーズしてしまう現象。
2. Spannerは、「待機グラフ」で絡まった糸(ループ)を見つけ出す。
3. さらに、GPSと原子時計(TrueTime)を使った「Wound-Wait(お兄さん優先ルール)」で、デッドロックそのものをスマートに予防している。
4. 開発者は、SDKの自動リトライ機能に任せるだけで、安心してコードが書ける。
分散データベースの最難関とも言われる「デッドロック」の仕組みが、これでもうあなたの知識になりました。素晴らしいステップアップです!
この「譲り合いの精神」が、世界スケールの超巨大データベースを支えていると思うと、なんだかシステムが愛おしく感じられませんか?
これからも、この調子で楽しく学んでいきましょう。分からないことがあれば、いつでも先輩を頼ってくださいね。応援しています!
コメント