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 : "멤버를 갖는다"
컬럼은 적지 않는다 — 마이그레이션이 원천이고, 여기 옮겨 적으면 낡는다. 이 스키마를 읽는 핵심은 무엇이 원천이고 무엇이 파생인가다.
| 파생 | 원천 | 갱신 |
|---|---|---|
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 |
