「後から掃除するから大丈夫」が、入口の不具合を隠していた:商品が毎日 400 件そっくり作り直されていた
トレカダッシュ 開発の記録 / 2026年8月
iOS アプリ「トレカダッシュ」の商品のデータには、毎日夜に、重複した商品をまとめる掃除の処理があります。「後から掃除するから、大丈夫」。そう思っていた場所に、毎日、そっくりな商品が作り直されている入口の不具合が、隠れていました。
2026 年 8 月 27 日の総点検で、「この日だけで商品が 437 件、作られていて、そのうち 410 件は、記事が 1 件も付いていない」と分かりました。
数字は、2026 年 8 月 27 日の作業の記録から書いています。
原因:「名前の完全一致」でしか、探していなかった
公式のカタログから、商品を登録する処理は、「同じ商品が、すでにあるか」を、名前の完全一致だけで判定していました。
既存「拡張パック「古代の咆哮」」 ↔ カタログ「拡張パック 古代の咆哮」
既存「Vスタートデッキ草(全角空白)フシギバナ」 ↔ カタログ「Vスタートデッキ草 フシギバナ」
括弧、空白、全角と半角の違いだけで、「まだ無い」と判定され、毎日、そっくりな商品が作り直されていました。
作られた重複は、後から、週 1 回と毎日の統合の処理が片付けていました。だから、気づけませんでした。ただ、その間は、利用者に同じ商品が 2 つ見え、記事の紐付け先も割れます。
直し方は、統合の処理が使っているのと同じ正規化(括弧、空白、全角と半角を吸収する)で比べることでした。カタログ側にも、表記ゆれの重複があるので、同じ実行の中では、同じキーを 1 つに絞ります。
教訓は、「後から掃除するジョブがある」仕組みは、入口の不具合を隠してしまうことです。掃除の件数が、毎回同じくらい出ているなら、片付いているのではなく、毎日、作られ続けています。
もう 1 つ:6 つの商品が、1 つに吸い込まれていた
マジック:ザ・ギャザリングの「FINAL FANTASY」の統率者デッキ 6 種(サイオンズ・スペル、リミットブレイク、カウンター・ブリッツ、トランス・リアニメイト、ほか)が、すべて 1 つの商品に吸い込まれていました。
- 商品の別表記に、ほかの弾の名前が入る。
- それが、AI の候補に出る。
- 次の誤った照合を呼ぶ。
という、自己増殖の形でした。別表記を掃除するだけでは、直りませんでした。AI が、同じ商品を選び続けるからです。そこで、括弧の中の固有名が違う商品を、照合の候補から外す判定(namesDifferentQuotedVariant)を、候補のフィルタにも入れました。候補から消えると、AI は「新しい商品」と答えられるので、正しい商品が作られます。
誤検知を潰すために、括弧の中が、総称、地名、店名、数量、状態のときは、固有名として扱いません。実データで確かめると、この条件を入れる前は、12 件のうち 6 件が誤検知でした。
また、採用しなかった案もあります。「商品名の固有名が、別表記に含まれないなら、消す」という強い規則も試しました。43 件のうち 6 件が誤検知(型番が同じ別の弾、「+(プラス)」の付く名前など)だったので、見送りました。
自分の修復で、先着に締切を付けてしまった
同じ告知のページの記事から、日付を引き継ぐ修復の処理(inheritDatesFromSameAnnouncement)を流しました。すると、先着(無くなり次第終了)の記事に、同じページの抽選記事の締切を、渡してしまいました。先着には、締切という概念がありません。利用者に、「その日まで買える」という嘘の期限を見せることになります。
引き継ぐ側の 2 か所(保存のときの処理と、修復の処理)の両方で、先着には、締切を渡さないようにしました。付けてしまった 1 件は、消しました。
教訓は、修復の処理を流したら、流す前と流した後で、監査を比べることです。「直った件数」だけを見ていると、自分が増やした異常に、気づけません。
分かったこと
後から掃除する仕組みは、入口の不具合を隠す。
- 掃除の件数が毎回同じなら、毎日作られている。片付いているのではありません。
- 入口と掃除で、同じ正規化を使う。入口が厳しいと、掃除が肩代わりします。
- 別表記の掃除だけでは、自己増殖は止まらない。AI の候補から外す必要があります。
- 強い規則は、誤検知を数えてから。43 件で 6 件の誤検知なら、見送ります。
- 修復の前後で、監査を比べる。自分が増やした異常を見つけるためです。
この話に出てくるもの
- トレカダッシュ — トレカの抽選・予約・先着の情報を集める iPhone アプリ
- 「重複は実質ゼロ」と数えたのに、監査は 112 組を出していた — 重複統合の別の失敗
- 公式の一覧から、「作品名だけの商品」が 393 件できていた — 同じカタログ投入の別の失敗
- 一度だけ生まれた誤字が、店ごとに増えていった — 自己増殖の別の例
ほかの記録
- Cloud Functions から月 4,300 円のサーバーへ移した:1 日 3,700 円の課金、EEA で全滅した Yahoo!、移行で踏んだ 5 つの罠 — 無料のお試しが終わると 1 日約 3,700 円(月約 10.6 万円)かかった。待ち時間にも課金される構造が原因で、コードをほぼ書き換えずにシンガポールの VPS(月約 4,300 円)へ移した。ヘルシンキでは Yahoo! が全滅、秘密の値は 5 本でなく 15 本だった。
- 広告が出ないと、実機チェックの 25 項目が止まった:開発者モードだけテスト広告を使い、賞品名を直書きしない — 配信前は広告の在庫が無く、実機チェックリスト 163 項目のうち 25 項目が確認できなかった。開発者モードのときだけ、Google のテスト用の広告枠に切り替えた。賞品名は 1 つの設定から読み、チェックリストにも直書きしない。
- 「これベイブレードじゃん」が、関門を入れても戻ってきた:弾いた値が、別の形で漏れた — 「商品名に玩具の名前があれば作らない」関門を入れても、AI が返す商品名から玩具の名前が消えて、すり抜けた。判定を告知の本文に移し、「カードゲームの語が 1 つも無いときだけ」弾く条件にした。
コメント
感想や、記事の間違いの指摘は、ここへ。書くには、GitHub のアカウントが要ります。
押すと、giscus(giscus.app)と GitHub に接続します。押さなければ、接続しません。詳しくはあつかいに書いています。