포스트

2026-08-30-TIL: 새 저장소 다섯 개로 시작한 한 주와 동시 정답 제출

이번 주에는 새 저장소를 한꺼번에 열었다. CTF 운영 플랫폼 flag-grid, 개발자 Q&A 플랫폼 quno, 시스템 설계 훈련 플랫폼 sys-drill, 옛 게임을 Unity로 다시 만드는 neon-diary, 그 원본 데이터를 분석하는 tokyo-yahwa다. flag-grid, neon-diary, tokyo-yahwa는 Codex로, quno와 sys-drill은 Claude Code로 진행했다. 저장소마다 AGENTS.md나 CLAUDE.md부터 두고, 기획 문서를 작업 계획서로 옮긴 다음 구현에 들어갔다. 커밋이 많아서 굵은 줄기만 남긴다.

이번 주 한 작업

flag-grid: CTF 백엔드와 FBCTF를 참고한 화면

백엔드 골격은 빠르게 나왔다. 대회, 팀, 제출, 점수, 리더보드, 힌트와 감점, 운영자 감사 로그 같은 기본 기능이 먼저 들어갔다. 점수는 추가만 하는 원장을 사실로 두고, 팀 점수는 그 원장을 모은 조회용 집계로 다루기로 했다. 실시간 갱신은 SSE로 내보내고, 인스턴스가 여럿일 때만 Redis로 이벤트를 퍼뜨리게 했다. Redis가 없으면 메모리 브로커로 내려간다.

화면은 처음에 폼 위주의 관리 화면이었는데, FBCTF를 1차 레퍼런스로 삼기로 정했다. 원본을 복제하지 않고 지금의 도메인 모델과 API에 맞춰 독립 구현한다는 조건도 같이 적었다. 주 후반은 대부분 이 포팅 작업이었다.

neon-diary와 tokyo-yahwa: 분석과 구현을 나눈 구조

두 저장소는 역할을 처음부터 나눴다. tokyo-yahwa는 원본 게임 파일을 분석해 파일 형식 문서와 리소스 목록, 구현 쪽에 넘길 핸드오프 문서를 만드는 상류 저장소다. 원본 파일은 Git에 넣지 않고 분석 도구와 결과물만 추적한다.

neon-diary는 이 결과를 받아 Unity 6 위에서 구현하는 쪽이다. 핸드오프 문서를 읽는 순서를 프롬프트 파일로 남겼다. 상류 결과를 먼저 믿고, 구현하다가 모순이 보일 때만 원본을 다시 연다는 규칙이다. 주 후반에는 세이브 후 복원이 제대로 되는지 왕복해 보는 검증 경로를 넣었다.

quno와 sys-drill: 계획서를 따라가는 진행

두 저장소는 계획서의 단계를 하나 끝낼 때마다 체크리스트를 갱신하는 방식으로 진행했다. quno는 질문 수정 이력, 답변 채택, 알림, PostgreSQL 전문 검색, 투표와 댓글까지 왔다. sys-drill은 장애 시나리오 훈련에 규칙 기반 평가와 Claude 평가를 섞었고, Toxiproxy로 실제 네트워크 지연을 주입하는 단계까지 끝냈다.

같은 팀의 정답 두 개가 동시에 들어오면

flag-grid에서 가장 기억에 남는 수정은 같은 팀이 같은 문제의 정답을 거의 동시에 두 번 제출하는 경우였다. 제출 처리 흐름은 이랬다.

  1. 이미 푼 문제인지 조회한다.
  2. 아니면 제출 기록을 저장한다.
  3. 정답이면 풀이 기록을 넣는다.

스키마에는 (competition_id, team_id, challenge_id) 유니크 제약이 있어서 점수가 두 번 들어갈 일은 없었다. 문제는 응답이었다. 두 요청이 동시에 1번을 통과하면 둘 다 “아직 안 풀었다”고 판단하고, 늦게 INSERT한 쪽이 제약에 걸린다. 이 DataIntegrityViolationException을 아무도 잡지 않아서, 사용자에게는 평범한 중복 제출이 500 서버 오류로 보였다.

고친 방식은 단순하다. 풀이 기록 INSERT를 saveAndFlush로 바꾸고 try로 감싸서, 제약 위반을 “이미 풀었음” 409로 바꿨다. saveAndFlush로 써 두면 ID 생성 전략이 바뀌어도 예외가 커밋 시점까지 밀리지 않고 try 안에서 난다.

핵심은 앞의 존재 확인이 동시성 방어가 아니라는 점이다. 확인과 INSERT 사이에는 틈이 있고, 그 틈은 애플리케이션 코드로 닫히지 않는다. 최종 판정은 DB 제약이 하고, 애플리케이션은 제약 위반을 도메인 오류로 번역하는 역할만 맡는다. 앞의 확인은 대부분의 중복을 싸게 걸러내는 최적화로 남겨 둔다. 테스트는 CountDownLatch로 두 스레드를 동시에 출발시키고, 응답이 200 한 건과 409 한 건인지, 점수가 한 번만 반영됐는지를 본다.

느낀 점

주 초반에 다섯 저장소의 주요 결정을 아키텍처 결정 기록으로 몰아서 적었다. 에이전트와 빠르게 진행하면 결정이 코드 속에 묻히기 쉬운데, 한 번 꺼내 적어 두니 이후 작업에서 “그 결정에 따라” 진행하는 흐름이 생겼다.

neon-diary와 tokyo-yahwa를 나눈 것도 같은 맥락이다. 분석과 구현을 한 저장소에 섞으면 에이전트가 매번 원본을 다시 파헤친다. 상류 저장소의 핸드오프 문서를 계약처럼 두니 구현 쪽 세션은 그 문서만 읽고 시작할 수 있었다.

동시 제출 건은 기능이 다 돌아가는 것처럼 보여도, 동시에 두 번 누르는 경우는 따로 시험해야 드러난다는 걸 다시 확인한 일이었다. 단건 테스트로는 이 500이 나오지 않는다.

Gemini 3.7 Flash

이번 달 Google이 Gemini 3.7 Flash를 내놨다. 3.6 Flash가 나온 지 몇 주 만이다. Google은 코딩과 에이전트 작업에 쓰는 “워크호스” 모델 중 가장 똑똑하다고 소개했고, 소프트웨어 엔지니어링 벤치마크에서 이전 버전보다 크게 올랐다는 수치를 같이 실었다. 어디까지나 회사가 고른 벤치마크라 실제 체감은 써 봐야 안다.

눈에 띈 건 가격 정책이다. 연말까지는 입문 가격으로 입력 100만 토큰당 0.75달러, 출력 3.75달러를 받고, 내년부터 두 배로 올린다고 미리 밝혔다. 싸게 써 보게 해서 에이전트 파이프라인에 먼저 자리 잡게 하려는 전략으로 읽힌다.

이번 주처럼 저장소를 여러 개 동시에 굴리면 비싼 모델 하나로 모든 일을 시키기 부담스럽다. 계획과 설계 판단은 큰 모델에 맡기고, 반복되는 구현과 테스트 수정은 이런 Flash급 모델로 돌리는 식으로 나눠 볼 만하다. 다만 입문 가격이 끝나는 시점에 비용이 두 배가 된다는 건 미리 계산에 넣어 둬야 한다.

참고:

다음에 확인할 것

  • 중복 정답으로 409가 나면 같은 트랜잭션의 제출 기록도 함께 롤백된다. 감사 관점에서 이 시도를 남겨야 하는지
  • neon-diary의 세이브·복원 검증 경로를 사람 손 없이 자동으로 돌리는 방법
  • flag-grid의 FBCTF 포팅이 어느 시점에 고유 디자인으로 넘어가야 하는지
  • 핸드오프 문서로 잇던 저장소 간 작업을 A2A 같은 프로토콜로 옮기면 무엇이 달라지는지
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.
고쳐 쓴 기록 1번 수정 ·
  1. docs(til): drop dates and internal codes and add ai news to the 2026-08-30 til

댓글

아직 댓글이 없습니다