본문으로 건너뛰기

엔진

엔진 선택하기

세 엔진은 모두 같은 형식, 즉 UUIDv7 ID와 데이터 배열을 저장하지만 디스크에 배치하는 방식은 서로 다릅니다. 이 배치 방식에 따라 각 엔진의 적합한 용도가 결정됩니다:

DocStore SegmentedLog Queue
디스크 저장 방식 레코드마다 JSONC 파일 하나, UUID 꼬리 기준 샤딩 길이와 체크섬 접두부가 붙은 JSON 줄로 구성된 추가 전용 세그먼트와 매니페스트 추가 전용 이벤트 로그 하나
쓰기 레코드별 원자적 파일 교체 체크섬이 있는 추가 쓰기. 덮어쓰기와 삭제는 새 항목을 추가하며, compact()에서 회수됩니다 큐 이벤트마다 체크섬이 있는 추가 쓰기
읽기 ID로 get() 조회, 인덱스 쿼리 커서 및 시간 범위 스캔, 상태 인덱스를 통한 get() enqueue 순서대로 claim()
적합한 용도 컬렉션 크기에 관계없이 개별 조회 및 인덱스 조회 약 1M개 레코드까지 추가 위주 스트림 프로세스 간 내구성 있는 작업 전달
취약한 용도 약 10k개가 넘는 레코드의 비인덱스 필드 스캔 잦은 단일 레코드 재작성(압축 전까지 이전 버전이 공간을 차지함) 범용 레코드 저장소로는 부적합하며 작업 완료 후 페이로드가 삭제됨

DocStore과 SegmentedLog은 같은 유창한 쿼리 API를 공유합니다. 같은 where() 체인을 두 엔진에서 실행할 수 있으며 실행 방식만 다릅니다. DocStore은 보조 인덱스에서 결과를 가져올 수 있고, SegmentedLog은 세그먼트를 스캔하며 필터링합니다. Queue은 쿼리할 수 없고 클레임 기반으로 동작합니다. 작업자는 작업을 필터링하는 대신 다음 작업을 claim()합니다.

일반적인 선택 기준:

  • 개별적으로 가져오고, 업데이트하거나 삭제하며 필드로 조회하는 레코드: DocStore. 필터링할 필드에 인덱스를 추가하세요.
  • 주로 추가하고 순서대로 또는 시간 범위별로 읽는 이벤트나 버전: SegmentedLog.
  • 생산자가 작업자 프로세스에 전달하는 작업: Queue. 클레임과 완료는 여러 프로세스에서 안전하며, claimMany()/completeMany()으로 잠금과 fsync 비용을 분산할 수 있습니다.
  • 모든 컬렉션을 대상으로 하는 조인, 검색 또는 보고: SQL 미러를 추가하세요. 파일이 기준 데이터로 유지되고, 미러는 SQLite 또는 MySQL에서 다시 빌드할 수 있는 프로젝션입니다.

엔진은 함께 사용할 수 있습니다. 루트 디렉터리만 공유하므로 한 애플리케이션이 같은 저장소 루트 아래에서 콘텐츠는 DocStore에, 감사 추적은 SegmentedLog에, 백그라운드 작업은 Queue에 저장할 수 있습니다.

DocStore

DocStore은 내구성 있는 레코드를 개별 조회하거나 간단히 스캔하는 데 적합합니다. 레코드마다 JSONC 파일 하나를 씁니다:

PHP
$store = new Storh\DocStore($root, 'articles');
$record = $store->put(['slug' => 'hello', 'published' => true]);
$same = $store->get($record->id());

레코드 파일은 UUID 끝 문자 기준으로 샤딩되므로 UUIDv7 쓰기 급증이 디렉터리 여러 곳에 분산됩니다:

Text
articles/data/2d/018bcfe5-6800-7000-8000-2d5dba6274da.jsonc

SegmentedLog

SegmentedLog은 추가 위주 레코드, 커서 읽기 및 시간 범위 읽기에 적합합니다. 레코드는 활성 세그먼트에 레코드마다 한 줄씩 <byte-length>\t<xxh32>\t<json> 형식으로 기록됩니다(원자성 참조). 세그먼트 파일의 확장자는 .ndjson이지만 길이와 체크섬 접두부가 있어 일반 NDJSON 도구로 읽을 수 없습니다. stream() 또는 쿼리 빌더로 레코드를 읽으세요. 전체 세그먼트는 희소 인덱스가 있는 봉인된 세그먼트로 전환됩니다.

PHP
$events = new Storh\SegmentedLog($root, 'events');
$events->put(['type' => 'user.created']);
$events->compact();

대기열

Queue은 처리량이 높은 내구성 작업 큐에 적합합니다. enqueue, claim, complete, requeue 및 purge는 queue.log에 추가 전용 이벤트를 기록합니다. 큐를 열면 큐 상태를 메모리에서 재생성합니다. 일괄 메서드는 여러 작업에 걸쳐 잠금 및 플러시 비용을 분산합니다. 이벤트가 발생할 때마다 로그가 커지며, stats()에서 현재 바이트 크기를 확인할 수 있습니다.

PHP
$queue = new Storh\Queue($root, 'mail');
$queue->enqueue(['to' => 'person@example.test']);
$job = $queue->claim();

내구성 및 동시성

storh은 대상 디렉터리 안에서 원자적 이름 변경으로 파일 쓰기를 게시합니다. 저장소를 다시 열거나 repair()을 실행하면 소유 프로세스가 종료된 임시 파일은 제거하고 실행 중인 작성자의 임시 파일은 그대로 둡니다. 작성자 표식을 사용해 이 검사를 상수 시간에 처리하므로 저장소를 열 때 충돌이 발생한 경우에만 컬렉션을 훑습니다.

SegmentedLog과 Queue은 길이와 체크섬으로 보호되는 로그 줄을 씁니다. 저장소를 다시 열거나 복구하면 잘린 끝부분을 마지막으로 커밋된 이벤트까지 잘라내고, 내구성 있는 로그 내용에서 메모리 상태를 다시 빌드합니다. 로그와 큐 쓰기는 파일 시스템 잠금으로 직렬화됩니다.

DocStore 변경은 컬렉션 수준 쓰기 잠금으로 직렬화되므로 여러 프로세스에서 레코드 파일과 보조 인덱스가 하나의 일관성 경계 안에서 함께 업데이트됩니다. 동일한 파일 시스템의 원자적 이름 변경 가정 아래 서로 다른 레코드 ID에 대한 동시 쓰기는 안전합니다. 같은 ID에 대한 동시 쓰기는 마지막 이름 변경이 승리합니다.

storh은 이름을 변경하거나 추가 쓰기를 완료하기 전에 파일 핸들을 플러시하고 fsync하며, 원자적 이름 변경 후 상위 디렉터리도 fsync하려고 시도합니다. 정전 시 내구성은 해당 동기화 작업을 실제로 수행하는 기반 파일 시스템과 마운트 옵션에 따라 달라집니다.