TT Lab
요금제

한국어

시작하기
블로그

블로그

숫자 하나가 배포를 멈춘 날: 268,729 의 729 와 정규식 단어 경계

상태: 해결 (2026-10-15 09:43, 주석 수정). 사실은 묶음 게이트의 실행 기록과 게이트 로그의 실패 문구, 커밋 기록에서 가져왔습니다. 정규식 비교 표는 파이썬으로 직접 돌린 값입니다. 정규식과 범인 찾기를 고치는 일은 아직 하지 않았습니다.

목차

한 줄 요약

영향 10-15 01:05 ~ 09:43, 묶음 게이트 26번 연속 실패. 같이 묶인 작업이 3개에서 19개까지 쌓여 아무것도 배포되지 않았다
원인 주석의 「268,729 바이트」 가 상표 검사 정규식 `\b(…
왜 길어졌나 실패한 시험이 바뀐 파일과 이름이 달라 범인 찾기가 「모름」을 냈고, 루프는 같은 묶음을 다시 돌렸다
고친 것 주석을 「약 262KiB」 로. 다음 바퀴에 19개가 함께 통과
남은 것 정규식 경계, 범인 찾기, 같은 실패가 되풀이될 때 멈추고 알리는 장치

1. 무슨 일이 있었나

카메라 점수판은 TT Lab 의 대표 화면입니다. 첫 화면이 그리지도 않는 녹화·편집·자동 점수 코드까지 먼저 받고 있어서, 덩어리로 나눠 늦게 부르게 바꿨습니다. 그 변경의 머리 주석에 이렇게 적었습니다.

//   정적 import 로 268,729 바이트를 받았다(Fast 3G 첫 그리기 4.7초). …

숫자를 정확히 적는 건 좋은 습관입니다. 문제는 이 저장소에 탁구 놀이터의 정적 파일에 특정 용품 상표와 내부 이름이 섞이지 않게 막는 시험이 있다는 것이었습니다. 상표 목록에는 탁구 러버로 유명한 숫자 이름 하나가 들어 있습니다. 네, 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 — 글자, 숫자, 밑줄)이고 다른 쪽이 단어 문자가 아닌 자리입니다.

\b 는 「단어 문자와 아닌 문자 사이」 2 6 8 , 7 2 9 공백 바 이 트 경계 경계 쉼표·공백은 \w 가 아니다 → \b729\b 가 맞는다 묶음 게이트 27번(10-15 01:05 → 09:43) 01:05 09:43 막대 = 묶음에 든 작업 수(3 → 19) 빨강 26번: 같은 시험, 같은 파일, 범인을 못 가림 초록 1번: 주석을 「약 262KiB」 로 고친 뒤 19개가 함께 통과
그림: 268,729 의 단어 경계와 묶음 게이트 27번의 시간선

천 단위 쉼표뿐 아니라 소수점(v1.729)이나 하이픈(729-2)도 마찬가지입니다. 숫자 상표를 \b 로 감싸면 숫자가 들어간 글은 언제든 걸릴 수 있습니다.

3. 왜 8시간 반이나 걸렸나: 범인을 못 가린 묶음 게이트

게이트(전체 시험)는 한 번에 약 20분 걸립니다. 그래서 작업마다 따로 돌리지 않고 여러 작업을 묶어 한 번에 시험하고, 통과하면 같이 올립니다. 떨어지면 「범인 찾기」로 실패의 원인이 된 작업만 빼고 다시 돌립니다.

범인 찾기는 이런 단서를 썼습니다.

  1. 떨어진 시험의 이름(test_tt_play)과 같은 이름의 파일(tests/test_tt_play.py, backend/app/tt_play.py)을 건드린 작업
  2. 실패 문구에 따옴표로 나온 .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. 무엇을 고쳐야 하나

정규식: 숫자 상표는 숫자에 붙은 자리를 뺀다

파이썬으로 두 정규식을 비교해 봤습니다.

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 를 잡습니다). 정규식 하나로 「상표로 쓴 729」 와 「숫자 속 729」 를 완벽히 가를 수는 없으니, 걸리는 쪽으로 두되 걸렸을 때 바로 알 수 있게 하는 것이 더 중요합니다. 이 변경은 아직 하지 않았습니다.

범인 찾기: 실패 문구 속 경로를 본다, 모르면 쪼갠다

루프: 같은 실패가 되풀이되면 멈추고 알린다

같은 시험이 같은 이유로 두 번 떨어졌다면 세 번째도 떨어집니다. 그때는 다시 돌리지 말고 멈춰서 사람에게 실패 문구를 보여 줘야 합니다. 26번의 재시도가 쓴 시간은 8시간 반이었고, 그동안 상관없는 작업 18개가 함께 묶여 기다렸습니다.

세 가지 모두 아직 하지 않았습니다. 이 글이 그 할 일 목록입니다.

5. 이 사고가 가르쳐 준 것

티티 한마디. 729 는 좋은 러버지만 숫자 속에 숨어 있으면 곤란해요! 정규식의 \b 는 쉼표를 벽으로 안 봐요. 그리고 똑같은 이유로 두 번 넘어졌다면, 세 번째로 달리기 전에 신발 끈부터 보세요.

참고

모든 자료는 2026-10-16 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다