Skip to content

[Fix] 신버전이 남긴 mission_log 가 구버전 기기의 적립을 막지 않게 한다 - #320

Merged
KiSeungMin merged 1 commit into
developfrom
fix/mission-log-cross-version
Sep 19, 2026
Merged

KiSeungMin merged 1 commit into
developfrom
fix/mission-log-cross-version

Conversation

@KiSeungMin

Copy link
Copy Markdown
Contributor

✔️ 연관 이슈

📝 작업 내용

같은 계정을 신버전과 구버전 기기에서 번갈아 쓰면 구버전에서 한 활동이 XP 로도 미션 진행도로도 남지 않던 것을 고칩니다.

  • 기록은 그대로 남기고 중복 방지 판정만 가릅니다. 자동 적립이 도는 요청은 실제로 적립된 행만 보고, 진행도를 올리는 요청은 지금처럼 모든 행을 봅니다(MissionLogRepositoryCustom 의 accruedOnly).
  • "적립됐는가"는 mission_log.point 가 담습니다. 적립이 돌면 정가, 돌지 않으면 0 입니다. 컬럼이 이미 있어서 마이그레이션이 없습니다.
  • 하루 200점 상한도 같이 풀립니다. getPointSumToday 가 point 합을 그대로 보므로, 지급되지도 않은 신버전 활동이 상한을 갉던 문제가 따로 손대지 않아도 사라집니다.
  • 관리자 복습 로그 화면의 점수는 미션 타입의 정가로 바꿨습니다. 저장값이 0 이 될 수 있어서, 화면에 보이는 값을 지금과 같게 두려고 AdminPracticeLogResponseDto 에서만 바꿨습니다.

🤔 주요 고민 및 해결 과정

1. 행을 안 남길 것인가, 남기되 판정에서 뺄 것인가

  • mission_log 를 읽는 자리를 전수로 확인했습니다. 중복 방지와 하루 상한 말고도 관리자 조회가 여섯 곳, 훈장이 한 곳입니다.
읽는 곳 무엇을 세나 근거
DAU, 순 방문자, 일자별 방문 수, 활성 사용자 목록 USER_LOGIN 행 MissionLogService:230-256
복습 로그 목록, 건수, 일자별 건수 NOTE_PRACTICE 행 MissionLogService:259-292
관리자 사용자 상세의 기록 목록 그 사용자의 모든 행 AdminUserController:91
훈장 '개근' 연속 로그인 USER_LOGIN 의 날짜들 AchievementStatsCollector:71-78
하루 200점 상한 오늘 행의 point 합 MissionLogRepositoryImpl:87-97
  • 그래서 "남기되 판정에서 뺀다" 쪽입니다. 신버전에서 행을 안 남기면 신버전 사용자가 늘어날수록 위 지표가 0 으로 수렴합니다. MissionLogRetentionTest 가 이미 같은 이유로 "적립을 꺼도 행은 남는다"를 잠가 두고 있습니다.
  • 마이그레이션도 피할 수 있었습니다. point 가 원래 지급 여부와 무관하게 정가를 담고 있어서 쓰이지 않는 자리였는데, 여기에 "적립이 돌았는가"를 담으면 컬럼을 새로 만들지 않아도 됩니다. ddl-auto 가 validate 라 컬럼 추가는 Flyway 파일이 필요합니다.

2. 판정을 양쪽 다 풀면 안 됩니다

  • 진행도 두 개가 중복 방지 가드 안에 들어 있습니다. 출석(MissionLogService:111)과 세트 완료(:189)는 "오늘 첫 로그인", "이 세트 첫 완료" 판정을 그대로 빌려 씁니다.
  • 그래서 신버전 요청 쪽 판정은 건드리지 않았습니다. 함께 풀면 앱을 열 때마다 주간 출석이 하루에 5까지 차고, 같은 세트에 완료 요청을 세 번 보내는 것만으로 주간 세트 미션이 채워집니다.
  • 푸는 것은 적립 쪽 하나뿐입니다. 구버전 요청만 "적립된 행" 을 보고, 신버전 요청은 지금처럼 모든 행을 봅니다.

3. 두 기기를 번갈아 쓰면 같은 대상에 적립과 진행도가 따로 잡힙니다

  • 받아들인 값입니다. 신버전으로 문제 A 를 복습하면 진행도가 오르고, 같은 날 구버전으로 A 를 다시 복습하면 적립 +5 가 들어갑니다. 기기마다 따로 한 활동이라 요청 하나가 두 경로로 받는 것은 아닙니다.
  • 반대 방향은 지금과 같습니다. 구버전으로 먼저 적립한 뒤 신버전으로 같은 활동을 하면 진행도가 오르지 않습니다. [Fix] 자동 적립이 도는 요청에서는 미션 진행도를 올리지 않는다 #280 에서 정한 동작 그대로입니다.
  • 활동을 통째로 잃는 것보다 낫다고 봤습니다. LegacyAccrualPolicy 가 "덜 주는 쪽이 더 위험하다"로 판정을 세워 둔 것과 같은 기준입니다.

👤 사용자 영향

  • 두 기기를 번갈아 쓰는 사용자는 구버전 활동이 다시 적립됩니다. 출석 +15, 오답노트 등록 +10, 복습 기록 +5, 세트 완료 +15 입니다.
  • 신버전만 쓰는 사용자와 구버전만 쓰는 사용자는 달라지는 것이 없습니다. 테스트로 고정했습니다.
  • 관리자 화면도 달라지는 것이 없습니다. 복습 로그의 점수 열은 지금과 같은 값이 나옵니다.
  • 두 기기를 번갈아 쓴 날에만 mission_log 행이 한 건 더 생깁니다. 신버전 행과 구버전 행이 따로 남아서, 그날 그 사용자의 "일자별 방문 수"와 "복습 로그 건수"가 2 로 잡힙니다. DAU 와 순 방문자는 사용자 단위 COUNT(DISTINCT) 라 영향이 없습니다.

🔌 API 호환성

  • 요청/응답 DTO 변경 없음
  • 기존 Flutter 앱 버전과 호환 확인
  • breaking change 있음

🗄️ DB migration

  • 없음
  • 있음: migration 파일과 기존 데이터 호환성 확인

🔐 인증/권한

  • 영향 없음
  • 사용자별 데이터 소유권 검증 확인
  • 관리자/특수 권한 영향 확인

✅ 검증 결과

  • 실패하는 테스트를 먼저 만들었습니다. 새 MissionCrossVersionAccrualTest 를 고치기 전 코드로 돌리면 11개 중 5개가 실패합니다. 네 가지 metric(출석 15→0, 오답노트 등록 10→0, 복습 기록 5→0, 세트 완료 15→0)과 하루 상한 재현입니다.
  • 불변식 쪽 6개는 고치기 전에도 통과합니다. 신버전 단독(출석과 세트 진행도가 한 번만, 오답노트 기록은 하루 3건까지), 구버전 단독(같은 활동 반복해도 한 번만 적립, 행 6개), 구버전 먼저 적립한 뒤 신버전 진행도가 안 오르는 것입니다. 고친 뒤에도 그대로 통과합니다.
  • test --tests 'com.aisip.OnO.backend.mission.*' --tests 'com.aisip.OnO.backend.concurrency.Mission*' --tests 'com.aisip.OnO.backend.common.web.*' --tests 'com.aisip.OnO.backend.achievement.*' --tests 'com.aisip.OnO.backend.admin.*': 575개 통과, 실패 0
  • 전체 test: 3486개 통과, 실패 0, 건너뜀 0

🚀 배포 리스크

  • 운영에는 아직 이 회귀가 없습니다. 버전 게이트 커밋 0d8b7ee 와 MissionProgressUpdater 가 develop 에만 있고 main 에는 없어서, 지금 손실이 나고 있는 곳은 dev 서버뿐입니다. main 으로 나갈 때 [Fix] 자동 적립이 도는 요청에서는 미션 진행도를 올리지 않는다 #280 과 이 PR 이 함께 나가야 합니다.
  • 옛 행은 전부 "적립된 행"으로 잡힙니다. 지금까지 쌓인 행은 적립 여부와 무관하게 정가가 들어 있어서, 신버전이 남긴 옛 행도 구버전 요청을 계속 막습니다. 배포 시점 이후 행부터 풀립니다. 덜 주는 쪽이 아니라 지금과 같게 두는 쪽이라 안전합니다.
  • ono.mission.legacy-accrual.enabled 를 끄면 모든 요청의 point 가 0 으로 들어갑니다. 적립이 전부 꺼지는 상태라 상한 계산이 의미가 없어지는 것이라 문제는 없지만, 그 기간 행의 point 가 0 이라는 것은 알아 두셔야 합니다.

↩️ 롤백/대응 방법

  • 코드 롤백은 이 PR revert 한 번입니다. 스키마 변경이 없습니다.
  • revert 하면 point 가 0 인 행이 남는데, 이 행은 롤백된 코드에서도 중복 방지 판정에 그대로 걸리고 하루 상한만 덜 갉습니다. 데이터를 되돌릴 필요가 없습니다.

⚠️ 이미 손실된 기록 (이번 범위 밖, 실행하지 않음)

막힌 요청은 행을 남기지 않아서 손실 건수 자체는 셀 수 없습니다. 대신 두 버전을 섞어 쓴 사용자의 상한을 잽니다. dev DB 기준입니다.

-- 1) 게이트(0d8b7ee, 2026-09-14) 이후 신버전 요청을 보낸 사용자 수
--    mission_progress 행은 미션을 받을 수 있는 앱에서 온 요청만 만든다
SELECT COUNT(DISTINCT user_id) AS mission_capable_users
FROM mission_progress
WHERE created_at >= '2026-09-14';

-- 2) 같은 날 mission_log 도 남긴 사용자 = 두 버전을 섞어 썼을 수 있는 사용자의 상한
SELECT COUNT(DISTINCT l.user_id) AS mixed_version_users_upper_bound
FROM mission_log l
JOIN mission_progress p
  ON p.user_id = l.user_id
 AND p.period_key = DATE_FORMAT(l.created_at, '%Y-%m-%d')
WHERE l.created_at >= '2026-09-14'
  AND l.deleted_at IS NULL;

-- 3) 날짜별, 활동별 분포
--    mission_type 은 MissionLog:32 에 @Enumerated 가 없어 ordinal 로 저장된다
--    (0 USER_LOGIN, 1 PROBLEM_WRITE, 2 PROBLEM_PRACTICE, 3 NOTE_PRACTICE)
SELECT DATE(l.created_at) AS d, l.mission_type, COUNT(DISTINCT l.user_id) AS users
FROM mission_log l
JOIN mission_progress p
  ON p.user_id = l.user_id
 AND p.period_key = DATE_FORMAT(l.created_at, '%Y-%m-%d')
WHERE l.created_at >= '2026-09-14'
  AND l.deleted_at IS NULL
GROUP BY d, l.mission_type
ORDER BY d DESC;

- 같은 계정을 신버전과 구버전 기기에서 번갈아 쓰면, 신버전 요청이 남긴 mission_log 행이 구버전 요청의 "이미 적립했다" 판정에 걸려 그 활동이 적립으로도 진행도로도 남지 않았다
- mission_log 행은 그대로 남긴다. DAU, 순 방문자, 일자별 방문 수, 활성 사용자 목록, 복습 로그 조회, 사용자별 기록, 훈장 '개근'이 전부 이 테이블만 본다. 행을 안 남기면 그 지표들이 통째로 0 이 된다
- 대신 MissionLog.point 가 "이 기록으로 자동 적립이 돌았는가"를 담는다. 적립이 돌면 정가, 돌지 않으면 0 이다. 상한에 걸려 깎이기 전 정가를 넣는 이유는 실지급액을 넣으면 상한 계산이 달라져 구버전만 쓰는 사용자의 동작이 바뀌기 때문이다
- 중복 방지 판정에 accruedOnly 를 뒀다. 자동 적립이 도는 요청은 적립된 행만 보고, 진행도를 올리는 요청은 지금처럼 모든 행을 본다. 뒤쪽을 함께 풀면 신버전만 쓰는 사용자가 앱을 열 때마다 출석 진행도가 다시 오른다
- 하루 200점 상한(getPointSumToday)은 point 합을 그대로 보므로, 지급되지도 않은 신버전 활동이 상한을 갉던 것도 함께 사라진다
- 관리자 복습 로그 화면의 점수는 저장값이 아니라 미션 타입의 정가를 쓴다. 화면의 뜻이 "이 행동의 값어치"라 적립 여부와 무관하고, 보이는 값은 지금과 같다
- 출석, 오답노트 등록, 복습 기록, 세트 완료 네 가지의 재현과 신버전 단독, 구버전 단독 사용자의 동작을 MissionCrossVersionAccrualTest 로 고정했다

Closes #318
@KiSeungMin KiSeungMin added the fix 버그, 오류 수정 label Sep 19, 2026
@KiSeungMin KiSeungMin self-assigned this Sep 19, 2026
@KiSeungMin
KiSeungMin merged commit 94ece8f into develop Sep 19, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix 버그, 오류 수정

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant