注文を確定したら、倉庫のシステムへ出荷依頼を送る。データベースの更新は成功したのに、メッセージの送信だけ失敗すると、注文は「確定」なのに出荷されません。逆に、メッセージを送った後でデータベースの更新が取り消されると、存在しない注文の出荷が始まります。データベースとメッセージキューを別々に書く「二重書き込み」で起きる、業務システムの典型的な不整合です。
Google Cloudは2026年10月2日の公式ブログで、Spannerにトランザクション内でメッセージを扱える「Spanner queues」の一般提供を始めたと発表しました。ブログはAIエージェントの用途を中心に説明していますが、注文処理や在庫のワークフローにも使えるとしています。ブログとSpanner APIの仕様を読み、二重書き込みの問題にどう効くかを整理します。
二重書き込みで起きること
ブログは、状態の更新とメッセージの送信を別のシステムで行う場合の失敗を2つ挙げています。
- データベースの更新が成功し、メッセージの送信が失敗する:決めたのに実行されない
- メッセージの送信が成功し、データベースの更新が取り消される:無効な状態にもとづいて実行される
これを避けるために、開発者は「アウトボックス」(送るべきメッセージをいったん同じDBの表に書き、別の処理が拾って送る仕組み)や、重複して処理しないための仕組み、ずれを直す処理を自前で作ってきた、とブログは説明しています。
Spanner queuesでは、メッセージの作成が同じトランザクション内の書き込みの1つになります。状態の更新と次に実行すべき処理が、両方とも確定するか、両方とも取り消されるかのどちらかになります。

SQLでの書き方
キューは、SpannerのGoogleSQLで定義・操作する表のような構造です。ブログにある返金の承認の例を引用します(SQLのコメントは編集部が要約)。
CREATE QUEUE OrderAgentTasks (
OrderId STRING(64) NOT NULL,
TaskId STRING(64) NOT NULL,
TaskType STRING(64) NOT NULL,
Payload JSON NOT NULL
) PRIMARY KEY (OrderId, TaskId, TaskType), INTERLEAVE IN Orders;
-- 同じ読み書きトランザクションの中で
UPDATE Orders
SET Status = 'REFUND_APPROVED',
UpdatedAt = PENDING_COMMIT_TIMESTAMP()
WHERE OrderId = @orderId;
INSERT INTO OrderAgentTasks (OrderId, TaskId, TaskType, Payload)
VALUES (@orderId, @taskId, 'EXECUTE_REFUND',
JSON '{"action": "execute_refund", "amount": 49.99}');
ブログは、この形なら「注文の状態が承認済みに変わったときに限り、返金のタスクが存在する」と説明しています。
ほかに、ブログは次の書き方を示しています。
- 遅らせて配信する。 システム列の
DeliverTimeに時刻を入れると、その時刻まで受信側に見えません。例では72時間後に上長へのエスカレーションを予約しています - 予約を取り消す。 承認が先に来たら、状態の更新と同じトランザクションで
DELETEし、予約したメッセージを消します - 受け取る。 処理する側は
RECEIVE_<キュー名>という関数をストリーミングで読み、メッセージを一定時間借り受けます(リース)。時間がかかる処理はRENEWLEASE_<キュー名>で延長します - 完了を記録する。 結果の書き込みとメッセージの
DELETEを1つのトランザクションで行い、ASSERT_ROWS_MODIFIED 1を付けます。リースが切れて別の処理がすでに片付けていた場合は、ここでエラーになり、古い処理が新しい状態を上書きするのを防げます
「ちょうど1回」は自分で仕上げる
ブログの表現は「少なくとも1回の配信(at-least-once)と、高々1回の完了記録(at-most-once ACK)を保証し、これによってちょうど1回の処理を実現できる」です。同じメッセージを2回受け取ることはありうる前提です。外部のAPIを呼ぶ場合、ブログは TaskId を相手側の冪等キー(同じ依頼を2回受けても1回分として扱うための値)に渡す例を示しています。決済や出荷のように二重実行が問題になる処理では、この設計が欠かせません。
使う前に確かめること
変更ストリームとの使い分け
Spannerには、データの変更を取り出す「変更ストリーム(change streams)」もあります。ブログは、変更ストリームは分析や保存先への継続的な変更データの取り込み向け、キューはリースや予約配信、トランザクション内での完了記録を伴うタスクの実行向け、と分けています。
クライアントライブラリの対応
Spanner API v1の仕様(Discoveryドキュメント、revision 20260909)では、書き込みの単位である Mutation に、キューへの send と ack が入っています。send は deliverTime を指定でき、ack は ignoreNotFound で、存在しないメッセージの完了記録を成功扱いにできます。
各言語の公式ライブラリのCHANGELOGには、次の版で「Send and Ack」の対応が書かれています。
- Go:1.87.0(2025年12月10日)、1.88.0(2026年2月11日)
- Java:6.109.0(2026年2月2日)
- Python:3.60.0(2025年12月10日)
Node.jsは、GitHubのnodejs-spannerリポジトリのCHANGELOGが8.6.0(2026年1月28日)で止まっており、該当する記載はありません。一方、npmで公開されている @google-cloud/spanner 9.0.0(2026年9月18日公開)の型定義には、queueSend と queueAck(ignoreNotFound を指定できる)がありました(10月5日に確認)。
公式ブログに書かれていないこと
対象のエディション、料金、キューごとの上限、Spanner Omni(自社環境で動かす版)で使えるかどうかは、ブログに記載がありません。Google Cloudのドキュメントは編集部の環境から開けず、確認できていません。採用を検討するときは、ドキュメントで確かめてください。
既存のシステムで考えること(編集部の整理)
すでにPostgreSQLなどで業務システムを動かしているなら、同じDBにアウトボックス表を置く方法でも二重書き込みは避けられます。キューのためだけにSpannerへ移る理由にはなりにくく、DBを増やす前に1つのDBでどこまで持つかはPostgres1本で持たせる判断で整理しています。
Spanner queuesが効くのは、すでにSpannerを使っている、またはこれから使う構成で、外部のキューとアウトボックスの中継処理をなくしたい場合です。Spannerを自社環境で動かす選択肢はSpanner Omniの記事で扱っています。
2026年10月3日に、Google Cloud公式ブログ「Announcing Spanner queues: Transactional messaging for agentic workloads and beyond」(2026年10月2日)、Spanner API v1のDiscoveryドキュメント(revision 20260909)、googleapisのgoogle-cloud-go(spanner)・java-spanner・python-spanner・nodejs-spannerのCHANGELOGを直接開いて照合しました。10月5日にnpmの
@google-cloud/spanner9.0.0の型定義も確認しました。Spannerのドキュメント(docs.cloud.google.com)は編集部の環境から開けず、エディション・料金・上限は確認していません。実際のSpannerインスタンスやエミュレーターでの動作は検証していません。
業務システムのデータ設計や、DBと外部サービスの連携の見直しは、開発・AI・自動化のご相談からお問い合わせください。
Sources
- Announcing Spanner queues: Transactional messaging for agentic workloads and beyond — Google Cloud Blog
- Cloud Spanner API v1 — Discovery document
- googleapis/google-cloud-go — spanner/CHANGES.md
- googleapis/java-spanner — CHANGELOG.md
- googleapis/python-spanner — CHANGELOG.md
- googleapis/nodejs-spanner — CHANGELOG.md
- @google-cloud/spanner — npm registry








