Skip to content

카드 표시값을 포인터 대신 만든 사람 기준으로 계산하고 위시에 item_id 를 되살린다 (1단계) - #1054

Merged
m-a-king merged 3 commits into
devfrom
refactor/1051-wish-item-ref
Sep 7, 2026
Merged

카드 표시값을 포인터 대신 만든 사람 기준으로 계산하고 위시에 item_id 를 되살린다 (1단계)#1054
m-a-king merged 3 commits into
devfrom
refactor/1051-wish-item-ref

Conversation

@m-a-king

@m-a-king m-a-king commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Situation

  • 위시 포인터(snapshot_id) 한 칸이 "내가 만든 값의 위치" 와 "내가 기다리는 작업" 두 역할을 겸한다. 새로고침이 포인터를 진행 중 버전으로 옮기는 순간 내 수기값은 계산에서 빠지고, 새로고침이 실패하면 빈 카드 또는 옛 기계값이 남았다. 내 새로고침이 INCOMPLETE 로 끝나도 옛 READY 가 이겨 "일부만 빈 상태" 가 보이지 않았다.
  • 화면값은 이미 행들로 계산하는데(카드 표시값을 파생 조회로: 최신 기계 READY 우선 + 맥락 스코프 수기 존중 + 히스토리 박제 #857), 그 계산에 필요한 사실 하나가 행에 없었다. 서버 버전을 누가 시켰는지다.

Task

Action

스키마 (additive)

마이그레이션 내용
item_snapshots.created_by 추가 이 버전을 만든 맥락의 사람. MANUAL 은 edited_by 복사, 서버 행은 그 행을 가리키는 가장 오래된 위시(없으면 출전)의 주인으로 추정 백필
wishes.item_id 추가 (nullable) snapshot_id 조인으로 전량 백필 + 인덱스. 새 위시는 dual write
  • 서버 행 추정 백필은 병합과 진행 중 합류가 겹친 드문 행만 틀릴 수 있고, 그 행은 다음 버전부터 정확해진다. 아무도 가리키지 않던 옛 이력은 NULL 로 남는다.

표시 규칙을 도메인으로

  • ItemVersions.displayFor(viewer, waitingOn) 가 한 상품의 버전들, 카드 주인, 카드가 기다리는 행으로 보일 버전을 고른다. ItemDisplayService 는 버전을 끌어와 카드별로 위임만 한다.
  • 카드의 참조(위시 snapshot_id·출전 pin)는 "보는 값" 이 아니라 "기다리는 행" 이다. 본인의 등록·새로고침·합류로만 움직이므로, 진행 중 표시는 그 행을 기다리는 카드에만 나타난다.
순서 규칙
1 카드가 기다리는 행이 진행 중이면 그것 (내가 시작했거나 합류한 등록·갱신)
2 내 맥락의 값(READY·INCOMPLETE)과 공유 값(수기 아닌 READY) 중 더 새로운 것
3 값이 없으면 기다리던 행의 결과, 그다음 내 최신 사실. 남의 수기는 어디서도 안 본다
  • 새로고침은 어떤 플로우도 막지 않는다. 위시에서 새로고침 중이어도 같은 사람의 토너먼트 카드는 출전 pin 을 기다리므로 그대로고, 시작도 막히지 않는다. 새로고침은 이전 값이 있는 카드에서만 시작된다(진입 조건 전수 확인).
  • FAILED 는 후보가 아니다. 새로고침이 실패해도 카드는 새로고침 전과 같고, 남이 뒤에 남긴 실패가 내가 기다리던 결과를 지우지 않는다.
  • 남의 것은 안 보인다. 남의 진행 중·남의 수기·남의 미완은 내 카드에 새어 들어오지 않는다(정책 A: 수기는 쓴 사람만 본다).
  • 내 INCOMPLETE 는 옛 READY 를 이긴다. 완성 조건이 수기라 일부만 빈 상태로 보여 수기를 기다린다.
  • 공유 값은 출처로만 가른다. 수기가 아닌 READY 는 누가 만들었든 공유다. 도입 전 행은 추정 백필로 만든 사람이 채워지므로 만든 사람으로 가르면 옛 값이 사라진다.

병합 추종

  • 정체성 병합은 버전만 재부모화해 위시의 item_id 가 진 상품(soft delete)에 남는 구멍이 생긴다. item 도메인이 ItemMerged 를 병합 트랜잭션 안에서 발행하고, 위시가 BEFORE_COMMIT 리스너로 같은 커밋에서 item_id 를 옮긴다. item 은 위시를 모르는 단방향 의존을 유지한다.

요청자 기록

  • 서버 버전을 만드는 네 지점(위시 등록·새로고침·공유 합류·토너먼트 링크 등록)이 ItemSnapshot.pending(itemId, requestedBy) 로 시킨 사람을 적는다. 공유 합류 경로는 resolveAttachment 에 요청자를 넘긴다.
  • 표시값 호출부(위시 목록·단건·수기 base·새로고침 가드·토너먼트 대기실·출전 판정·시작 박제)는 카드 주인을 함께 넘긴다. 위시는 주인, 출전은 출전시킨 사람이다.

검토 후 채택하지 않은 안

이유
새로고침 실패 시 포인터를 되돌린다 파싱 워커가 위시를 만져 도메인 경계를 넘고 재시도 차단(409) 판정과 얽힌다
진행 중을 상품 단위로 보인다 남의 새로고침이 내 카드에 보이는 것은 불변식 위반. 등록 합류(값이 없을 때)만 예외
한 PR 로 전환을 끝낸다 옛 컨테이너가 snapshot_id 만 쓰는 배포 창에서 NOT NULL·DROP 이 깨진다
진행 중을 만든 사람 기준으로 보인다 위시 새로고침이 같은 사람의 토너먼트 카드를 가로채 시작을 막고, 합류자에겐 진행 중이 안 보인다. 카드가 기다리는 행 기준으로 바꿨다(리뷰 반영)
남의 수기를 빈 카드의 fallback 으로 빌린다 남의 오타·장난값이 내 카드에 뜬다. 수기의 신뢰 경계는 사람이라는 원칙(#825 결정 4) 유지

Result

  • 표시 규칙 분기는 ItemVersionsTest(단위 17건)가 망라하고, WishDisplayIntegrationTest 는 HTTP 경계에서 불변식 세 건(남의 진행 중 안 보임·수기값 유지·내 INCOMPLETE)을 고정한다. 병합 추종은 정체성 기록 통합 테스트가 위시 item_id 이동으로 고정한다.
  • 죽은 배치 조회 findLatestMachineReadyByItemIds 3층을 지웠다. 상품별 버전 전체 조회의 상한은 이슈 후속 최적화로 남긴다.
  • 가격 이력 테스트의 표시값 기대가 바뀌었다. 내 수기가 LLM READY 보다 새로우면 포인터가 기계 버전을 가리켜도 카드는 내 수기값이다.
  • 후속 단계: 2단계에서 위시 읽기를 item_id 로 전환하고 알림 수신자를 "그 행을 기다리는 카드 주인" 기준으로 바꾼다. snapshot_id 는 제거하지 않고 "기다리는 행" 으로 의미와 이름을 바꾼다. 3단계에서 edited_by 를 제거한다.

연관 이슈

- 위시 포인터(snapshot_id) 한 칸이 "내가 만든 값" 과 "내가 기다리는 작업" 두 역할을 겸해, 새로고침이 포인터를 옮기는 순간 내 수기값이 계산에서 빠지고(실패 시 빈 카드·옛 기계값) INCOMPLETE 는 옛 READY 에 밀렸다. 화면값 계산에 필요한 사실은 "이 버전을 누가 만들었나" 인데 행에 없어 포인터로 간접 표현하던 것이 원인이다
- item_snapshots.created_by 를 두고 등록·새로고침·공유 합류·토너먼트 링크 등록 네 지점에서 요청자를 기록한다. MANUAL 행은 editedBy 가 곧 created_by 다. 백필은 MANUAL 은 edited_by 복사, 서버 행은 그 행을 가리키는 가장 오래된 위시(없으면 출전)의 주인으로 추정 - 병합·합류가 겹친 드문 행만 틀릴 수 있고 다음 버전부터 정확해진다
- 표시 규칙을 도메인 ItemVersions.displayFor(viewer) 한 곳에 박았다: 내가 시킨 진행 중 > 내 맥락의 값(READY·INCOMPLETE) vs 공유 기계 READY 중 최신 > 값이 없으면 상품의 최신 사실. FAILED 는 후보가 아니라 새로고침 실패 뒤 카드가 새로고침 전과 같고, 남의 진행 중·남의 수기·남의 미완은 내 카드에 보이지 않는다. ItemDisplayService 는 버전을 끌어와 카드 주인별로 위임만 한다
- 위시에 item_id 를 되살려(nullable, 백필, dual write) 위시가 아이템만 참조하는 후속 단계의 바닥을 깐다. 읽기는 아직 snapshot_id 경로다 - 옛 컨테이너가 item_id 를 안 쓰는 배포 창을 견디기 위해 NOT NULL·읽기 전환·snapshot_id 제거는 다음 단계로 나눈다
- 가격 이력 테스트의 표시값 기대를 새 규칙으로 바꿨다(내 수기가 LLM READY 보다 새로우면 포인터와 무관하게 내 수기). 표시 규칙 분기는 ItemVersionsTest(단위)가 망라하고 WishDisplayIntegrationTest 는 HTTP 경계에서 불변식 세 건(남의 진행 중·수기값 유지·내 INCOMPLETE)을 고정한다

Claude-Session: https://claude.ai/code/session_01HnKAcdnoziL8hdVr9xinFb
@m-a-king m-a-king added the refactor 구조 개선, 외부 동작 불변 label Sep 6, 2026
@m-a-king m-a-king self-assigned this Sep 6, 2026
@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Team

Run ID: 117ef657-2727-47cb-b547-9852b07889e7


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Discord 스레드 연동용 메타데이터입니다. discord-pr-bot 워크플로가 자동 생성하며, 수정·삭제하면 PR 과 Discord 알림 연동이 끊깁니다.

- 코드 리뷰 반영. 진행 중을 "만든 사람" 기준으로 보이면 위시 새로고침이 같은 사람의 토너먼트 카드를 가로채 시작을 막고, 남의 파싱에 합류한 사람에겐 진행 중이 안 보였다. 카드의 참조(위시 snapshot_id·출전 pin)를 "기다리는 행" 으로 의미 확정하고, 그 행이 진행 중일 때만 진행 중을 보인다. 새로고침은 이전 값이 있는 카드에서만 시작됨을 진입 조건 전수로 확인했다
- 값 없는 fallback 을 기다리던 행의 결과 우선으로 바꿨다. 남이 뒤에 남긴 FAILED 가 합류자의 INCOMPLETE 를 지우고 409 로 복구까지 막던 구멍이고, 마지막 fallback 이 남의 수기까지 새던 것도 막았다(정책 A: 수기는 쓴 사람만 본다)
- 공유 값 판정을 출처만으로(수기 아닌 READY) 바꿨다. 같은 PR 의 추정 백필이 도입 전 행에도 만든 사람을 채우므로 만든 사람으로 가르면 옛 값만 갖던 카드가 빈다. == null 비교도 이 수정으로 사라졌다
- 정체성 병합이 위시의 item_id 를 안 따라가던 구멍을 ItemMerged 이벤트 + BEFORE_COMMIT 리스너로 같은 트랜잭션에서 옮기게 했다. item 도메인이 위시를 모르는 단방향 의존을 유지한다
- 호출처가 사라진 findLatestMachineReadyByItemIds 3층을 지웠다. 옛 정책을 말하던 주석도 함께 사라진다

Claude-Session: https://claude.ai/code/session_01HnKAcdnoziL8hdVr9xinFb
@m-a-king
m-a-king merged commit 0d4bf0c into dev Sep 7, 2026
5 checks passed
@m-a-king
m-a-king deleted the refactor/1051-wish-item-ref branch September 7, 2026 09:54
m-a-king added a commit that referenced this pull request Sep 7, 2026
…정한다 (2단계)

- 1단계(#1054)가 깐 wishes.item_id 를 배포 창의 NULL 재백필 뒤 NOT NULL 로 올리고, 목록·단건·수기 base·메모·새로고침·출전 소유 체크·메모 조회가 위시의 item_id 로 상품에 간다. 스냅샷 theta join 세 개가 사라진다
- Wish.snapshotId 를 waitingSnapshotId / waitFor 로 바꿔 "보는 값" 이 아니라 "기다리는 행" 임을 코드가 말하게 했다. 컬럼명(snapshot_id)은 rename 이 3단계 배포를 요구해 그대로 둔다
- 표시값 질의를 DisplayCard(itemId, owner, waitingOn) 로 바꿨다. 위시는 포인터 행을 따로 읽지 않고 카드 셋으로 묻고, 출전은 pin 에서 상품을 읽는 waitingOn 팩토리로 같은 모양을 쓴다. 결과는 입력 순서의 리스트라 pin 이 겹치는 카드도 안전하다
- 해소 통지(#1028) 수신자를 "미완성 버전을 가리키던 사람" 에서 "화면값이 실패·미완이었다가 이 버전으로 채워지는 카드의 주인" 으로 바꿨다. 카드 표시값 규칙(ItemVersions) 그대로 이 버전을 뺀 표시값과 넣은 표시값을 비교하므로 알림과 화면이 어긋날 수 없다. 상태 필터 쿼리 두 개는 상품의 카드 전부를 읽는 조회로 대체
- editedByMe 를 created_by 로 읽는다. edited_by 제거는 3단계
- raw SQL 로 위시를 넣던 테스트 두 곳에 item_id 를 채웠다(NOT NULL 회귀)

Claude-Session: https://claude.ai/code/session_01HnKAcdnoziL8hdVr9xinFb
m-a-king added a commit that referenced this pull request Sep 8, 2026
)

* refactor: 위시 읽기를 item_id 로 옮기고 참조를 "기다리는 행" 으로 확정하며 해소 통지를 표시값 규칙으로 판정한다 (2단계)

- 1단계(#1054)가 깐 wishes.item_id 를 배포 창의 NULL 재백필 뒤 NOT NULL 로 올리고, 목록·단건·수기 base·메모·새로고침·출전 소유 체크·메모 조회가 위시의 item_id 로 상품에 간다. 스냅샷 theta join 세 개가 사라진다
- Wish.snapshotId 를 waitingSnapshotId / waitFor 로 바꿔 "보는 값" 이 아니라 "기다리는 행" 임을 코드가 말하게 했다. 컬럼명(snapshot_id)은 rename 이 3단계 배포를 요구해 그대로 둔다
- 표시값 질의를 DisplayCard(itemId, owner, waitingOn) 로 바꿨다. 위시는 포인터 행을 따로 읽지 않고 카드 셋으로 묻고, 출전은 pin 에서 상품을 읽는 waitingOn 팩토리로 같은 모양을 쓴다. 결과는 입력 순서의 리스트라 pin 이 겹치는 카드도 안전하다
- 해소 통지(#1028) 수신자를 "미완성 버전을 가리키던 사람" 에서 "화면값이 실패·미완이었다가 이 버전으로 채워지는 카드의 주인" 으로 바꿨다. 카드 표시값 규칙(ItemVersions) 그대로 이 버전을 뺀 표시값과 넣은 표시값을 비교하므로 알림과 화면이 어긋날 수 없다. 상태 필터 쿼리 두 개는 상품의 카드 전부를 읽는 조회로 대체
- editedByMe 를 created_by 로 읽는다. edited_by 제거는 3단계
- raw SQL 로 위시를 넣던 테스트 두 곳에 item_id 를 채웠다(NOT NULL 회귀)

Claude-Session: https://claude.ai/code/session_01HnKAcdnoziL8hdVr9xinFb

* refactor: 해소 통지 판정을 도메인(ItemVersions.recovers)으로 옮기고 리뷰 지적을 반영한다

- 코드 리뷰 반영. 해소 통지 판정이 서비스 안 지역 함수로 남아 있던 것을 ItemVersions.recovers 로 옮기고, 판정 기준을 "그 버전을 뺀 집합" 이 아니라 "그 버전이 생기던 순간의 접두 상태" 로 바꿨다. 성공 버전이 둘 쌓인 채 앞 버전 이벤트를 처리하면 집합 비교는 앞 버전을 뒤 버전이 가려 아무 알림도 안 나갔다. 그 버전 뒤에 기다리기 시작한 카드도 제외한다. 단위 9건 추가
- 적재도 표시값과 같은 경로를 탄다(ItemDisplayService.versionsContaining). 상품은 이벤트의 itemId 가 아니라 버전 행에서 되짚어, 정체성 병합이 파싱 완료와 겹쳐 버전이 이긴 상품으로 옮겨져도 예외·유실 없이 판정한다. 알림 해석기가 스냅샷 저장소를 직접 쓰지 않게 돼 동결 목록에서 뺐다
- 후보를 상태로 SQL 선필터하지 않는다. 내 INCOMPLETE 가 기다리는 행(READY)보다 새로우면 규칙 2 로 카드가 미완성이 되므로 기다리는 행의 상태만 보는 필터는 그 카드를 놓친다. 출전은 파생을 타는 대기실(PENDING)만 후보다 - 시작된 토너먼트는 pin 을 박제해 읽어 채워질 카드가 없다. 두 케이스를 통합 테스트로 고정
- 표시값 배치 결과를 카드 키 맵으로 바꿔 호출부가 순서에 기대지 않게 했다. 출전 판정 맵은 위시의 상품을 키로 만들고 키 커버리지로 검사한다
- 메모만 수정하는 경로의 표시값 이중 파생, displayedSnapshotsOf 의 도달 불가 fallback, 한 줄 위임 displayOf, 죽은 projection data class 2개를 제거했다. TournamentItemCardView 는 라우팅 뷰를 상속한다
- 수기 팩토리 인자를 createdBy 로 바꿔 쓰는 곳을 하나로 모았다(edited_by 는 같은 값을 함께 적고 3단계에서 제거). editedByMe 는 도메인 술어(isManual·isOwnedBy)로 판정한다
- 메트릭 SQL 의 위시-스냅샷 조인을 wishes.item_id 직접 참조로 바꾸고 옛 주석을 고쳤다. CLAUDE.md 도메인 용어의 "wish 는 활성 버전을 가리킨다" 를 현재 모델로 고쳤다

Claude-Session: https://claude.ai/code/session_01HnKAcdnoziL8hdVr9xinFb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor 구조 개선, 외부 동작 불변

Projects

None yet

Development

Successfully merging this pull request may close these issues.

위시 참조를 아이템으로 바꾸고 화면값을 행에서 계산한다 (스냅샷 created_by 통합)

1 participant