Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
2年前に削除したPHPクラスが、 ある日突然決済をエラーにした
Search
ykagano
July 20, 2026
Technology
1.4k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
2年前に削除したPHPクラスが、 ある日突然決済をエラーにした
2026/7/20 PHPカンファレンス2026 発表資料
ykagano
July 20, 2026
More Decks by ykagano
See All by ykagano
人気商品が「ちゃんと買える」をつくる ー ECの負荷改善
ykagano
1
260
カートの信頼性を担保するWireMockを使ったe2eテスト
ykagano
0
900
決済システムの信頼性を支える技術と運用の実践
ykagano
1
1.4k
Copilot Agentを普段使いしてわかった、バックエンド開発で使えるTips
ykagano
1
2.4k
プレイングマネージャーになったときの話
ykagano
2
1.5k
WEBエンジニアが知っておきたい決済の仕組み
ykagano
3
3.9k
プロジェクトにおけるリーダブルコードの考え方
ykagano
2
2.2k
GWにスマートスピーカーアプリを作ってみた
ykagano
1
1.3k
開発効率を上げるSwaggerの話
ykagano
0
1.5k
Other Decks in Technology
See All in Technology
山手線を徒歩で一周してわかった、 位置情報アプリは「足」が最強のデバッガー
hinakko
0
180
Claude Codeを「使うほど育つ」AI秘書にするノウハウ
minorun365
PRO
33
30k
AIエージェントの一手は 誰も見ていない - Falco拡張OSS「Prempti」とeBPFで サーバーレス実行基盤を二層防御する
keitah
0
460
AIは推し活である。
kurazuuuuuu
1
940
beyond jj: config & tools ecosystem
indirect
0
5.7k
バイブコーディング時代のWebアプリ開発入門~Cloud Runで学ぶセキュアなビルドとデプロイ
waiwai2111
1
140
積み重なった技術負債への挑戦 〜初手としての全社ゴト化〜
techtekt
PRO
0
1.5k
Von-Neumann Machines, Dreams & The Futures of IT
ufried
0
120
Deployment の 先にある AI Agent 基盤 - kagent vNext、Agent Substrate、Hermes から読み解く Agent Runtime の現在地 / k8s-matsuri-2-ai-agent-platform-amsy810
masayaaoyama
4
700
ソフトウェアDNAとクラウドエージェントのススメ
cloudace
0
120
時うどん〜Socket.getifaddrsで学ぶネットワーク編 / Tokiudon: The Socket.getifaddrs Edition
coe401_
4
210
作って終わりじゃないサーバーレス 〜9年運用する大規模EC物流API基盤の設計・運用のリアル〜
zozotech
PRO
0
430
Featured
See All Featured
Music & Morning Musume
bryan
48
7.4k
So, you think you're a good person
axbom
PRO
2
2.2k
From π to Pie charts
rasagy
1
370
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
320
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
700
Getting science done with accelerated Python computing platforms
jacobtomlinson
2
480
The Curious Case for Waylosing
cassininazir
1
520
A Modern Web Designer's Workflow
chriscoyier
699
190k
The Cult of Friendly URLs
andyhume
79
7k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
380
Transcript
© 2012-2026 BASE, Inc. 2026/7/20 PHPカンファレンス2026 2年前に削除したPHPクラスが、 ある日突然決済をエラーにした 1 @ykagano
2 © 2012-2026 BASE, Inc. 氏名:加賀野 祐(ykagano) 所属:BASE株式会社 BASE Department Product
Division 役割:シニアエンジニア(バックエンド) 趣味:お酒、旅行、キャンプ 2024年8月にBASE株式会社に入社 その前は以下の開発を経験してきました - 2009年〜:NET CASH - 2012年〜:WebMoney - 2017年〜:Yahoo!ウォレット、PayPay ykagano 自己紹介
3 © 2012-2026 BASE, Inc. アジェンダ • ある日突然、決済がエラーに • なぜ2年後に突然爆発したのか
• どう修正したのか
© 2012-2023 BASE, Inc. ある日突然、決済がエラーに 4
5 © 2012-2026 BASE, Inc. ある日、Sentryが鳴った 普段見ないエラーだった Sentry調査用のBotから AIに調査してもらった
6 © 2012-2026 BASE, Inc. 購入失敗は2件だけだった BASEはECなので、購入失敗は少ない件数でもサービスの根幹に関わる 絶対にケアが必要 → インシデント扱い
同じ原因で何度もエラーが起こるかもしれないので、Slackのインシデントチャンネルを立てて共有 何が起きていたのか エラーはどこで起きていたのか DBに保存された 詳細データ をPHPのクラスに復元する処理 クラスの存在チェックに失敗して LogicException → 決済フローが停止
7 © 2012-2026 BASE, Inc. MukashinoDetail 犯人は「2年前に削除したクラス」 2024 昔のコードの一掃 クラス削除
2年間の沈黙 エラーなし ・ 誰も気づかない ! 2026 突然の発火 決済エラー LogicException 削除したとき問題は何もなかった • grepで参照ゼロを確認 ✓ • テストはすべて通過 ✓ • コードレビューも通過 ✓ • リリース後もエラーなし ✓ それでも「時限装置」は仕掛けられていた
© 2012-2023 BASE, Inc. なぜ2年後に突然爆発したのか なぜ2年後に突然爆発したのか 8
9 © 2012-2026 BASE, Inc. 決済が失敗した pending と同じ keyである failed
の行には、クラス名がシリアライズされたまま残っていた DBには「遺物」が眠っていた key status detail 753190E46D820159 2023年・昔の決済 failed {"class": "MukashinoDetail"...} ← クラスは削除済み・復元できない 753190E46D820159 2026年・新規の決済 pending {"class": "ShinkinoDetail"...} 同じキーなのに並存できる (key は非UNIQUE) DBの中に眠る「遺物」 コードを消してもデータは残る 新規の key は作成時に以下の仕様で作られていた • 完了した決済なら key は重複しない • 失敗した決済は key の重複を許容する ← 今回はこのパターン
10 © 2012-2026 BASE, Inc. これまで新規の key 作成時に、削除済みのクラスと同じ key を引き当てることはなかった
今回が初めての事象 単独では誰も悪くない実装が時間差で噛み合って「時限装置」になった 発火の瞬間に対象のkeyの検索が「壊れた行」まで一緒にロードしてしまった 2年越しのキー衝突 1 決済処理 keyで検索 2 同一 key の 全行をロード 3 クラスに 復元を試みる 4 クラス存在 チェック → 失敗 5 決済エラー ⚠ 削除済みクラスを参照 する行も混入 購入失敗
11 © 2012-2026 BASE, Inc. key の生成には str_shuffle() を使用している エラーが発生する8日前のリリースで、key
生成より前に mt_rand() を消費するコードが購入経路に入り、 乱数消費位置の分布が過去と重なった(テープの同じ位置から key を引き始めた) これにより衝突確率が跳ね上がったと想定される(不具合ではなく、DBでの key の重複は仕様通り) キーの衝突がなぜ突然起きたか 過去のリクエスト 83 17 52 96 41 08 75 29 63 34 90 12 57 … リリース前 83 17 52 96 41 08 75 29 63 34 90 12 57 … リリース後(今回) 83 17 52 96 41 08 75 29 63 34 90 12 57 … 事前に消費 key生成に使用(str_shuffle) key生成に使用 → 開始位置がズレていて衝突しない 新コードが消費(mt_rand) key生成に使用 str_shuffle() と mt_rand() は同じ seed(初期値) なら毎回同じ乱数列(=同じテープ)を返す テープの同じ位置から同じ並びを引く → 同じ key が生成される(衝突) 同じ seed
12 © 2012-2026 BASE, Inc. grepが探せるのは「コードの参照」だけ テストが検証するのも「コードの動き」だけ DBに残るクラス名入りのデータは誰も見ていなかった さらに発火には低確率のキー衝突が必要 →
だから2年間、沈黙していた コードは消したが、データは消えていなかった
© 2012-2023 BASE, Inc. どう修正したのか 13
14 © 2012-2026 BASE, Inc. status でDBにフィルタをかけるようにした 壊れた行をそもそもロードしない Before DBから全行を取得
PHP側で除外 ⚠壊れた行もロードされて爆発 $details = $this->detailMapper->find(key: $key); $unfailedDetails = array_values(array_filter($details, function (Detail $detail) { return $detail->getStatus()->includedIn(Status::unfailed()); })); After DBクエリの段階で failed を除外 必要な行だけ取得 ✓壊れた行はそもそもロードしない $details = $this->detailMapper->find( key: $key, statuses: Status::unfailed(), );
15 © 2012-2026 BASE, Inc. 今回の事象は、2年前に削除したクラスが、DBにシリアライズされて残っており、 新規の決済が同じ key を引き当てたことで、過去データの復元に失敗し、決済がエラーになったものです 「もう使われていない」をgrepだけで判断していませんか
コードを消してもDBのデータは残る クラス名をシリアライズして保存する設計は時限装置になりうる 削除するときは「コードの参照」だけでなく「データの参照」も確認しましょう 皆さんの現場でも、同じ時限装置が眠っているかもしれません まとめ
16 © 2012-2026 BASE, Inc. 以上です ご清聴ありがとうございました!! BASE社の採用情報: https://binc.jp/jobs