ふくふくHukuhuku Inc.
EP.05Legacy Revival 13分公開: 2026-09-16

ストラングラーパターンで止めずに移行する ── 旧新並行運用の実装

止められない基幹システムを、動かしたまま1機能ずつ新へ移す。ルーティングでの段階切替、旧新の二重書き込みと整合性チェック、フィーチャーフラグでの即時ロールバック、そして「どこまで移せば旧を退役させてよいか」の判断まで、並行運用の実装を現場の順序で書く。

#レガシー刷新#移行戦略#ストラングラーパターン#並行運用#フィーチャーフラグ
執筆 / 監修
松尾 亮合同会社ふくふく 代表社員

データ基盤・データパイプライン構築 / BI / 生成 AI 活用支援を専門とするエンジニア (28 年)。 本記事は AI 利用ポリシーに基づき、生成 AI の補助で執筆 → 人間が監修・編集して公開しています。

プロフィール詳細
シェア

EP.4 までで、読めなかったを LLM で読み解き、EP.3 特性化テスト で現状の振る舞いを固定するところまで来た。ここからが本番だ。動いているものを、動かしたまま、別のものへ入れ替える。基幹系は止められない。請求も在庫も日々流れている。この記事では (ストラングラーパターン)を軸に、旧システムを稼働させたまま機能を1つずつ新システムへ移す実装を、現場でやる順番どおりに書く。

なぜ一括切替ではなく「絞め殺し」なのか

ストラングラーパターンの名前は、木に巻き付いて少しずつ宿主を覆い、最後に元の木が枯れても構造が残る絞め殺しの木に由来する。旧システムの周りに新システムを機能単位で被せ、1つずつトラフィックを移し、旧が誰にも使われなくなった時点で退役させる。一括切替(ビッグバン)は切替日に全リスクを集中させるが、絞め殺しはリスクを機能の粒度に小分けし、各ステップで「戻せる」状態を保つ。基幹系では、移行が長引く代償を払ってでも、業務停止の期待値を下げる方を選ぶ。移行全体は、おおむね次の4つの局面を機能ごとに繰り返す作業になる。

  • ①ルーティングでの段階切替 — プロキシ/ゲートウェイで、機能単位にトラフィックを新へ寄せる
  • ②二重書き込みと整合性チェック — 旧新の両方に書き、差分(ドリフト)を突き合わせて検出する
  • ③フィーチャーフラグと即時ロールバック — 切替をコード変更なしで戻せるようにする
  • ④退役判断 — どこまで移せば旧を消してよいか、ログで確証を取る

①ルーティングでの段階切替

最初に用意するのは、旧新の前段に立つルーティング層だ。リバースプロキシでもアプリ内ゲートウェイでもよい。ポイントは、切替の単位を URL パスやエンドポイントではなく業務機能で持つこと。「請求書発行」「在庫引当」といった機能ラベルでリクエストを分類し、そのラベルごとに新旧どちらへ流すかをフラグで決める。パスベースだと、1機能が複数エンドポイントに散らばっている旧システム( のストアドが画面裏で連鎖しているような構成)でうまく切れない。まず分類器を1枚かませて、そこから先の判断をフラグに委譲する形にしておく。

機能ラベル単位でルーティングを切り替える。既定は旧(安全側)に倒す。
Python
# ルーティング層: 機能単位で新旧どちらへ流すかを決める# 切替の単位はパスではなく「業務機能」にするのがコツ
def route(request):    feature = classify(request)          # 例: "invoice", "inventory"    if flags.enabled(feature, request.tenant_id):        return forward(NEW_BACKEND, request)   # 新システムへ    return forward(LEGACY_BACKEND, request)    # 旧システムへ(既定)
def classify(request):    # 仮実装: 実際は URL・操作種別・呼ばれるストアド名などから機能を推定    return FEATURE_TABLE.get(request.route_key, "unknown")
既定は必ず旧に倒す

分類器が unknown を返したとき、あるいはフラグ評価に失敗したときは、迷わず旧へ流す。新は「明示的に許可された機能・テナントだけ」通す設計にしておくと、想定外のリクエストが未完成の新経路へ漏れる事故を防げる。フェイルオープンではなくフェイルセーフ(旧へ)が並行運用の原則だ。

②旧新の二重書き込みと整合性チェック

読み取りの切替は比較的安全だが、書き込みは怖い。ここで使うのが shadow write(影書き込み)だ。移行初期は旧を真実の源(source of truth)とし、同じ書き込みを新にも「影」として流す。新側が失敗しても旧は止めない。そして旧新の結果を後から突き合わせ(reconciliation)、差分=ドリフトを検出する。これを何日も回し、ドリフトがゼロに収束して初めて、真実の源を新へ切り替える判断ができる。突き合わせの `normalize` が地味に重要で、丸め・タイムゾーン・NULL 表現・ 由来のゾーン10進などの表現差を吸収しないと、意味は同じなのに差分と誤検知して疲弊する。

影書き込み+突き合わせ。ドリフトが収束するまで真実の源は旧のまま。
Python
# shadow write: 旧を真実の源に保ちつつ、新へ影書き込みdef write_invoice(cmd):    legacy_result = legacy.write(cmd)          # 旧: 当面の真実の源    try:        new.write(cmd)                         # 新: 影書き込み    except Exception as e:        log.warn("shadow write failed", key=cmd.key, error=e)  # 旧は止めない    enqueue_reconciliation(cmd.key)            # 後で突き合わせる    return legacy_result                       # 応答は旧の結果を返す
# 突き合わせバッチ: 旧新を比較し、ドリフトを記録def reconcile(key):    a = legacy.read(key)    b = new.read(key)    if normalize(a) != normalize(b):           # 表現差を吸収した上で比較        record_drift(key, a, b)                # 差分を記録・アラート        return "DRIFT"    return "OK"
突き合わせは「一致率」ではなく「ゼロ件」を見る

99.9%一致、では退役できない。残り0.1%に、締め処理・返品・月跨ぎといった業務上いちばん壊れてはいけないケースが集まっていることが多いからだ。ドリフト件数がゼロで安定し、かつ発生したドリフトの原因がすべて説明できている状態を目指す。件数ではなく「未説明のドリフトがない」ことが基準になる。

③フィーチャーフラグと即時ロールバック設計

切替の引き金はコードのデプロイではなく、フラグの値であるべきだ。デプロイで切り替えると、問題発生時のロールバックに再デプロイの時間がかかり、その間ずっと業務が傷つく。フラグなら数秒で旧へ戻せる。粒度は機能×テナント(あるいは事業所・店舗)で持ち、まず社内テナントで、次に小規模テナントで、と段階的に広げる。各段階が下の表の「戻せるか」列で 戻せる である限り、前進は安全だ。逆に、この列が 戻しにくい になる手前が最大の注意点になる。

並行運用の段階。真実の源が新に移り、旧への影書き込みを止めた瞬間から不可逆性が上がる。
段階トラフィック真実の源戻せるか
shadow旧100%(新は影書き込みのみ)即戻せる
canary新へ一部の読み書き(社内→小規模テナント)戻せる
新主・旧影新が主、旧へ影書き込みを継続戻せる(条件付き)
旧退役新100%、旧への書き込み経路を停止戻しにくい

④どこまで移せば「完了」とみなすか ── 退役判断

退役は移行の最後のステップであって、切替直後ではない。判断材料は3つ。第一に、全トラフィックが新に寄っていること。第二に、一定期間(締め処理を最低1サイクル含む長さ)ドリフトが未説明ゼロで安定していること。第三に、旧の書き込み経路が実際に呼ばれていないことをログで確認できること ── 「使っていないはず」ではなく「呼ばれていないという記録がある」まで求める。この3点が揃って初めて、旧を止め、最終的に消す。この段階まで来ると、旧システムが抱えていたの返済がようやく確定する。逆に、ハードウェアやミドルウェアのに追われて確認を飛ばすと、後述の片道移行に陥る。

旧を消す前に「戻れる版」を1つ残す

退役の直前に、旧システムの読み取り専用スナップショット(データと固定ページの静的コピー)を1つ確保しておく。新で想定外の欠落が見つかったとき、旧の当時の値を参照できる退避路があるだけで、事故の深刻度が段違いに下がる。消すのはいつでもできるが、消してからでは取り戻せない。

落とし穴 ── 並行運用が壊れる3つの典型

  • 二重書き込みのデータ不整合 — 旧新の両方を「真実の源」にしてしまうと、両方が別々に更新され、突き合わせても勝者を決められない。真実の源は常に片方だけ。もう片方は影と割り切る。
  • 切替時のセッション/トランザクション跨ぎ — 1つの業務トランザクション(受注→引当→請求)の途中で新旧の境界を跨ぐと、片方だけコミットされて宙に浮く。切替の単位は「トランザクション境界」に合わせ、処理の途中で経路が変わらないようにする。
  • ロールバック不能な片道移行 — 新側でしか作れないデータ(新採番の ID、新形式の履歴)を旧が受け取れないと、真実の源を新にした瞬間に旧へ戻れなくなる。影書き込みは双方向で設計し、「新→旧」も書けるうちは戻せる状態を保つ。

これらの落とし穴を早期に見つける安全網が、EP.3 で作っただ。移行前の旧システムの振る舞いをテストで固定してあれば、新経路に流したリクエストが旧と同じ結果を返すかを機械的に照合できる。突き合わせバッチが本番データで差分を探すのに対し、特性化テストは既知の入力で回帰を止める ── 両輪で回すことで、「気づいたら壊れていた」を「切替前に落ちる」に変えられる。順序としては、特性化テストで振る舞いを固定 → 影書き込みで実データのドリフトを観測 → フラグで段階切替、が安全だ。


ここまでのまとめと、この先

本シリーズはここまでで、読めないコードを LLM で読み解くコード考古学の6ステップから始め、依存の可視化、特性化テストによる振る舞いの固定、そして本 EP の並行運用による移行までを扱った。ストラングラーパターンの実装は、派手な一括置換ではなく、ルーティング・影書き込み・フラグ・退役判断という地味な4局面を、機能の粒度でひたすら繰り返す作業だ。遅く見えて、止めないための最短路である。実際の基幹系移行の設計・伴走が必要ならレガシー刷新・資産化支援で相談を受けている。続編は、読者リアクション(もっと詳しく/続編希望)に応じて随時追加していく。移行のどの局面を掘り下げてほしいか、フッターのボタンで教えてほしい。

シェア

この記事の感想を教えてください

あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。

シリーズの外も探す:

まずは、現状を聞かせてください。

要件が固まっていなくて大丈夫です。現状診断と方針提案までを無料でお手伝いします。

無料相談フォームへ hello [at] hukuhuku [dot] co [dot] jp