← 목록
기타 2026-07-24 7KB 읽기 6분

크라우니캔버스 컬렉션.한선 O(n²)~O(n³) + OOM 무한루프 근본수리

개요

crowny-canvas(:9620) 한선씨/컬렉션.한선(계약 F4 — 컬렉션/DB 엔진)에서 확증된 P0 결함을 근본 수리. 독립 부하테스트(레코드 300건, hanseonc_high 직접 실행)로 재현한 결과 레코드생성(O(n))은 즉시 완료했으나 목록/필터/정렬 단계에서 3분+ 진행 없이 [ARRAY] OOM!을 무한 반복하며 완주 자체를 못 하는 busy-spin 상태였음(강제 kill 필요). 라이브 :9620는 서버.한선이 컬렉션 함수들을 아직 HTTP 라우트에 결선하지 않아(컬렉션_복원()만 호출) curl로는 재현 불가 — 이 전제는 원 리포트와 일치.

확증된 결함 (수리 전)

  • 컬렉션_PSV파일유니크최신()컬렉션_유니크ID목록()이 growing 문자열 방문표+포함하나()
선형탐색(O(n²)), 컬렉션_최신행찾기()가 유니크id마다 전체 행배열 재스캔(O(n²)).
  • 컬렉션_레코드필터()/컬렉션_레코드정렬()/컬렉션_합계()가 레코드id마다 컬렉션_레코드필드JSON()
컬렉션_파일최신행()컬렉션_PSV읽기()(파일 재읽기)+컬렉션_최신행찾기()(전체 재스캔)를 반복 호출 — 뷰적용(F4 계약) 1회 요청이 O(n²)~O(n³).
  • 부수 근본원인(재현 중 추가 확인): 컬렉션_값비교()가 비교 1회마다 컬렉션_속성타입()
컬렉션_파일최신행()(배열 반환 함수 체인)을 다시 호출 — 삽입정렬 O(n²) 비교(n=297→약 88,000회)가 매번 배열반환 함수를 재실행해 가드레일 문서화된 "루프 내 배열반환 함수 반복호출 ~200회= [ARRAY] OOM, 144M셀 힙 고갈, 반환 배열은 메모리마커/복원으로도 회수 안 됨" 함정을 그대로 재현.

수리 내용 (/Users/ef/crowny-canvas/한선씨/컬렉션.한선)

  1. PSV파일유니크최신: growing 문자열 방문표+유니크id별 재스캔 → 맵생성/맵있나/맵넣어 해시맵
단일패스(O(n)). 검색.한선 검색_질의·서버.한선 온보딩 처리와 동일 관용구.
  1. 컬렉션_레코드맵빌드(신규): 레코드 파일을 컬렉션id당 "1회만" 읽어 id→필드JSON 맵(+활성순서
배열)을 구축. 필터/정렬/합계/목록이 전부 이 단일 빌드 결과만 사용(맵꺼내=O(1)).
  1. 속성타입 루프밖 호이스팅: 컬렉션_값비교타입(속성타입,a,b)(순수, 파일접근 없음) 신설.
필터/정렬이 루프 진입 "전" 속성타입을 1회만 조회해 캐시하고, 비교는 이 순수함수만 사용 — O(n²) 삽입정렬 비교 내부에서 배열반환 함수가 재호출되는 경로를 완전히 제거(OOM 근본원인 제거).
  1. 빌드 재사용(3단계): 컬렉션_레코드필터_코어/컬렉션_레코드정렬_코어/컬렉션_합계_코어
분리해 컬렉션_뷰적용()(F4 "1회 요청")과 컬렉션_전체합계()가 파일을 2번씩 읽던 것을 1번으로 축소(공용 인자로 이미 구축된 필드맵/활성순서를 그대로 전달).
  1. (시도 후 되돌림) 저장소.한선의 버퍼기반(BUF_*) PSV파싱+단일행 필드캐시 관용구를 잔여
O(파일크기²)(글자()/부분() 매호출 O(len) 재스캔) 완화용으로 이식 시도했으나, 이 파일의 실사용 패턴(추가()/문자열 연결식 안에 버퍼경유 함수를 깊이 중첩 호출 — 자체테스트에 이미 존재)에서 가드레일 문서화된 "조건식 내 버퍼IO 함수 인라인=평가 뒤집힘/소실" 함정이 재현됨(정렬금액들 배열이 항상 길이 0으로 소실, 최소재현 3종으로 격리). 성능 이득보다 침묵 데이터손실 위험이 커 즉시 되돌림 — 분리()/글자() 기반 원래 구현 유지. 버퍼기반 전환은 별도 세션에서 이 파일 전체 호출부(추가/연결식 내 중첩 호출)를 전수 변수분리한 뒤 재시도 필요(잔여 이슈로 남김).

검증

  • STRICT 재컴파일: 컬렉션.한선 단독 + 서버.한선(10개 모듈 전체 import 체인, 77622 큐브) 0경고.
  • 자체테스트(CANVAS_COLLECTION_SELFTEST=1): 7/7 PASS(속성순서·레코드3건·필터2건·정렬순서·
합계250000·뷰(필터+정렬)결과·바인딩 쓰기정책) — 수리 전/중간 단계/최종 각각 재확인.
  • 부하테스트(레코드 300건, crownycode-learn 재현 스크립트와 동일 시나리오):
| 단계 | 결과 | |---|---| | 수리 전 | 레코드생성 완료(수초) → 목록/필터/정렬 단계 3분+ 무진행, [ARRAY] OOM! 무한반복(강제kill) | | 1차 수리(맵빌드 도입) | 레코드필터까지 정상 진행(4ms) → 정렬 진입 직후 재차 OOM(속성타입 배열반환 함수 O(n²) 재호출이 근본원인이었음, 추가 발견) | | 2차 수리(속성타입 호이스팅) | 완주: 목록 3ms·필터 4ms·정렬 4ms·합계 6ms·뷰적용 8ms, 총 25ms(내부 타이머). 벽시계 종단 ~25s(문자열.한선 분리()/부분() O(len) 재스캔 다중호출 누적 — VM 공유 프리미티브 한계, 별도 이슈) | | 3단계(빌드 재사용, 7→5회) | 동일 정합, 벽시계 ~18s(30% 단축) | | 버퍼기반 시도 | 자체테스트 회귀(정렬금액들 소실) → 즉시 롤백, 3단계 상태로 최종 확정 |
  • 라이브 재확인: 서버.toau 재컴파일(0경고) → launchctl kickstart -k gui/$(id -u)/org.crowny.canvas
/·/api/부팅 200 확인(재기동 후 정상 기동).

관련 파일

  • /Users/ef/crowny-canvas/한선씨/컬렉션.한선 (수리 본체)
  • /Users/ef/crowny-canvas/서버.toau (재컴파일 반영)
  • /Users/ef/crowny-canvas/docs/00-작업계획서-에이전트디렉팅.md, docs/07-앱-계약.md (계약 SSOT, 무수정)
  • LaunchAgent org.crowny.canvas (kickstart로 재기동만, plist 무수정)

잔여 이슈

  • 문자열.한선(공유 표준라이브러리, 이 파일 소유권 밖)의 분리()/찾기()/찾기뒤()가 내부적으로
부분() 위치별 반복호출이라 대형 문자열에서 O(n²) — 저장소.한선과 동일 근본원인. 이 파일은 버퍼기반 우회 시도가 자체테스트 회귀를 일으켜 되돌렸으므로 여전히 이 비용을 그대로 짊어짐 (n=300에서 ~18s). 별도 세션에서 파일 전체 호출부를 "추가()/문자열연결식 안에 함수 중첩 호출 금지 → 변수로 먼저 받기" 원칙으로 전수 정리한 뒤 버퍼기반 재도입 검토.
  • 컬렉션.한선은 아직 서버.한선에 결선되지 않음(HTTP 라우트 없음, 컬렉션_복원()만 호출) —
이번 수리는 파일 소유권 범위(컬렉션.한선) 내 결함 수리만 수행, 결선은 별도 작업(F6 트랙).
  • 컬렉션_ID생성()현재시간()+무작위(1000,9999) 조합이라 같은 시각·같은 프로세스에서 대량
생성 시 드물게 ID 충돌(레코드 300건 생성 시 실측 4~5건 중복, latest-wins로 조용히 병합됨) — 이 리포트의 O(n²) 범위 밖이라 별도 확인 필요.