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
890
決済システムの信頼性を支える技術と運用の実践
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
AIによるクリエイティブ生成を行う上での試行錯誤
plaidtech
PRO
0
160
おい、エージェントを使って終わらせろ
nwiizo
2
740
Issue 駆動でスペシャリストの意図を届ける、AI 実装のアクセシビリティ向上
thkt
0
120
エージェントはローカル、検証はMicroVM — Lambda MicroVMsでつくるServerless CI
fujioka6789
3
470
Goodbye ShellScript, Hello File-based App
shunsock
0
380
JSONataとAWS Step Functionsで目指すRuntimelessな世界
mu7889yoon
0
280
人間はどの意思決定を手放せるのか
kawasima
15
7.5k
映像変換サーバーなしで端末内でHLSを生成してライブ配信
hikarusato
0
130
Cloudflare Workers 向けアプリを C# で構築する ~WASM Native AOT への道~
nenonaninu
1
530
その Lambda、8分で 管理者権限まで奪われます
k1nakayama
6
3.2k
AI 時代のスタートアップエコシステ厶から考究する技術的負債との向き合い方
m3m0r7
PRO
3
2.3k
あけおめLINE 傾向とその対策
nasa9084
0
180
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
76k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
Crafting Experiences
bethany
1
340
Leadership Guide Workshop - DevTernity 2021
reverentgeek
1
370
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
600
Money Talks: Using Revenue to Get Sh*t Done
nikkihalliwell
0
500
Chasing Engaging Ingredients in Design
codingconduct
0
320
We Have a Design System, Now What?
morganepeng
55
8.3k
Ethics towards AI in product and experience design
skipperchong
2
380
Faster Mobile Websites
deanohume
310
32k
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