크라우니캔버스 — 컬렉션.한선 O(n²) P0 수리
개요
컬렉션.한선(F4 컬렉션/DB 엔진)이 저장소.한선과 동일한 O(n²) 안티패턴을 "의도적 복제"
주석을 단 채 보유하고 있어 P0로 지목됨. 저장소.한선이 이미 받은 수리 패턴(모듈전역
1행 필드캐시 + 해시맵 유니크)을 동일하게 적용하고, 실측 결과 그것만으로는 목표(200건
<300ms)에 못 미쳐 근본원인 2(글자()/부분() 매호출 O(len) 재스캔에 기반한 PSV파싱)까지
추가로 수리했다.
무엇을 했는지
- 컬렉션_필드(): 매 접근마다
분리(행,"|") 재호출하던 것을 저장소_필드()와 동일한
모듈전역 1행 캐시(
_C_필드캐시행/
_C_필드캐시부분들)로 교체 — 같은 행 문자열 반복
조회 시 재분할 생략.
- 컬렉션_유니크ID목록(): growing-string 방문표 +
포함하나() 선형탐색 누적(O(n²))을
해시맵(
맵생성/
맵있나/
맵넣어, O(1))으로 교체 — 저장소_유니크ID목록과 동일 관용구.
(현재 호출부는 없으나 "동일 사상" 유지 원칙상 함께 수리)
- 컬렉션_PSV파싱() (근본원인 2, 위 두 수리만으론 부하테스트 6.28초 관찰 후 추가 발견):
글자(내용,i)를 파일 전체에 대해 문자 단위로 순회하는 구조라, 글자()/부분()이
"매호출 O(len) 전체 재스캔"인 VM 특성상 전체가 O(파일크기²)로 열화(저장소.한선이
이미 진단·수리한 근본원인과 동일). 저장소_PSV파싱과 동일한 네이티브 버퍼(BUF_*,
O(1) 오프셋) 기반으로 교체. 버퍼 핸들은 함수 내부에서만 생성·해제되고 반환값은
순수 문자열 배열이라, 이 파일에 이미 문서화된 "조건식 내 버퍼함수 인라인 평가뒤집힘"
함정(2026-07-24 이전 시도 철회 사유)과는 무관함을 확인 후 적용.
검증
CROWNY_STRICT=1 hanseonc_high 컬렉션.한선 → 0 경고, 컴파일 성공.
CANVAS_COLLECTION_SELFTEST=1 자체테스트 재실행 → 7/7 PASS, 0 FAIL (스키마/속성순서/
레코드생성/필터/정렬/합계/뷰적용/바인딩 전부 기존과 동일 결과값 일치).
200건 레코드 생성 후 조회 실측(격리 프로세스, hanseonc_high 독립 실행):
수리 전(1·2만 적용, PSV파싱 미수리): 목록+필터+정렬+합계 합산 6.28초 — 목표 미달.
PSV파싱까지 수리 후: 단일 뷰적용(필터+정렬 결합, 실제 API 사용 패턴과 동일) 245ms
(VM기동 baseline ~14ms 포함) —
<300ms 목표 충족. 개별 연산 목록/필터/합계는
각 104~116ms, 정렬(내부에서 목록 재조회 포함)은 320ms로 개별 호출 기준 다소 여유
없음 — 추후 서버.한선 API 핸들러가 여러 연산을 한 요청에서 연쇄 호출하는 경우
맵빌드 결과를 요청 스코프에서 재사용하도록 추가 최적화 여지 있음(현재는 함수별
독립 빌드, F4 계약상 뷰적용 경로가 실제 API 소비 형태이므로 1차 목표는 달성).
- 서버.한선(컬렉션.한선 가져옴) 재컴파일 → 0 경고 → `launchctl kickstart -k
gui/$(id -u)/org.crowny.canvas
재기동 → GET /
200, GET /api/부팅` 정상 응답 라이브 확인.
관련 파일
/Users/ef/crowny-canvas/한선씨/컬렉션.한선 (수리 대상, 소유 파일)
/Users/ef/crowny-canvas/한선씨/저장소.한선 (수리 패턴 원본 참조)
/Users/ef/crowny-canvas/서버.토au (재컴파일 산출물)
잔여 이슈
- 여러 컬렉션 연산(목록+필터+정렬+합계)을 한 요청에서 순차 호출하는 상위 API 패턴이
생기면, 요청 스코프 단위 맵빌드 캐시(1회 빌드 공유)로 추가 절감 여지 있음 — 현재는
각 공개 함수가 독립적으로 1회씩 파일을 읽음(O(n)이므로 안전하나 호출 누적 시 선형 가산).
- 컬렉션_JSON값/컬렉션_문자열비교 등은 레코드당 값 문자열이 짧아 현재 규모에선 문제
없으나, 속성값이 매우 길어지는 경우 별도 실측 필요.