Profile·Social·Group·Import의 원격 사본을 owner resource로 — A11 (2026-09-10)
- 기간: 2026-09-10, Codex 1세션. 오너 지시: “바벨릭 #1419 진행해줘”, “분석 끝나면 바로 진행해”.
- 랜딩: 앱 PR #1516,
release/v0.18.0mergedaca15d90ccfafb74ec2c5f2912135d411a64ad8(2026-09-10 16:06:58 KST). Merge Check34448266204 성공, 잡1분22초. 마이그레이션·Edge 변경·Production 배포 없음. - 설계서: A11 실행 이슈 #1419의 분석·6 Phase 계획, 공통 resource 통합 계약.
- 정본: owner resource, 화면 연결, 인입 경계.
- 도구: 저장소 밖 작업 디렉터리의 테스트 선택 runner·검증 로그. #1478 제외 목록을 읽어 검증 범위만 선택하며 앱 runtime/CI가 문서를 읽도록 추가하지 않았다.
- 게이트: 필수 precheck 통과(4분4초, 단위3,509 pass/0 fail/60 조건부 skip), release Merge Check와 실제 release 병합 성공. full/browser/DB/Production 행동은 이번 세션에서 실행하지 않았다.
- 버그리포트: 없음. Production 결함을 실측해 수리한 배포가 아니라 승인된 구조 이전 후보다.
- 계약: feature-resource-integration·owner-scoped-resource-cache·profile/feed/group props·import-pipeline의 A11 절.
Phase 현황
| Phase | 내용 | 상태 |
|---|---|---|
| Phase 1 | 프로필 snapshot/workspace·쓰기 binding | 코드·검증 a0611518 |
| Phase 2 | 내 피드 페이지 resource와 refresh/append | 코드·검증 d5cdc09d |
| Phase 3 | 팔로우·친구·댓글·좋아요·차단 | 코드·검증 04262f2a |
| Phase 4 | 그룹·알림·권한·이탈 lifecycle | 코드·검증 9e288254 |
| Phase 5 | 인입 관찰·job ID 복원·진행 표시 | 코드·검증 fb2ded61 |
| Phase 6 | 전체 조립·제거 목록·문서·PR | 조립 2dac339e, 승인된 검사 제외 a0fd30f2, precheck·Merge Check 성공, PR1516 release 병합 |
1. 배경
선행 A03/A04/A05/A09/I01의 코드가 목적 release에 포함된 것을 ancestry로 확인했다. 프로필 workspace·피드·소셜·그룹·인입 조회는 feature별 state/ref/map과 요청 순번으로 원격 응답을 각각 보관했다. A11은 이를 A08 owner resource와 A07 epoch로 연결하는 작업이다.
2. 문제 제기
동일 서버 값을 여러 소유자가 유지했다
프로필/피드는 remoteDataController, 소셜/그룹은 controller store의 별도 응답 배열·캐시, 인입은 일회성 runner의 지역 job이 상태를 보유했다. 화면 이동·새 요청·쓰기 ACK가 겹칠 때 각 구현이 응답 적용 순서를 따로 관리해야 했다. Production에서 중복 요청 횟수나 사용자 증상을 실측한 것은 아니다.
3. 해결 방안
원칙
- D1(2026-09-10): 분석 후 바로 진행한다는 오너 go를 적용했다.
- D2: 화면은 resource의 파생 props를 받고 서버 값은 한 owner/대상 키에 둔다. 기존 원자 저장·권한·I01 결과 의미를 유지한다.
- D3: #1478의 Wodup·관리자 자동 검사 제외를 따른다. 제외 검사를 통과 수에 넣지 않는다.
- D4(2026-09-10 후속): “끝났으면 precheck에 병합까지 해. 이거 전체 지침에 올려줘.” 구현·Draft에서 멈춘 처리를 정정하고, 공통 완료 기준을 반영했다.
접근
feature 내부를 공용 resource로 옮기고 공유 요청·취소·owner epoch·확정 쓰기 무효화를 연결했다. 기존 ref 옆에 캐시를 더 붙이는 방식은 원격 사본이 계속 두 벌이라 채택하지 않았다. timer/retry를 늘려 경합을 가리는 방식도 채택하지 않았다. 인입의 주기 조회는 기존 비동기 job 관찰 기능이며 중복 루프를 한 곳으로 모았다.
4. 적용한 내용
Phase 1 — 프로필
profileController를 features/profile/profileFeatureBinding으로 옮겼다. 초기 프로필과 workspace는 같은 resource snapshot을 사용한다. 이름·사진·신체 데이터·설정·InBody의 기존 저장 payload를 유지하고 owner/필드 쓰기 순서로 이전 ACK와 읽기를 막는다.
Phase 2 — 피드
페이지 키는 owner/target/limit/cursor다. 첫 페이지부터 연결된 창을 id로 중복 제거하며 최대200행을 유지한다. 첫 페이지 refresh 중 append는 새 cursor를 기다린다. PROFILE_FEED*는 읽기 전용 화면 투영이며 독립 저장값이 아니다.
Phase 3 — 소셜
팔로우/검색·친구 리포트/PR/달력/피드·댓글·좋아요·차단의 서버 응답을 resource로 옮겼다. 확정 댓글/좋아요가 늦은 읽기에 의해 되돌아가지 않으며 unfollow/block은 해당 친구 데이터와 열린 화면을 정리한다. 같은 계정 재로그인도 이전 epoch의 쓰기 ACK를 버린다.
Phase 4 — 그룹·알림
목록·초대·라운지·보드·완료자·월별 표시·출석·상세를 키로 분리했다. 보드와 완료자 독립 로딩·보드 원자 직렬화를 유지했다. 권한 상실 뒤 이전 보드/목록 표시와 늦은 ACK 재표시를 차단한다. 알림 페이지·읽음 cutoff·실제 세션 owner 대조를 유지했다.
Phase 5 — 인입 관찰
같은 owner/job의 UI와 명령이 하나의 관찰 루프를 공유한다. 기기 저장은 작업 ID뿐이며 화면 이탈은 서버 작업을 취소하지 않는다. 취소·detached·실패·이미 가져온 파일을 성공과 구분한다. 성공 시 관련 resource를 갱신하고 I01의 통계 갱신 대기 표시를 유지한다. parser/worker/원문 정책은 바꾸지 않았다.
Phase 6 — 조립과 인계
여러 feature를 한 runtime에 조립하고 3회 계정 전환/해제 후 owner listener가 기준값으로 돌아오는 것을 확인했다. G05 장부에 실제 새 경로와 증거를 연결했다.
주요 결정과 그 근거
그룹 최대128, 친구96, 인입8 항목의 메모리 캐시를 사용한다. 캐시 상한은 실사용 메모리 벤치마크 결과가 아니라 구현 제한이다. 조회 payload를 가진 map/ref는 제거하되 선택된 화면·입력·pending·낙관 표시·취소 controller는 UI 상태로 남겼다.
작업 중 드러난 것
- 이동 전 파일/함수 위치를 고정한 기존 테스트 일부를 새 실제 경계로 갱신했다. 원래 동작 단언은 유지했다.
- Phase 2 전체 실행의 source anchor 실패3건, Phase 4 AbortSignal 인자 비교 실패1건, Phase 5 lazy runner 위치 실패1건을 기록하고 수정했다. 최초 실행을 사후 전체 통과로 바꾸지 않았다.
- 초기 종료 때 #1478 제외 구현이 v0.18.0에 없다는 이유로 필수 precheck를 보류했다. 오너 후속 지시로 이 중단을 정정했다. 최신 release
5b16a162를 합치고 자기 작업 브랜치에 승인된 Node 검사 제외를 현재 A05/I01 경로로 적용해 원형 precheck를 통과했다. 다른 담당자의 브랜치·큐는 바꾸지 않았다. - 과거 미실행 표기 정정: 최초 외부 제외 목록은 A05의 새 import repository 검사와 I01의 Wodup replay 항목까지 완전히 대응하지 못했다. 해당 이전 선택 실행을 Wodup 관련 검사가 전혀 실행되지 않은 증거로 보지 않는다. 원형 precheck 전에 기존 승인 목록과 현재 모듈을 대조해 .optional.mjs로 분리했다. 원래 단언·함수는 보존했으며 일반 사용자·InBody·공용 계약/job 검사는 유지했다.
5. 적용 결과
| 항목 | 결과 |
|---|---|
| 같은 키 두 소비자의 요청 | 개별 store 관리 → 공용 조회1회(행동 검사) |
| 조회 중 한 소비자/마지막 소비자 이탈 | 남은 소비자 유지 / 전송 취소·늦은 응답 폐기 확인 |
| owner 구독 잔여 | 반복3회 조립/해제 후0 증가 |
| 피드 표시 창 | 기존200행 상한 유지, 새 cursor를 기준으로 append |
| 인입 재진입 | runner 지역 상태만 → owner별 job ID 복원, 원문 복제0 |
| Phase 3 선택 단위 검사 | 3,557 pass / 0 fail / 50 skip |
| Phase 4 최초 선택 검사 | 3,562 pass / 1 fail / 50 skip, 수정 후 관련16/16 pass |
| Phase 5 최초 선택 검사 | 3,546 pass / 1 fail / 48 skip, 공용 관찰6/6·좁은 재검증9/9 pass |
| 최종 조립/경합 검사 | 19/19 pass |
| 최종 선택 단위 검사 | 3,548 pass / 0 fail / 48 skip, 176.27초. excluded와 미실행은 통과에 넣지 않음 |
| 정적·빌드 | static·unused·local build·artifact 검사 성공 |
| 문서 검사 | 약관4버전·navigation321경로·build·artifact31파일 검사 성공 |
| 필수 precheck | a0fd30f2 / release 5b16a162, local-1789023621662-30588, 4분4초. static·1,926+1,583 단위 통과, 실패0, 조건부 skip60 |
| release Merge Check | 실행34448266204 성공(잡1분22초), release merge daca15d9. full 실행 아님 |
| 운영·긴 이력·시각 품질 | Production 응답시간/모바일 프레임/실제 제공자·인입 end-to-end 미측정 |
6. 이번 개선으로 향상된 것
화면 이동과 확정 저장이 같은 응답 적용 규칙을 사용한다
사용자A의 오래된 응답을 사용자B나 재로그인한A에게 표시하지 않고, 댓글·좋아요의 확정 결과를 먼저 시작한 읽기가 되돌리지 못한다. 이 규칙은 feature마다 별도 순번을 손으로 맞추는 대신 resource/owner 경계와 행동 검사로 유지한다.
원문과 진행 표시의 책임을 구분한다
인입 화면은 작업 ID로 진행 상황을 다시 볼 수 있고, 취소 표시가 서버 처리 중단을 뜻하지 않는다. 원문과 배치 처리는 기존 I01/D09의 책임이다.
남은 것
필수 precheck는 후속 지시에 따라 자기 브랜치에서 필요한 승인 정책을 반영하여 통과했고, Merge Check 성공과 실제 release 병합까지 확인했다. 현재 요청에 남은 구현·precheck·release 병합 작업은 없다. A16/UI에는 AnyRecord·snake case 행 mapper·props 투영을, A15/R01에는 셸 호환 투영 제거 조건을 인계한다. R04의 실제 긴 이력·성능 측정과 Production 배포는 이 트랙의 검증으로 완료 처리하지 않는다.