製品
ソリューション
会社情報
エンタープライズ
サインインネットワークを作成
architecture · schema-evolution · compatibility · platform-engineering · honest-architect

バージョン重複が計測されないときスキーマ進化は壊れる

スキーマ変更が本番で壊れるのは移行が悪いからではなく、バージョン重複が計測されていないからだ。後方・前方互換性、expand-and-contract、境界でのwire契約に関する定理3。

バージョン重複が計測されないときスキーマ進化は壊れる

スキーマ変更は通常、ソフトウェアシステムにとって最も困難な変更タイプの一つであり、ByteByteGoの記事『Schema Evolution: Changing the Contract Without Breaking What Runs』は、なぜそうなのかという正直な理由で始まる:移行はstagingで綺麗に走り、無関係なサービスが本番で壊れ始め、移行自体には何も問題がなかった(ByteByteGo、『Schema Evolution: Changing the Contract Without Breaking What Runs』、2026年8月20日、https://blog.bytebytego.com/p/schema-evolution-changing-the-contract)。壊れたのは変更ではない。壊れたのは「スキーマバージョンは一つしか稼働していない」という前提だ。何年も前に書かれた行は、その後に置き換えられたコードによって読まれる。キュー内のメッセージは、現在のコンシューマが書かれる前に公開された。十八ヶ月前のモバイルアプリが実デバイスにインストールされたままで、まだAPIを呼んでいる。「スキーマ変更で破損なし」という特性は、後方・前方互換性にexpand-and-contractのシーケンシングを加えたメカニズムが実装され、二つのバージョンがまだ生きているかを計測しているとき正確に保証される。Theorem 3:特性はそのメカニズムが実装され計測されているとき正確に保証される。スキーマ変更は契約変更であり、バージョン重複は単一バージョン前提を致命的にするトポロジーだ。

主要な結論

  • スキーマ変更が本番を壊すのは移行が悪いからではなく、二つのアプリバージョンが同じデータベースに対してまだ走っている間に効力を生じるからであり、そのうち一つのバージョンだけが変更されたスキーマを参照していた(ByteByteGo、2026年8月20日)。
  • 常に複数のスキーマバージョンが稼働している:何年も前に書かれた行、現在のコンシューマより前にキューに入れられたメッセージ、十八ヶ月前のモバイルアプリがまだAPIを呼んでいる——あるバージョンで書かれたデータが別のバージョンで読まれる。
  • 「破損なし」は、後方・前方互換性がメカニズムとして実装され、バージョン重複が計測されるとき正確に成り立つ特性だ。互換性メカニズムのないスキーマ変更は、保証のない契約変更だ。
  • Theorem 3:特性はそのメカニズムが実装され計測されているとき正確に保証される。Expand-and-contractがメカニズムであり、最後の旧リーダーを追跡する非推奨タイムラインが計測だ。

常に複数のスキーマバージョンが稼働している

ByteByteGoの導入部は、すべてのスキーマ変更ポストモーテムが再発見する構造的事実を名指しする:移行はstagingで滑らかに走り、無関係なサービスが本番で壊れ、調査で移行自体には何も問題がないと分かる——二つのアプリバージョンが同じデータベースに対してまだ走っている間に効力を生じ、そのうち一つだけが変更されたスキーマを参照していた。それはstagingと本番のギャップではない。単一バージョン前提が複数バージョン現実に出会うのだ。Stagingは新コードに対して移行をテストする。本番は新コードと旧コードと旧コードが書いた旧データと旧パブリッシャが書いたキュー内メッセージに対して移行を走らせる。Staging環境は単一バージョントポロジーであり、本番はバージョン重複トポロジーだ。

[UNIQUE INSIGHT] バージョン重複はデプロイウィンドウに境界づけられない。ByteByteGoの導入部は明示的だ:何年も前に書かれた行は、その後に置き換えられたアプリコードによって生成され得る。キュー内のメッセージは、現在のコンシューマが書かれる前に公開された。十八ヶ月前のモバイルアプリが実デバイスにインストールされたままで、まだAPIを呼んでいる。デプロイウィンドウは最小の重複であり、耐久状態の重複——旧行、キュー内メッセージ、インストール済みモバイルクライアント——が実際にあなたを壊す重複だ。デプロイウィンドウしか考慮しないスキーマ変更計画は、最小のバージョン重複を計測し、最大の上で特性を断言している。

Honest ArchitectはByteByteGoのクレームをトポロジークレームとして読み、プロセスクレームとしては読まない。「常に複数のスキーマバージョンが同時に稼働している」は、分散システムの形についての声明だ:ライタとリーダは時間で脱結合されており、スキーマはその時間ギャップをまたぐ契約だ。契約への変更は、まだ生きているすべてのリーダをまたぐときだけ安全であり、データが書かれたときに生きていたリーダとデータが読まれるときに生きるリーダを含む。後方互換性と前方互換性は二つの異なるメカニズムであり、一つではない。

後方互換性と前方互換性は二つの異なるメカニズム

後方互換性は新コードが旧データを読む特性だ。前方互換性は旧コードが新データを読む特性だ。名前では対称に見え、メカニズムでは非対称だ。後方互換性は新コードの著者が欠落フィールドを許容することで保証できる特性であり、新コードは旧スキーマがどうだったか知っている。前方互換性は新コードの著者が単独では保証できない特性であり、旧コードは既にデプロイされていて変更できないからだ。唯一の保証は、旧コードが既に許容する形で変更を行うことだ(任意フィールドを追加する、旧コードが読むフィールドを削除しない、フィールド意味を変更しない)。後方互換性は書き込み時保証であり、前方互換性は変更自体に対する設計時制約だ。

ByteByteGoの記事は「どの変更がコンシューマを壊し、どれが壊さず、それを決める修飾子は何か」をカバーすると約束する——そして修飾子がメカニズムだ。任意フィールドの追加は後方互換(新コードはそのフィールドを欠く旧データを読む)かつ前方互換(旧コードは無視する余分なフィールドを持つ新データを読む)だ。フィールドのリネームはどちらでもない:旧コードは新データを読んで旧フィールド名を探し、見つからず、壊れる。旧コードが読むフィールドの削除は前方非互換だ。フィールドの型や意味の変更は、wireバイトが同じでも両方向に非互換であり、契約は意味であってバイトではないからだ。Honest Architectはリネームや型変更をスキーマ変更ではなく契約破壊として扱う——それは生きているリーダが立てている前提に違反する。

[PERSONAL EXPERIENCE] Everythinkのwire型はZodで、@everythink/typesで一度定義され、レスポンスはネットワーク境界で解析される。それは境界でのスキーマレジストリであり、単なる型定義ではない。Zodスキーマが契約であり、境界解析が計測だ——スキーマに一致しないペイロードは型付きApiErrorとして現れ、クラッシュしない。我々はそれをProduction ✅とタグ付けする。なぜならメカニズム(parse-at-boundary)が実装され、計測(型付きエラー)がすべてのレスポンスで走るからだ。バックエンドのスキーマ変更が対応するZodスキーマ変更なしに行われると、それはコンシューマ側計測のない契約変更だ——Zodスキーマが更新され境界解析がドリフトを捉えるまでPartial ⚠️。

Oracleの不変量——確率は正確に一箇所、everythink-oracle::ensembleで正規化され、コンシューマはsum(probability) ≈ 1.0に依存できる——は、ダウンストリームコードが読むスキーマ契約だ。正規化位置やソート順への変更は、wireバイトが同一に見えても契約破壊になる。なぜなら契約はコンシューマが依存する保証だからだ。我々はその不変量をProduction ✅とタグ付けする。メカニズム(単一正規化サイト)が実装され、計測(ensembleテスト)が走るからだ。その不変量のスキーマ進化規律は:旧コンシューマが旧保証を読み続け、新コンシューマが新しいものを読むexpand-and-contractシーケンスなしには、正規化サイトを移動させないことだ。

Expand and contract — トポロジームーブ

Expand and contractは、バージョン重複下で契約変更を安全にするメカニズムだ。Expand:後方かつ前方互換な形で新スキーマ要素を追加する。旧リーダに旧契約を読み続けさせ、新リーダに新契約を読み始めさせる。バージョン重複が排出するのを待つ——旧モバイルクライアントが更新し、旧キュー内メッセージが消費され、旧行が移行または経年する。Contract:生きているリーダがもう一つも参照しないとき、旧スキーマ要素を削除する。Expandフェーズはトポロジー拡張(二つのルートが共存)であり、Contractフェーズはトポロジー収縮(一つのルートが残る)だ。

Contractフェーズをゲートする計測は、最後の旧リーダを追跡する非推奨タイムラインだ。ByteByteGoは「バージョニング戦略と非推奨タイムライン」を約束する——そして非推奨タイムラインは、expand-and-contractを保証された特性にする計測であり、希望ではない。Honest Architectは、最後の旧リーダ計測のないexpand-and-contract計画をPartial ⚠️とタグ付けする:メカニズム(expand、待つ、contract)は実装されているが、contractをゲートする計測はない。最後の旧リーダを追跡する計画——クライアントバージョンテレメトリ、キュー内メッセージ年齢、行スキーマバージョンタグで——そしてそのカウントがゼロになったときだけ収縮するものはProduction ✅だ。

[ORIGINAL DATA] Everythinkの移行規律はこれを直接タグ付けする。すべての.up.sql移行には対応する.down.sqlがある——それはロールバックパスであり、失敗したexpandが取り消せる契約だ。.down.sqlはexpandフェーズのセーフティネットだ:expandが生きているリーダを壊せば、スキーマ変更を収縮し(down移行を実行し)、旧リーダが再開する。我々はロールバックパスをProduction ✅とタグ付けする。すべての移行がそれを持ち、マイグレータがペアリングを強制するからだ。一つのステップで追加し削除する移行はcutoverであり、expand-and-contractではない——旧コードがまだ生きていればPartial ⚠️、Contractフェーズがバージョン重複内で走るからだ。

World Monitorの地理キャッシュは、別の形で同じ規律を運ぶ。GeoSignalのidは決定的uuidv5(source, native_id)であり、再取り込みは更新し、重複しない。それはキャッシュの前方互換メカニズムだ:同じ信号の新取り込みは二行目を作る代わりに行を更新し、旧idを見たリーダと新idを見るリーダが同じ行を読む。id安定性が契約であり、決定的uuidv5がメカニズムであり、upsertが計測だ(再取り込みで行数は増えない)。我々はそれをProduction ✅とタグ付けする。メカニズムが実装され、行数安定性が観察可能だからだ。idスキームへのスキーマ変更は主キーのリネームであり——最も前方非互換な変更であり——旧idを削除する前に両方のidを書きリーダを移行するexpand-and-contractを必要とする。

ペイウォール導入部でHonest Architectが読むもの

ByteByteGoの記事は「Version Overlap」セクションヘッダの後ペイウォールにかかり、Honest Architectは本文を捏造しない。可視なのは構造的クレーム——常に複数のスキーマバージョンが稼働している、移行はstagingで綺麗に走り本番で壊れる、失敗は移行ではなくバージョン重複だ——であり、そのクレームはタグ付け規律を適用するのに十分だ。可視な導入部は:複数バージョン現実(Production ✅、分散システムの構造的事実として)、stagingと本番のギャップの単一バージョン対複数バージョントポロジー差(Production ✅、フレーミングとして)、そして後方/前方互換性、expand/contract、スキーマレジストリ、非推奨タイムラインのメカニズムとしての約束を与える。

Honest Architectの規則:実ソースを引用し、URLやメトリックを捏造せず、記事が言っていないことを言ったとクレームしない。導入部は、移行がstagingで綺麗に走り本番で壊れると述べる。二つのバージョンが同じデータベースに対して走っているからだ。それが引用されたクレームだ。残りの分析はEverythink自身のスタックに適用されたメカニズムだ——Zod境界、.up.sql/.down.sqlペアリング、Oracle不変量、World Monitor id安定性——我々自身の実装に対してタグ付けされ、ByteByteGoには帰属されない。クロスドメインクレーム(expand-and-contractはデータベーススキーマと予測ensembleで同じトポロジームーブだ)はPartial ⚠️だ。形は共有され、ドメインは分離している。

スコープ制限:Everythinkはcivil-and-defensive予測プラットフォームであり、データベースコンサルではない。スキーマ進化の教訓はクロスドメインだ——契約変更はバージョン重複が計測されるときだけ安全だ。token、wallet、community-creditの結果は約束されない。それらはRoadmap 🔵であり、Howeyレビューに服する。

よくある質問

移行がstagingで綺麗に走ったのにスキーマ変更が本番を壊すのはなぜ?

Stagingは新コードに対してだけ移行をテストし、本番は旧スキーマで書かれた旧コード、旧データ、キュー内メッセージ、インストール済みモバイルクライアントと一緒に新コードを走らせるからだ。移行は綺麗であり、バージョン重複はそうではない。壊れたのは変更ではなく、スキーマバージョンが一つだけ稼働しているという前提だ。Theorem 3:「破損なし」特性は、バージョン重複メカニズムが実装され計測されているときだけ保証される。

後方互換性と前方互換性の違いは何か?

後方互換性は新コードが旧データを読む特性であり、新コードの著者が欠落フィールドを許容することで保証する。前方互換性は旧コードが新データを読む特性であり、新コードの著者は旧コードを変更できないので、旧コードが既に許容する形で変更を行うことだけが保証だ。後方互換性は書き込み時保証であり、前方互換性は変更自体に対する設計時制約だ。

Expand and contractとは何か?

Expand:後方かつ前方互換な形で新スキーマ要素を追加する。新旧リーダに共存させる。バージョン重複が排出するのを待つ——旧クライアントが更新し、旧メッセージが消費され、旧行が経年する。Contract:生きているリーダがもう一つも参照しないとき、旧要素を削除する。Expandフェーズはトポロジー拡張(二つの契約バージョンが共存)であり、Contractフェーズはトポロジー収縮(一つのバージョンが残る)だ。

Everythinkはwire境界でスキーマ進化をどう強制するか?

wire型はZodで、@everythink/typesで一度定義される。レスポンスはネットワーク境界で解析され、スキーマに一致しないペイロードは型付きApiErrorとして現れ、クラッシュしない。Zodスキーマが契約であり、parse-at-boundaryが計測だ。対応するZodスキーマ変更のないバックエンドスキーマ変更は、コンシューマ側計測のない契約変更だ——Zodスキーマが更新されるまでPartial ⚠️。

Everythinkはスキーマ変更がコンシューマを決して壊さないと約束するか?

いいえ。我々が約束するのはメカニズムだ:Zod parse-at-boundary、.up.sql/.down.sqlペアリング、Oracle単一正規化サイト不変量、World Monitorの決定的uuidv5。「与えられた変更で破損なし」の特性は、変更がexpand-and-contractに従い、非推奨タイムラインが最後の旧リーダを追跡するとき成り立つ。バージョン重複内でcutoverする変更はPartial ⚠️だ。token、wallet、community-creditの結果は約束されない。それらはRoadmap 🔵だ。

ソース

あなたのチームがデプロイウィンドウではなくバージョン重複を計測する準備ができたら、networkを作成——トポロジーは二つの契約バージョンをルーティングし、境界は解析し、ロールバックパスは取り消す。

関連
ai · architecture · mechanism · theorem-3 · customization · mixture-of-experts · inkling · honest-architect

カスタマイズとはメカニズムの分離であり、オープンウェイトではない

Inkling がカスタマイズされるために設計されているのは、Apache 2.0 ライセンスのためではなく、各アーキテクチャ上の決定が測定可能な属性を独自のメカニズムの背後に隔離するからだ。バイアスベースの負荷均衡は Theorem 3 の最も純粋な実例であり、主目的と競合しないメカニズムによって保証される属性である。

data-enrichment · background-checks · coherence · honest-architect · mechanism

データエンリッチメントは体量ではなくコヒーレンスだ

より多くのデータが自動的により良いインサイトを意味するわけではない。定理3:特性(より良いインサイト)はメカニズム(データポイント間のコヒーレンス・チェック)から来る、体量からではない。価値はより良い質問であり、確実性ではない。

自らの主張を証明するエンジンの上に、あなたの世界を築く。

2016 年から稼働し続けるエンジンの上に、あなた自身のネットワークを作る——あるいは 21 本の論文を書いたチームに話しかける。