Skip to content

Repository files navigation

코테독촉기

tests

GitHub 저장소의 백준 풀이 커밋을 모아 월별 랭킹을 매긴다. 그룹을 만들어 서로의 진척을 비교한다. 대학교 4학년 프로젝트로 Flask 로 만들고 Kotlin + Spring Boot 로 옮겼다 — 두 구현이 한 저장소에 있다. 기능은 조회·정렬·삽입·삭제가 있다.

웹 데모

구조

server/       Kotlin + Spring Boot (현재)
app/          Flask (원본. 대조용, 유지보수 안 함)
migrations/   Flask 시절 Alembic 마이그레이션
docs/         ADR · 용어집 · 기록 · 완료된 계획(archive/)

스키마의 진실 원천은 server/src/main/resources/db/migration/ 의 Flyway 마이그레이션이다.

데이터

erDiagram
    users ||--o{ commits : "커밋을 남긴다"
    users ||--o{ history : "풀이를 기록한다"
    users ||--o{ participate : "그룹에 참여한다"
    users ||--o{ groups : "소유한다"
    users ||--o{ user_monthly_score : "월별 점수를 갖는다"
    groups ||--o{ participate : "멤버를 갖는다"
Loading

컬럼은 적지 않는다 — 마이그레이션이 원천이고, 여기 옮겨 적으면 낡는다. 이 스키마를 읽는 핵심은 무엇이 원천이고 무엇이 파생인가다.

파생 원천 갱신
user_monthly_score.score commits 커밋 저장과 같은 트랜잭션에서 COUNT(*) 절대값으로 덮어쓴다
groups.member_counter participate 조건부 원자적 UPDATE (WHERE counter < max)

절대값이라 몇 번을 다시 계산해도 같은 값이 나온다 — 증분(+= n)이 한 번 틀리면 복구가 안 됐던 문제를 구조로 없앤 것이다. 랭킹은 user_monthly_score 하나만 읽는다. 예전에는 Redis ZSET 이 세 번째 사본이었고, 사본들이 어긋나는 게 결함의 출처였다.

user_monthly_score행이 있다 = 랭킹에 포함된다. 그래서 랭킹 쿼리는 users 를 조인하지 않고, 탈퇴자는 FK 의 ON DELETE CASCADE 가 행을 지워서 빠진다 (조인을 없앤 효과는 5만 행 기준 87ms → 0.5ms. 2026-08-17 측정).

실행

테스트 — Docker 만 있으면 된다. Testcontainers 가 실제 MySQL 을 띄운다.

export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home
./server/gradlew -p server test

서버

cp .env.example .env          # DB 접속 정보와 GITHUB_TOKEN 을 채운다
docker compose up -d          # MySQL (스키마는 기동 시 Flyway 가 만든다)
./server/gradlew -p server bootRun    # http://localhost:8080 (웹 데모 포함)

bootRun 이 저장소 루트의 .env 를 읽어 환경 변수로 넣는다 — export 를 따로 하지 않는다. .env.gitignore 에 있고, 키 목록은 .env.example 에 있다.

인증은 없다(permitAll). userId 를 파라미터로 받는다 — 원본과 동일하며 범위에 넣지 않았다.

더 읽을 것

왜 그렇게 정했나 docs/adr/ — 결정 하나당 한 파일
용어 docs/용어집.md
틀린 것과 그 경위 docs/기록.md — 잘한 것보다 틀린 것을 자세히 적었다
고칠 때 지킬 것 AGENTS.md
API 명세 · 트러블슈팅 server/README.md
성능 측정 기록 docs/archive/bench/README.md — Redis 를 걷어낸 근거. 지금은 재현되지 않는다

이 저장소에서 넣었다가 도로 뺀 것들이다. 왜 뺐는지는 docs/기록.md 에 있다.

넣은 것 뺀 때 무엇으로 대체했나
Redis ZSET 랭킹 캐시 + 자가치유 스케줄러 2026-08-19 user_monthly_score 집계 테이블. 랭킹의 사본이 셋에서 하나로
커밋 수집 Redis 분산 락 2026-08-20 없앴다. UNIQUE(user_id, sha) + 절대값 재계산이라 연산이 이미 멱등이다
users.active 소프트 삭제 2026-08-21 물리 삭제 + FK ON DELETE CASCADE

About

BOJ commit ranking service (코테독촉기) — Flask→Kotlin/Spring Boot rewrite, Testcontainers + CI

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages