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.5k
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
購入ドメインでの課題と取り組み
ykagano
0
310
人気商品が「ちゃんと買える」をつくる ー ECの負荷改善
ykagano
1
290
カートの信頼性を担保するWireMockを使ったe2eテスト
ykagano
0
950
決済システムの信頼性を支える技術と運用の実践
ykagano
1
1.4k
Copilot Agentを普段使いしてわかった、バックエンド開発で使えるTips
ykagano
1
2.4k
プレイングマネージャーになったときの話
ykagano
2
1.5k
WEBエンジニアが知っておきたい決済の仕組み
ykagano
3
4k
プロジェクトにおけるリーダブルコードの考え方
ykagano
2
2.2k
GWにスマートスピーカーアプリを作ってみた
ykagano
1
1.3k
Other Decks in Technology
See All in Technology
パッチ更新でterraform initが落ちた日
grimoh
1
120
宣言型部分更新は何を解決しどこへ向かうのか
shuta13
0
300
Essential architectural thinking (revised edition)
ufried
0
110
オブザーバビリティを高める AI エージェント体験を考える / Designing AI Agent Experiences That Enhance Observability
aoto
PRO
2
330
LocalStack を使ったサーバーレスアプリケーション開発 / Serverless Development with LocalStack
kakakakakku
1
370
Snapshot Testing in Practice: Predictable and Reliable SwiftUI Views
fespinoza
0
180
IoTデバイスを繋げるプロトコルMatterをAndroidで使用する方法
harutiro
0
140
1万名の社員が使う認証基盤で どう信頼性を担保するか?
kairim0
0
230
ビジネスを止めない技術的負債の返済のための戦略とその手法 - 技術的負債と向き合う / Complexity and Simplicity
soudai
PRO
3
680
AI時代に必要な脅威モデリング― 変化の速度に負けない理解とリスク判断
masakane55
5
1.2k
しろおびから始める!ログ活用の第一歩
shumei_ito
0
870
脆弱性診断って、何をしているの? 〜弱点を見つけて、会社の改善につなげるまで〜
keeg
0
830
Featured
See All Featured
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
310
How to Ace a Technical Interview
jacobian
280
24k
Are puppies a ranking factor?
jonoalderson
2
4k
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
350
Fireside Chat
paigeccino
43
4.1k
Joys of Absence: A Defence of Solitary Play
codingconduct
1
540
The browser strikes back
jonoalderson
0
1.8k
Balancing Empowerment & Direction
lara
6
1.3k
The Limits of Empathy - UXLibs8
cassininazir
2
710
Discover your Explorer Soul
emna__ayadi
2
1.3k
HDC tutorial
michielstock
2
950
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
580
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