Skip to content

Latest commit

 

History

History
44 lines (31 loc) · 7.55 KB

spec-alignment.md

File metadata and controls

44 lines (31 loc) · 7.55 KB

티어·점수 스펙 정합화

기준: 공통, 백엔드, 점수 정책, 2026-09-14 개정.

계산·저장

  • 점수 소유 테이블은 user_score_summaries, cycle_score_states, score_transactions 세 개다. 사이클 키는 사용자·챌린지·안정된 사이클 UUID이며, 운영 방으로 향하는 FK가 없다. 진행 중 연속 결과는 NULL, 종료 결과는 사이클 날짜순으로 조회한다.
  • 가입 초기 10점도 점수 도메인이 원장과 함께 기록한다. 사용자 요약에 배타 잠금을 잡고 원장·현재 요약·사이클·COMMIT·공통 아웃박스를 한 트랜잭션으로 확정한다. 중복 INSERT의 공유 잠금에서 배타 잠금으로 승격할 때 생기는 교착을 피한다.
  • ScoreCalculator는 정수 산식, 사이클별 순변동 ±20, 계정 0~2,000, 실제 티어 배점과 표시 티어 20점 유예를 계산한다. 남은 가능일은 판정일 이후의 실제 날짜다. 한도 누계에는 실제 반영량만 더한다.
  • 같은 논리 시각은 날짜 판정 → 마감 → 사건 순, 같은 종류는 UUID·날짜·원본 키의 고정 순서로 처리한다. 표시 티어는 같은 시각 묶음의 마지막에서 확정한다. 사이클 시작 시점의 실제 티어로 배점을 고정한다.
  • 원본+버전과 요청 키+입력 해시를 모두 검사한다. 같은 요청의 재시도는 저장된 완료 응답을 반환하며, 같은 키에 다른 입력이면 거절한다. 새 전송 봉투로 같은 원본을 보내도 중복 계산하지 않는다.
  • 과거 입력·정정은 최초 영향 시각 묶음부터 취소/대체 RESULT를 추가한다. 기존 행을 UPDATE/DELETE하지 않는다. 후속 사이클 배점·연속 기록·같은 버전의 후속 사건까지 재생하고, 변화 0도 CORRECTION_COMMIT을 남긴다. 현재 상태/사이클 이벤트는 보내고 과거 사용자 알림은 재발행하지 않는다.
  • 사건 감점은 당시 AUTO 근거·시각·확정 감점·완료 주간/면제 입력을 검증한다. 사이클 한도는 사용하지 않는다. 자진 탈퇴 완료 주간은 실제 종료된 유효 사이클 수다. 무감점 면제도 완료 근거를 남긴다.

도메인 연결

  • 챌린지 영역이 공통 7일 사이클·가입 스냅샷·목표·인증 가능일·마감을 제공한다. 가입 이벤트가 없는 기존 멤버는 보존된 멤버십 가입 시각을 사용한다. 부분 참여 주간과 가입 전 과거 인증은 점수에서 제외한다.
  • 인증 확정/인용 트랜잭션에서 재생 가능한 입력을 공통 아웃박스에 저장한다. 원본 AUTO 근거는 이후 설정 변경·이탈·방 삭제와 무관하게 유지된다. GPS 등의 원시 신호는 점수 원장에 넣지 않는다.
  • 누락 보정은 전역 MAX 시각 대신 원본 ID 페이지와 버전을 대조한다. 한 사용자 실패가 뒤의 사용자를 건너뛰게 하지 않는다. 마감은 모든 가능일의 확정과 앞선 대상 사이클의 종료를 확인한다. 필요한 근거가 없으면 임의 실패/성공으로 채우지 않고 보류한다.
  • 방 내부는 현재 사이클 버전과 참여 구간을 검사해 오래된 경고·강퇴 이벤트를 거른다. 확정된 강퇴는 정정으로 취소하지 않는다. 챌린지 영역은 최신 표시 티어로 유지 조건을 검사해 TIER_GATE로 이탈·음소거·랭킹을 정리하며 추가 감점/재입장 대기를 만들지 않는다.
  • 마이페이지 그래프는 취소되지 않은 RESULT의 논리 시각과 재생 잔액을 사용한다. 가입/완료 메타데이터는 변동 그래프에 표시하지 않는다. 변동 상세는 취소·정정 행을 보존한다.

배포·이관 조건

V62는 빈 구 점수 원장에 대한 전환이다. 기존 점수 이력이 있는 DB에는 그대로 배포할 수 없다. 구 원장에는 안정된 원본 시각·참여 구간·설정·재생 입력이 없어 이를 현재 값으로 추정하면 과거 이력을 훼손한다. V62는 DROP 전에 구 원장/사이클/연속/정정 행이 없고 요약이 초기 BRONZE 10점인지 검사한다. 실패 시 업무 테이블을 제거하지 않는다.

데이터가 있는 환경은 다음 절차로 별도 검증된 이관이 필요하다.

  1. 점수 쓰기와 방 하드 삭제를 중단하고 구 점수 테이블, 판정, 가입·설정 이력, 미처리 아웃박스를 백업한다.
  2. 원본별 당시 자격·시각·사이클 입력을 복원한다. 구 현재 스트릭을 과거 모든 사이클에 복사하거나 수신 시각을 귀속 시각으로 대체하지 않는다.
  3. 격리 DB에서 사용자별로 재생해 요약·유효 원장 합계·각 사이클 한도·연속 연결·정정/완료 키를 대조한다. 복원되지 않는 범위는 전환을 보류한다. 현재 V62의 선행 검사를 우회하지 않는다.
  4. 백업과 새/구 ID 매핑, 미처리 이벤트 변환을 검증한 전용 이관을 적용하고 쓰기를 전환한다. 구 버전 바이너리만 재배포하는 롤백은 지원하지 않는다.

CHECKPOINT COMMIT 읽기는 선행 사이클 의존성이 없는 검증된 계정 경계만 지원한다. 경계 payload의 체크섬은 손상 검사이며 외부 이관 검증의 대체물이 아니다. 과거 사이클 상태가 필요한 경계는 SCORE_CHECKPOINT_CYCLE_BASELINE_MISSING으로 거절한다. 자동 체크포인트 생성이나 물리 원장 삭제는 하지 않는다. 1년은 조회 범위이며 복구 근거·최신 종료 사이클은 계속 보존한다.

구 부정행위/권한 강퇴 아웃박스에 당시 인증 방식과 발생 시각이 없으면 재시도 대상으로 남긴다. 기존 탐지·강퇴 원본으로 검증해 새 payload를 재발행해야 한다.

운영·검증

  • 일 05:00 KST에 사용자 잠금 아래 원본 재생과 요약·유효 원장·사이클 상태를 대조한다. score.integrity.mismatched.users, score.integrity.failed.checks가 0이 아니면 해당 사용자와 오류 종류를 로그에서 조사한다. 손상된 증거를 자동 덮어쓰지 않는다. 누락 입력은 분 단위 원본 대조/공통 아웃박스가 재처리한다.
  • score.processing.duration, score.user.lock.wait, score.replay.inputs, score.processing.failures를 노출한다. 메서드 타이머는 프록시의 최종 DB 커밋 시간은 포함하지 않는다.
  • 재생 상한은 app.score.max-replay-inputs=20000, 사용자 트랜잭션 제한은 30초다. 초과·실패는 전체 롤백하고 공통 아웃박스가 재시도한다. 정상 입력도 보존 입력으로 계산 상태를 복원하므로 이력 크기에 따른 비용이 있다. 운영 6만 건 집중 부하에 대한 성능 보장은 별도 환경 측정이 필요하다.
  • IntegerScoreTest, TierScoringIT, ScoreCalculatorTest, ScoreReplayIT가 정수 산식·경계·정정 전파·동시 중복·응답 유실 재시도·아웃박스 실패 시 COMMIT 포함 전체 롤백·INVALID·선행 마감 보류·1년 이력 재생을 검증한다. 인증·마이페이지·방 회귀 테스트도 함께 실행한다.
  • 로컬 MySQL 8.4 Testcontainers에서 1년·3개 챌린지 입력 1,248건을 보존한 계정에 사건 20건을 순차 처리한 측정은 p95 43.0ms, 최대 60.3ms였다(2026-09-15, 최적화 전 p95 102.5ms). 사이클 잠금/상태 조회를 사용자별 한 번으로 합친 결과이며, 운영 집중 부하나 다중 사용자 처리량을 측정한 수치는 아니다.