ステータス: 解決済み (2026-10-15 09:43、コメント修正)。 事実はバッチゲートの実行履歴とゲートログの失敗メッセージ、コミット履歴から取得しました。 正規表現の比較表はPythonで直接実行した値です。正規表現と犯人特定の修正は まだ行っていません.
目次
- 一言まとめ
- 1. 何があったのか
- 2. なぜ「268,729」が「729」なのか: 単語境界
- 3. なぜ8時間半もかかったのか: 犯人を特定できなかったバッチゲート
- 4. 何を修正すべきか
- 5. このインシデントが教えてくれたこと
- 参考
一言まとめ
| 影響 | 10-15 01:05 ~ 09:43、バッチゲートが26回連続で失敗。一緒にまとめられた作業が3個から19個まで積み重なり、何もデプロイされなかった |
| 原因 | コメントの「268,729バイト」が商標検査の正規表現 `\b(… |
| なぜ長引いたのか | 失敗したテストが変更されたファイルと名前が異なり、犯人特定が「不明」を返し、ループが同じバッチを再実行した |
| 修正したこと | コメントを「約262KiB」に変更。次の周回で19個が一緒に通過 |
| 残った課題 | 正規表現の境界、犯人特定、同じ失敗が繰り返されたときに停止して通知する仕組み |
1. 何があったのか
カメラのスコアボードはTT Labの代表的な画面です。最初の画面が表示もしない録画・編集・自動スコアのコードまで先に読み込んでいたため、塊に分けて 遅延ロードするように変更しました。その変更の先頭のコメントに次のように書きました。
// 정적 import 로 268,729 바이트를 받았다(Fast 3G 첫 그리기 4.7초). …
数字を正確に書くのは良い習慣です。問題はこのリポジトリに 卓球遊び場の静的ファイルに特定の用品商標と内部名が混ざらないように
ブロックするテストが存在していたことでした。商標リストには卓球ラバーで有名な数字の名前が1つ含まれています。そう、 729 です。
BRANDS = re.compile(r"\b(butterfly|tenergy|…|sanwei|729)\b", re.I)
ゲートログの失敗メッセージは正直でした。
AssertionError: <re.Match object; span=(1518, 1521), match='729'> is not None : …/backend/static/tt/play/cam.js
2. なぜ「268,729」が「729」なのか: 単語境界
\b は文字ではなく 位置にマッチします。片側が単語文字(\w —文字、数字、アンダースコア)であり、もう片側が単語文字ではない位置です。
,は単語文字ではありません。そのため,と7の間は境界です。9後ろのスペースも単語文字ではありません。そのため9とスペースの間も境界です。- 結果: 人間の目には「26万8千7百29」という数字が、正規表現の目には「268」と「729」という2つの単語に映ります。
千単位のカンマだけでなく小数点(v1.729)やハイフン(729-2)も同様です。数字の商標を \b で囲むと、数字が入った文章はいつでも
ひっかかる可能性があります。
3. なぜ8時間半もかかったのか: 犯人を特定できなかったバッチゲート
ゲート(全体テスト)は1回あたり約20分かかります。そのため作業ごとに分けて実行せず、複数の作業をまとめて一度にテストし、通過すれば一緒に アップロードします。失敗した場合は「犯人特定」機能で失敗の原因となった作業だけを除外して再実行します。
犯人特定には次のような手がかりが使用されていました。
- 失敗したテストの名前(
test_tt_play)と同じ名前のファイル(tests/test_tt_play.py,backend/app/tt_play.py)に手を加えた作業 - 失敗メッセージに引用符で示された
.pyファイルに手を加えた作業
今回はどちらも外れました。失敗したテストファイルを修正した人はおらず、失敗メッセージに出現したファイルは .py ではなく cam.js でした。
失敗メッセージに cam.js の全体パスが 堂々と存在していたにもかかわらず 、犯人探しはそれを見ていませんでした。結果は「ゲート失敗、犯人を特定できませんでした」.
そして、外側のループは数分後に と同じまとまりを再度 実行しました。新しく入ってきた作業も同じまとまりに乗っかりました。
| 時刻(10-15) | まとまりに含まれる作業 | 結果 |
|---|---|---|
| 01:05 ~ 01:46 | 3 | 失敗 3回 |
| 02:06 ~ 02:25 | 8 | 失敗 2回 |
| 02:45 ~ 08:21 | 9 | 失敗 18回 |
| 08:42 | 18 | 失敗 |
| 09:03 ~ 09:23 | 19 | 失敗 2回 |
| 09:43 | 19 | 通過 — 19個が一緒にアップロード |
26回すべて同じテスト、同じファイル、同じ match='729' でした(残っているゲートログ26個をすべて確認)。コメントを「静的 import で約 262KiB を取得した」に変更した作業ファイルが修正された時刻は 09:42、次の周回が 09:43 に開始して通過しました(268,729 ÷ 1,024 ≈ 262.4)。
4. 何を修正すべきか
正規表現: 数字の商標は数字に付いた桁を除外する
Pythonで2つの正規表現を比較してみました。
old = re.compile(r"\b(729)\b")
new = re.compile(r"(?<![\d.,])729(?![\d.,])") # 앞뒤가 숫자·점·쉼표면 상표가 아니다
| テキスト | \b729\b |
数字の隣接を除外 |
|---|---|---|
268,729 바이트 |
ヒット | - |
약 262KiB |
- | - |
Friendship 729 러버 |
ヒット | ヒット |
729-2 러버 |
ヒット | ヒット |
v1.729 |
ヒット | - |
0x729 |
- | ヒット |
新しい式も完璧ではありません(0x729 を捉えます)。正規表現1つで「商標として使った 729」と「数字の中の 729」を完全に切り分けることはできないため、ヒットする側に倒しつつ ヒットした時にすぐ分かるように することがより重要です。この変更はまだ行っていません。
犯人探し: 失敗メッセージの中のパスを見る、分からなければ分割する
- 失敗メッセージに現れた すべての ファイルパス(拡張子に関わらず)を、作業が触れたファイルと突き合わせる。今回はこれだけですぐに見つけられたはずです。
- それでも分からなければ、まとまりを半分に分けて通過したテストだけを実行する(二分探索)。テスト1つは数秒で終わります。
ループ: 同じ失敗が繰り返されたら停止して通知する
同じテストが同じ理由で2回失敗した場合、3回目も失敗します。その時は再実行せずに停止し、人に失敗メッセージを見せなければなりません。26回の再試行が費やした時間は8時間半であり、その間に関係のない作業18個が一緒にまとめられて待機していました。
3つともまだ行っていません。この記事がそのToDoリストです。
5. このインシデントが教えてくれたこと
- 保護するテストが間違った理由で失敗することがあります。 商標チェックは自身の仕事を行いました。ただ、「729」という商標と「268,729」という数字を区別する方法を知らなかっただけです。
- 「不明」は結果ではなくアラートでなければなりません。 犯人を特定できなかったということは、「もう一度やってみよう」ではなく「人間が確認しなければならない」を意味します。
- まとまりは共に勝ち、共に負けます。 バッチゲートは時間を節約してくれますが、犯人を特定できない瞬間、まとまり全体が人質になります。
Titiの一言。 729 は良いラバーですが、数字の中に隠れていると困ります!正規表現の
\bはカンマを境界としてみなしません。そして同じ理由で2回転倒したなら、3回目に走る前に靴紐から確認してください。
参考
すべての資料は 2026-10-16 に閲覧しました。
- Python
reドキュメント(\b, 前後表示(?<!…)·(?!…)): https://docs.python.org/3/library/re.html