Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
2年前に削除したPHPクラスが、 ある日突然決済をエラーにした
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
ykagano
July 20, 2026
Technology
1k
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
カートの信頼性を担保するWireMockを使ったe2eテスト
ykagano
0
48
決済システムの信頼性を支える技術と運用の実践
ykagano
0
1.3k
Copilot Agentを普段使いしてわかった、バックエンド開発で使えるTips
ykagano
1
2.4k
プレイングマネージャーになったときの話
ykagano
2
1.5k
WEBエンジニアが知っておきたい決済の仕組み
ykagano
2
3.9k
プロジェクトにおけるリーダブルコードの考え方
ykagano
2
2.2k
GWにスマートスピーカーアプリを作ってみた
ykagano
1
1.3k
開発効率を上げるSwaggerの話
ykagano
0
1.5k
Other Decks in Technology
See All in Technology
JavaScript 研修 (2026)
recruitengineers
PRO
1
430
【CEDEC2026】ゲームシナリオライターを支援するAIツール開発の実践 ― 設計とプロンプトの工夫 ―
cygames
PRO
1
770
【CEDEC2026】『ウマ娘 プリティーダービー』 英語版のキャラクターの方言や口調をローカライズするための創造的アプローチ
cygames
PRO
1
200
【CEDEC2026】次世代デジタルカードゲームのサーバー設計と運用 〜『Shadowverse: Worlds Beyond』の舞台裏~
cygames
PRO
1
940
SmartHR Engineering Team Deck
smarthr
0
210
侵入は突然に 〜 IoTマルウェアと悪用される家庭の機器 ~ / When Intrusion Strikes: IoT Malware and the Abuse of Home Devices
nttcom
0
1.5k
SnowflakeCoCoでデータエンジニアリング!
foursue
0
180
【CEDEC2026】『GRANBLUE FANTASY: Relink - Endless Ragnarok』のバトル制作事例 ~最高のキャラゲーを目指して~
cygames
PRO
0
210
Data Hubグループ 紹介資料
sansan33
PRO
0
3.1k
グローバル基準のSREは、運用現場でどう機能したか:成熟度アセスメントの実践 / SRE NEXT 2026
sorawatanabe
0
120
Issue設計から始める仕様駆動開発 / 20260731 Mizuki Hirata
shift_evolve
PRO
1
140
Retriever と Reranker、結局どうする?
kazuaki
3
690
Featured
See All Featured
What does AI have to do with Human Rights?
axbom
PRO
1
2.3k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
450
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.3k
Game over? The fight for quality and originality in the time of robots
wayneb77
1
240
How to train your dragon (web standard)
notwaldorf
97
6.7k
Ruling the World: When Life Gets Gamed
codingconduct
0
290
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
830
Data-driven link building: lessons from a $708K investment (BrightonSEO talk)
szymonslowik
1
1.2k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
260
My Coaching Mixtape
mlcsv
0
200
The Organizational Zoo: Understanding Human Behavior Agility Through Metaphoric Constructive Conversations (based on the works of Arthur Shelley, Ph.D)
kimpetersen
PRO
0
400
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