事例

エラーゼロの同期停止と、直しても戻るバグの正体

外部POSサービスとの部門データ同期が無言で1週間以上停止した事故を題材に、存在しない権限スコープとデプロイ経路の緩さという二重の落とし穴を解説します。

Papapapapa
OAuth連携POSシステムデプロイ運用CI/CD障害対応中小企業IT
エラーゼロの同期停止と、直しても戻るバグの正体

エラーが出ない障害という厄介

同期が、静かに死んでいた。

外部POSサービス「スマレジ」と連携している部門(カテゴリ)データの同期処理が、1週間以上まったく動いていませんでした。厄介だったのは、ログにも監視にも「エラー」らしい形跡がほとんど残っていなかったことです。処理は止まっていたのに、誰も気づける形で叫んでくれなかった。

一般的な障害は、例外が飛び、アラートが鳴り、担当者のスマホが震えます。ですが今回のケースは違いました。認可の入口で静かに弾かれ、そのまま「何も起きなかったこと」として処理され続けていたのです。動いているようで動いていない状態が、監視の網をすり抜けていました。

この「エラーが出ない障害」は、実は中小企業のシステム運用で最も見落とされやすいパターンです。件数が少ない連携、日次バッチのような目立たない処理ほど、こうした静かな停止に気づくのが遅れます。

存在しない権限を要求していた

原因を辿ると、驚くほど単純な話に行き着きました。スマレジとの認可リクエストに pos.categories:read という、そもそも存在しないOAuthスコープが含まれていたのです。

OAuth連携では、こちらが要求するスコープをサービス側が審査し、認められたものだけがアクセストークンに反映されます。しかし今回は要求スコープの中に実在しない項目が混じっていたため、認可プロセスそのものが正しく完了しない状態になっていました。エラーとして明示的に落ちるのではなく、連携設定や後続のトークン取得処理が「静かに」不整合を起こし、部門データの同期だけがそのまま止まっていたのです。

修正は、部門同期処理から存在しないスコープの要求を取り除くという内容で、6ファイル・67行の追加と7行の削除という比較的小さな変更でした。原因が特定できてしまえば、直すこと自体は難しくありません。問題は「なぜ気づくまでに1週間以上かかったか」の方にありました。

設定の初期値にも同じ地雷が埋まっていた

さらに掘ると、同じ誤りがもう一箇所に潜んでいました。連携設定のデフォルトスコープにも、同じ存在しない権限が初期値として埋め込まれていたのです。こちらは2ファイル・36行の追加という小さな修正でしたが、これがあることで話が厄介になります。

つまり、部門同期処理側だけを直しても、新規に連携設定を作ったり再設定したりするたびに、デフォルト値から同じ間違ったスコープが再び紛れ込む構造になっていたということです。バグの発生源が一つではなく、コードとデフォルト設定という二層に分かれて存在していました。片方だけ直して「解決した」と判断してしまうと、時間差で同じ障害が再発する典型的な構造です。

修正が巻き戻る本当の原因

ここまでの因果だけなら、よくあるバグ修正の話で終わります。しかし今回、本当に掘るべきだったのはもう一段深いところにありました。修正を入れたはずのコードが、なぜか本番環境で元の状態に戻ってしまう現象が起きていたのです。

調べてみると、開発の本流(メインブランチ)にきちんと取り込まれていない作業ブランチから、そのまま本番へデプロイできてしまう経路が存在していました。つまり、誰かが古い状態のブランチから何気なくデプロイを実行すると、本流に統合されたはずの修正がまるごと巻き戻ってしまう。直したはずのバグが息を吹き返す土壌が、デプロイの仕組み自体に組み込まれていたのです。

これはコードの品質の問題ではなく、リリースの「経路」の問題です。どれだけレビューを重ねてバグを直しても、本流を経由しないデプロイが可能な限り、修正は永久にいたちごっこになります。実際、この構造こそが「なぜ同じ障害が表舞台に残り続けたのか」の答えでした。

入口で止める、を仕組みにする

この根本原因への対処として、本流にマージされていないブランチからのデプロイを検知し止める仕組みを導入しました。5ファイルにわたる477行の追加という、単純なバグ修正とは規模の異なる変更です。個別のバグを直すのではなく、「未統合のコードは本番に届かせない」という入口そのものにガードを設けた形になります。

あわせて、OAuthスコープについてはコード側と設定のデフォルト値側の両方から実在しない項目を除去し、二層構造の再発源を塞ぎました。加えて、認可やトークン取得が失敗した際に無言で処理が止まるのではなく、明示的に検知できるようにする発想も重要です。エラーが出ない障害は監視の作り方次第で「出るようにできる」障害でもあります。

再発防止の考え方は三段構えです。まずコード側の誤りを取る。次に設定の初期値という見えにくい発生源を取る。そして最後に、直した修正がなぜ何度も巻き戻ったのかという経路そのものにガードをかける。この順序を踏まないと、表面的な修正だけを繰り返す不毛なサイクルに陥ります。

同じ事故を避けるためのチェックポイント

外部サービスとの連携やデプロイの仕組みは、一度組んだら長く手をつけない領域になりがちです。だからこそ、次のような観点で一度点検してみる価値があります。

  • 外部API連携で要求している権限(スコープ)が、サービス側の最新仕様と一致しているか。仕様変更や誤字によるズレは静かに進行します。
  • 連携設定のデフォルト値そのものに古い・誤った権限指定が残っていないか。コードだけでなく設定初期値も点検対象にする。
  • 障害が「例外を吐かず沈黙する」タイプでも検知できる監視になっているか。同期処理なら「最終成功時刻」の監視は特に有効です。
  • 開発の本流に統合されていないコードが、そのまま本番へデプロイできる経路が残っていないか。レビュー済みの修正が巻き戻る事故は、コードの問題ではなく経路の問題として扱う必要があります。

静かな障害と、緩いデプロイ経路。どちらも普段は目に入りませんが、両者が重なったときに「直したはずのバグが何度も生き返る」という厄介な事態を生みます。今回のように小さな設定ミスひとつでも、経路の緩さと組み合わさると1週間以上のサービス停止に発展し得る、という事実は、規模の大小を問わずすべての開発現場に当てはまる教訓だと考えています。

この記事をシェア

関連記事