crownyc 트윈 마이그레이션 마스터플랜 — 맵 729캡 근본해결
2026-06-11 · 아키텍트 종합 · 분석·계획 문서 (라이브 서비스/공유 crownyc 바이너리 무변경)
검증 기반: /Users/ef/CrownyOS/crownyc/crownyc.c 직접 독해 (5868~5929, 9066~9087, 2691~2716, 1567~1646, 1583~1619)1. 문제·목표
문제 (소스 검증 완료)
crownyc VM의 해시맵은 고정 729엔트리 open-addressing 선형프로빙 평면배열이다.
| 위치 | 사실 | 결과 |
|---|---|---|
crownyc.c:5875 | 맵생성(op412) heap_ptr -= 1458 (729 키-값쌍 고정) | 모든 맵이 정확히 729 슬롯 |
crownyc.c:5889 맵넣어(op413) | for(hp<729){ hs=(hs0+hp)%729; hka=va-hs*2 } | 729 다 차면 루프 종료 → silent drop (체이닝/오버플로 없음) |
crownyc.c:5894 | 빈슬롯(hek==0) 만나야 삽입 | 729 충돌 시 신규 키 조용히 유실 |
crownyc.c:5914 맵꺼내(op414) | 동일 729 프로빙, 못 찾으면 -1 | 유실 키 영구 조회 불가 |
crownyc.c:9069·9074 맵키목록(op852) | 결과배열 1024 고정 + hk_count<1023 + 729 스캔상한 | >1023키는 추가 truncation (프로빙과 독립적 2차 손실) |
crownyc.c:2701·2715 trit_hash_ | 6트릿 접기 → 정확히 0..728 (3⁶) 반환 | 729 캡의 수학적 근원, ISA729 729진 아키텍처 결속 |
invite-snap.p0~p11.dat 12-파티션 샤딩으로 캡을 수동 우회하고 있다 — 근본해결 아님.목표
- 729캡 근본해결: 단일 맵이 용량을 자연증식(rehash-grow)하도록 VM 수술. 샤딩 같은 애플리케이션-레벨 우회 제거.
- 라이브 무중단: 33개 라이브 서비스를 멈추지 않고 점진 컷오버. 블라스트 반경을 카나리 1서비스로 제한.
- 후방호환 (3중 보장): 기존 toau 바이트코드 재컴파일 없이 new VM에서 동일 동작. 기존 .dat 무변환 로드.
- 원자성·롤백:
mvrename 원자 스왑 + 구 바이너리 백업으로 즉시 복귀.
2. crownyc-next 설계 — 권장 접근 확정
확정 접근: "sized-at-creation + 동적 rehash-grow 하이브리드, open-addressing 유지"
체이닝(linked overflow)은 명시적으로 기각한다.
기각 근거 (체이닝): 현 구조는 맵핸들=주소, 슬롯이 핸들에서 va-hs*2로 아래로 펼쳐지는 순수 평면배열이다.
체이닝은 큐브 안에 next-포인터를 트릿 패킹해야 하고 → GC 힙스캔/문자열 remap/맵키목록/디스크 .dat 직렬화가
전부 포인터-추적으로 바뀐다. 수술 표면 폭발 + 후방호환 깨짐. open-addressing 유지 시 데이터 레이아웃
(키/값 인접쌍)이 보존돼 후방호환이 깨끗하다.
확정 접근 메커니즘:
- 용량 헤더 블록: 맵생성(op412)이 선택적 용량 인자(트라이트)를 받아
cap=올림(3^k)(기본 729) 슬롯 할당,
- cap-인지 프로빙: 맵넣어/맵꺼내/맵키목록의
729하드코딩 상수를 메타슬롯 cap 읽기로 치환. - rehash-grow (자연증식): 맵넣어 삽입 후 부하율 >
cap*2/3초과 시 →cap'=cap*3새 블록 할당,
m = 맵넣어(m,k,v) 핸들-반환 패턴 존재 (5902).
- 해시 cap-투영: cap=729일 땐 현 trit_hash 분포 정확히 보존(0..728이 cap에 단순 포함). cap>729일 땐
(hs0*상수)%cap 재투영. cap=729 폴백 시 분포 무변경이 후방호환 핵심.heap 완화 (★2026-06-11 적대검증·소스재확인으로 정정★)
KILL-SHOT 정정: 초안의 "mem_ensure realloc로 MEM_DEFAULT→MEM_HARD_LIMIT 12M 여유" 주장은 거짓이다.crownyc.c:1567-1568MEM_DEFAULT == MEM_HARD_LIMIT == 12000000(동일 상수). 1576에서 부팅 시 calloc(12M) 즉시 할당,MEM_MAX=MEM_DEFAULT. mem_ensure(1610)는n>MEM_MAX && n<=MEM_HARD_LIMIT일 때만 grow하나 둘이 같으므로 항상 거짓=성장 불능(dead code). 아레나는 부팅부터 12M 고정, 헤드룸 0. rehash-grow는 이 고정 아레나 안에서만 해야 한다.
- map-of-maps 현실 (셀코어 새셀 56줄 = 맵생성): finance 초대DB는 맵의 맵이다. 셀 1개 = 자체 1458슬롯 내부맵.
- 그래서 grow는 free-list 회수가 필수(선택 아님): grow 시 옛 블록을 반드시 회수해야 12M 안에서 순증식 가능.
FREE(229)는 최상단-복원만 지원(free-list/워터마크 없음 — 소스 확인). → free-list 신규 구현이 W6의 핵심 난도.
- 대안 트랙(병행 검토): ①MEM_HARD_LIMIT 상향(예 24M, calloc 두 배 = ~648MB RSS, 별도 메모리/회귀 영향) — 가장 단순하나 전 서비스 RSS 증가.
- GC 압력:
free_strings힙스캔이heap_ptr..MEM_MAX2패스 → 큰 맵이 heap_ptr를 끌어내려 스캔범위 확대. 워터마크 캐싱으로 완화(여전히 유효). - 가드 일반화: 5870
heap_ptr-1458<mem_count를heap_ptr - cap*2 < mem_count로. grow 경로에도 동일 가드. - 상한 정책: cap 최대 3⁹은 12M 고정 아레나에선 비현실(13122슬롯×다수=고갈). 실효 상한은 heap 잔량 기반 동적으로 재산정 필요.
3. 트윈 아키텍처
[라이브 — 무변경] [트윈/섀도 — 신규]
crownyc (현 바이너리) crownyc-next (별도 경로, 동적맵)
├ /tmp/*.toau (그대로) dual-run ├ /Users/ef/CrownyOS/crownyc/build/crownyc-next
└ /Users/ef/crowny-*/data/*.dat ──복사──→ └ /tmp/twin-{svc}/data/*.dat (복사본, 공유 아님)
:라이브포트 :섀도포트 (라이브 + 10000 오프셋)
- 별도 바이너리 경로:
/Users/ef/CrownyOS/crownyc/build/crownyc-next(라이브crownyc절대 안 건드림). - 섀도 포트: 라이브포트 + 10000 (예: finance 9750 → 트윈 19750). gateway.yaml에 등록 안 함 (SSOT 오염 방지),
crowny-ports.sh check 19750로 사전 확인.
- 상태 .dat 복사 (공유 금지): 라이브 .dat를 트윈 디렉토리로 복사. 라이브 WAL에 트윈이 쓰지 않게 격리.
4. 단계별 컷오버 게이트
| 게이트 | 작업 | 통과 기준 |
|---|---|---|
| G0 빌드 | crownyc-next 컴파일 (cc -O2 -o build/crownyc-next crownyc.c) | 무경고 빌드 + 기존 회귀 테스트 통과 + cap=0인자(기본) 맵 동작 = 구버전 비트동일 |
| G1 트윈검증 | finance .dat 복사 → 트윈 로드 → 전 키 맵꺼내 diff + 삽입순서 재현 패리티 | 729 미만 구간 100% 일치. ★open-addressing 레이아웃은 삽입이력 의존(GC compaction이 재정렬)이므로 단순 키 readback diff 불충분 → 동일 삽입순서 replay 후 맵키목록 순서·맵꺼내 동치 검증. heap_ptr 최저치·free_strings 스캔큐브수 회귀 없음 |
| G2 카나리 (1서비스) | finance 단일 서비스만 crownyc-next로 라이브 전환 (게이트웨이 조율) | 24h 무장애. 신규 초대 silent drop 0건. 응답 패리티 유지. 롤백 미발동 |
| G3 클러스터 | Finance 클러스터 순차 (bank→auth→dex→wallet→chain). 연동그래프 순서 준수 | 클러스터 내부 호출 체인 정상. 상태 정합 (WAL replay 순서 보존) |
| G4 전함대 | 위험高 잔여(church/gateway/enterprise/aimed) → 나머지 28서비스 | 33서비스 전체 crownyc-next. 7일 안정화. 12M heap 한도 여유 확인 |
5. 롤백
- 백업: 컷오버 직전
cp crownyc crownyc.$(date +%s).bak(구 바이너리 보존, 롤백 원본). - 원자 스왑:
mv build/crownyc-next crownyc(cp 덮어쓰기 금지 — 실행중 바이너리 cp는 손상. mv = 원자 rename). - 즉시 복귀:
mv crownyc.{ts}.bak crownyc+ 대상 서비스 재시작. 게이트웨이는 SIGHUP rolling restart. - 롤백 트리거 (자동 발동 기준):
[MAP] 힙 부족) 또는 OOM → 즉시.
3. 카나리 24h 내 장애 1건 이상 → 게이트 미통과, 롤백.
4. WAL replay 정합 실패 (상태 손상) → 즉시 (.dat 복사본이라 라이브 원본은 안전).★롤백 경계 (point-of-no-return) — 적대검증 정정★: 롤백이 비트-클린한 것은 모든 맵이 ≤729(구 실효 cap)인 동안만이다. new-VM이 730번째 키를 받는 순간(=근본해결이 실제 발동) 그 상태는 구 VM으로 표현 불가 → 그 시점 이후 구 바이너리로 롤백하면 >729 엔트리 유실. 즉 카나리 통과 후 실데이터가 729를 넘기 시작하면 그 맵은 사실상 비가역. 운영 정책: ①카나리 기간은 인위적으로 729 미만 유지하며 후방호환만 검증, ②"근본해결 발동(>729 허용)"은 별도 명시 게이트로 되돌릴 수 없음을 선언하고 진입. .dat 복사본은 라이브 원본을 지키지만, 컷오버된 라이브가 >729를 쓰면 그 라이브 상태 자체가 구 VM 비호환이 됨.
6. 위험 레지스터
| # | 위험 | 블라스트반경 | 완화 | |
|---|---|---|---|---|
| R1 | 후방호환 깨짐 (cap=729 폴백 시 해시분포 변화 → 기존 .dat 키 미스) | 전 서비스 | cap=729일 때 trit_hash 분포 비트동일 강제. G0에서 구버전 비트동일 검증. G1 diff 100% 일치 게이트 | |
| R2 | 상태 정합 (WAL replay 순서/메타카운터 불일치) | finance/chain/bank/reward | .dat 복사(공유 금지)로 라이브 원본 격리. meta 카운터(count\ | partition) 매칭 검증. replay 순서 보존 |
| R3 | heap 고갈 (동적맵 누적 + GC 스캔범위 확대) | 메신저/웹 등 문자열多 서버 | mem_ensure realloc 연동. cap 상한 3⁹. free-list 단편회수. GC 워터마크 캐싱. G1에서 heap_ptr 계측 | |
| R4 | 블라스트 반경 (gateway=reverse proxy 허브, 전 트래픽 경유) | 전 함대 | gateway는 G4 후반. 카나리=finance(독립성 높음). 섀도포트로 라이브 무영향 검증 선행 | |
| R5 | 세션 조율 (라이브 프로세스/게이트웨이 스왑은 본 세션 영역 밖) | — | 계획·트윈·패리티까지만 본 세션. 라이브 바이너리 스왑·재시작은 인프라/게이트웨이 세션 사인오프 필수 | |
| R6 | 맵키목록 2차 truncation (>1023키는 프로빙과 독립적으로 잘림) | 큰 맵 보유 서비스 | op852 결과배열 1024→cap+1 동적화. cap 인지 스캔. 이건 grow와 별개 수술 사이트 | |
| R7 | 단편화 누적 (grow 옛블록 회수 불가 시 heap 잠식) | 장기 운영 | free-list 등록. FREE(229) 최상단-복원 시맨틱과 정합. 장기 누적 모니터링 |
7. 타당성 Verdict (정직)
소화 가능 (Opus+울트라 범위):
- VM 수술 자체 (8개 사이트: op412/413/414/852 + trit_hash + heap_ptr/ALLOC/FREE + GC + MEM가드). 국소적·잘 정의됨.
- 후방호환 additive 설계 (cap 메타슬롯 + 0인자 폴백). 데이터 레이아웃 불변이라 디스패치 arity 무변경.
- 트윈 빌드·섀도포트·dual-run 패리티 하니스. 전부 읽기/측정/별도경로 — 라이브 무영향.
- G0~G1 (빌드+트윈검증)은 본 세션 단독 완수 가능.
- G2~G4 라이브 컷오버: 실행중 공유 crownyc 바이너리 스왑 + 33서비스 재시작 = 인프라/게이트웨이 세션 영역 (R5).
- gateway 재구성: SIGHUP rolling restart, trident/health 캐시 복구는 gateway 세션 담당.
- 각 도메인 서비스 검증: finance/church/enterprise의 비즈니스 로직 정합은 해당 도메인 세션이 확인.
8. 작업 분해 (WBS) — 다음 실행 단위
| ID | 작업 | 의존 | 세션 |
|---|---|---|---|
| W1 | crownyc.c 복사 → 수술용 트리 (build/ 격리). 라이브 무변경 | — | 본 |
| W2 | op412 맵생성: 선택적 cap 인자 + 메타슬롯(va+1) + cap2 할당 + 가드 일반화 | W1 | 본 |
| W3 | op413/414 맵넣어/맵꺼내: 729→cap 메타읽기 치환 + 부하율 rehash-grow | W2 | 본 |
| W4 | op852 맵키목록: 결과배열 1024→cap+1 동적화 + cap 스캔 | W2 | 본 |
| W5 | trit_hash cap-투영 (cap=729 비트동일 보존, cap>729 k트릿 확장) | W2 | 본 |
| W6 | heap: mem_ensure grow 연동 + free-list 단편회수 + GC 워터마크 + cap 상한 3⁹ | W3 | 본 |
| W7 | G0 빌드 + 회귀: cap=0(기본) 맵 구버전 비트동일 검증 하니스 | W2~W6 | 본 |
| W8 | 동일 로직 .한선 동반 (맵grow 셀코어 룰) + 패턴 학습 등록 | W7 | 본 |
| W9 | 트윈 하니스: finance .dat 복사 → 섀도포트 dual-run → diff 도구 (한선씨) | W7 | 본 |
| W10 | G1 패리티 검증 + heap_ptr/GC 계측 리포트 | W9 | 본 |
| W11 | G2 카나리 컷오버 (finance) — 라이브 mv 스왑 | W10 | 인프라/게이트웨이 조율 |
| W12 | G3 클러스터 (Finance 순차) | W11 | 인프라 조율 |
| W13 | G4 전함대 + gateway 마지막 | W12 | 게이트웨이 세션 |
9. 실행 로그 — W1·W2·G0 완료 (2026-06-11)
crownyc.c → build/crownyc-next.c 복사. 라이브 crownyc/crownyc.c 무접촉(확인: 라이브 바이너리 size/mtime 불변, finance 라이브 health ok).mslots=cap*2+1, 헤더에 cap 기록.729→헤더 cap 읽기, base=va-1 프로빙.729→cap 스캔, base=va-1.mcap<1||>MEM_MAX→729. cap=729일 때 %729·관측동작 불변(0인자=후방호환).cc -O2 -I.. -o build/crownyc-next build/crownyc-next.c -lm -lpthread -framework Security -framework CoreFoundation (라이브 링크와 동일, 확인). exit 0.build/crownyc-next(격리), 라이브 무영향.다음 — W3 (난도 핵심)
rehash-grow(부하율>cap2/3 → cap3 재해시) + free-list 단편회수(고정 12M 아레나,FREE는 top-of-heap만 → 신규 구현 필요). 이게 근본해결의 실난도. 그 후 W4(keylist>1023)·W5(hash cap-투영)·W6(heap)·G1(finance .dat 트윈 패리티, 삽입순서 replay).10. W3 완료 — rehash-grow + forwarding + free-list (2026-06-11, 격리)
울트라 워크플로우(설계2→구현→적대검증3). build/crownyc-next.c 단독, 라이브 무접촉.
- 블록 레이아웃:
slots=cap*2+2(헤더 va=cap + 엔트리 2
3*newcount>2*mcap(>2/3) → cap'=cap*3(729→2187→6561…) 새 블록 재해시. cap=729·≤486 구간은 G0과 100% 동일(grow 미발동).map_resolve(va)가 모든 맵 op 진입부에서 체인 follow(guard 64).검증 (독립 재확인)
- 빌드 exit 0(608768B). G0 cap=729 비트동일 유지.
- 중첩-grow crux: 외부맵에 내부맵 저장→내부맵 1500키 grow(재배치)→외부 경유 옛핸들 조회 k1=1,k1500=1500,합=1,125,750(정확). 라이브는 729서 유실(합=265314). forwarding 자가치유 입증.
- T1 단일2500키 0유실 / T3 공유alias 양방향 / T4 경계(486/487/729/730/1457/2187/2188) / finance .dat 부팅·로드 무손상.
- 라이브 crownyc(608520,01:18)·crownyc.c·:9750 무접촉.
잔여(정직)
- map-GC 부재 → grow-discard 누적 완전bound 아님(grow 기하적이라 실용 OK, 향후 map-GC 정제).
- 다음: G1(finance .dat 트윈 패리티, 삽입순서 replay) → G2~ 라이브 컷오버(인프라/게이트웨이 사인오프 + 롤백 경계 수용).
11. G1 완료 — finance 트윈 A/B 패리티 (2026-06-11, 라이브 무접촉)
격리 트윈: 라이브 소스 sed(포트 9750→19750, data→/tmp/twin-finance/data), hanseonc_high 컴파일(VM-무관 toau), 같은 toau를 라이브 crownyc vs build/crownyc-next에 각각 fresh .dat 복사로 기동·동일요청·diff. 라이브 crownyc/finance/data 무접촉 확인.
결과
- ✅ 729-미만 패리티 = 완전 비트동일: 실제 finance 상태(162 연락처=중첩 map-of-maps) 기준 boot replay·sync·join·rewards·feed·stats·shareholder·멱등rejoin 18줄/2244바이트 동일. 729 미만 구간에서 crownyc-next는 완벽한 drop-in.
- ✅ 캡 초과 = 결정적·완전 (정밀 재측정): 빈 상태 1000 고유연락처 + 재시도/페이싱 하니스 → next: registered 1000/1000, retrievable 1000/1000, 손실 0 vs live: retrievable 242, 손실 758. crownyc-next는 finance 1000 연락처에서 무손실.
- ⚠️→정정: "2차 한계"는 측정 아티팩트였음: 초안의 817/880·"finance 2차 한계"는 단일스레드 raw 한선씨 백엔드에 1000요청 rapid-fire 시 HTTP 전송 드롭이 손실로 오집계된 것. 재시도+페이싱 하니스로 재측정하니 next 1000/1000 완전. → 측정 파이프라인을 먼저 의심하라([[feedback_test_matrix_traps]]). finance 자체엔 (이 스케일에선) VM 캡 외 추가 손실 한계 없음.
컷오버 함의 (정직)
- G2 카나리는 729-미만에서 후방호환 검증(perfect parity 입증). 롤백 경계와 정합.
- ">729 근본해결 발동"은 VM 수술만으로 finance 1000 연락처 무손실 입증됨(2차 감사 불요). 단 더 큰 스케일(수천+)의 heap/map-GC 한계는 미측정(디스크 100% ENOSPC로 3000 테스트 중단) — 향후 디스크 확보 후 ceiling 측정.
- map-GC(W3 잔여) [MAP] 힙 부족 미발생(1000 무크래시) — 당장 차단요인 아님.