diff options
| author | eunhhu <[email protected]> | 2026-09-28 10:44:19 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-09-28 10:44:19 +0100 |
| commit | 4c4c73c30a0524cf893d813fda215199150a6934 (patch) | |
| tree | 07118a2384cdc192ac881fc1008a39aca4a7d9ae /docs/design | |
| parent | cff25dd387376311b09bab3a58377fecbfe818e4 (diff) | |
| download | pure-4c4c73c30a0524cf893d813fda215199150a6934.tar.gz pure-4c4c73c30a0524cf893d813fda215199150a6934.zip | |
Keep lists as images and a log of their changes, checkpointed off the owner
Diffstat (limited to 'docs/design')
| -rw-r--r-- | docs/design/008-module-format.md | 28 |
1 files changed, 24 insertions, 4 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 233f39b..d515c12 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -264,16 +264,36 @@ text·리스트가 남을 때만 위치를 유지한다. | 상태 | 저장 형태 (런타임 이름공간, 문서 키와 겹치지 않음) | |---|---| | text | `text-이름`: UTF-8 바이트 그대로. 앱의 draft가 곧 저장 문서라 입력 한 글자는 바뀐 익스텐트만 추가한다 | -| 리스트 | `list-이름`: `PLS2`, 레코드 필드 수, 필드마다 종류·이름, 항목의 바이트 수(u64), 항목(수는 binary64, text는 u32 길이와 바이트), 그 뒤 끝까지 변경 기록(저널). 기록은 종류(u8: 1 삽입, 2 제거, 3 설정)와 위치(u32), 그리고 삽입이면 항목, 설정이면 필드(u8)와 값이다. 리스트 연산은 메모리의 항목을 바꾸고 기록을 남긴다. 저장은 기록을 문서 끝에 붙인다. 붙은 기록이 항목의 바이트 수(최소 64 KiB)를 넘게 되면 그 저장은 항목을 새로 쓰고 기록을 비운다. 시작은 항목을 읽고 기록을 차례로 다시 적용한다. `PLS1`(길이·기록 없음)은 읽고 시작할 때 `PLS2`로 다시 쓴다 | +| 리스트 | 이미지와 로그(아래). 트리에는 두지 않는다 | | 수·커서·인덱스 | `values`: `PVS1`, 상태마다 종류·이름·binary64. 바뀐 이벤트에서만 다시 쓴다 | -- **시작.** 저장된 상태를 이름과 종류로 불러온다. 없는 상태는 초기값으로 시작한다. 레코드가 달라진 리스트는 +- **리스트: 이미지와 로그.** 리스트는 저장소 디렉터리의 두 곳에 산다. **이미지** `image-이름`(파일 하나)은 + `PLI1`, 레코드 필드 수, 필드마다 종류·이름, 그것이 담은 저장의 번호(u64), 항목(수는 binary64, text는 u32 길이와 + 바이트)이고, 통째로 옆에 써서 동기화한 뒤 이름을 바꿔 끼운다(파일 전체의 다이제스트로 잘림·손상을 거부). + **로그**(`log-0`, `log-1`, ... 구간, `core/log.h`)는 리스트를 바꾼 저장마다 항목 하나다. 항목은 저장 번호, 앞 항목의 + 번호와 다이제스트, 트리도 커밋했는지, 페이로드(바뀐 리스트마다 이름과 변경 기록: 종류 u8(1 삽입, 2 제거, 3 설정), + 위치 u32, 삽입이면 항목, 설정이면 필드와 값, 모두 그 리스트 이미지의 필드로)다. 구간은 처음 만들 때 통째로 할당하고, + 이미지가 모두 담은 항목만 가진 구간은 다시 쓴다. 그래서 덧붙이기는 파일을 키우지도 새 블록을 받지도 않아 동기화가 + 데이터만 쓴다. 항목은 앞 항목의 다이제스트를 가리키므로 찢긴 쓰기나 재사용한 구간에 남은 옛 바이트는 이어지지 않는다. +- **저장 번호.** 저장마다 번호가 하나 오른다. 로그 항목이 있고 트리도 커밋하는 저장은 그 번호를 트리의 상태 `seq`에 + 함께 쓴다. 트리도 커밋한 저장의 로그 항목은 트리가 그 번호(이상)를 가질 때만 센다. 그래서 로그를 먼저 쓰고 트리를 + 나중에 커밋하는 사이에 죽어도 둘 다 남거나 둘 다 없다. 트리 커밋이 확실히 실패하면 로그 항목을 거둬 다음 저장이 그 + 자리에 쓰고, 동기화가 실패해 저장 여부를 모르면(불확실) 앱은 다시 시작할 때까지 저장하지 않는다(`conflict`). +- **체크포인트.** 리스트의 로그 기록이 항목 바이트(최소 1 MiB)를 넘으면, 또는 로그가 64 MiB를 넘으면 그것을 가장 + 오래 붙잡은 리스트의 이미지를 스냅숏에서 새로 쓰고, 로그는 모든 이미지가 담은 항목을 놓는다. 기본은 저장 직후 + 같은 스레드에서 쓰고, `pure_app_background_checkpoints`면 호스트가 다른 스레드에서 쓴다(pure-serve). 리스트는 로그에 + 변경이 적히기 전에 이미지를 가진다: 새 리스트, 이미지 이전에 저장된 리스트, 레코드가 바뀐 리스트(새 필드로)는 시작이나 + 교체 때 이미지를 먼저 쓴다. 교체 중 이미지 쓰기가 실패하면 앱은 다시 시작할 때까지 저장하지 않는다. +- **이미지 이전 형식.** 트리의 `list-이름`(`PLS2`: 머리글, 항목 바이트 수 u64, 항목, 기록 / `PLS1`: 머리글, 항목)은 + 시작할 때 읽어 이미지를 쓰고 비운다. +- **시작.** 저장된 상태를 이름과 종류로 불러온다. 리스트는 이미지를 읽고 로그에서 그 이미지가 담은 저장 뒤의 변경을 + 차례로 다시 한다. 없는 상태는 초기값으로 시작한다. 레코드가 달라진 리스트는 이름·종류가 같은 필드를 옮기고 새 필드는 기본값을 쓴 뒤 다시 저장한다(교체와 같은 규칙, 마이그레이션 스크립트 없음). 커서가 text 범위나 글자 경계를 벗어나거나, 인덱스가 리스트 밖이면 0이 된다. 깨진 리스트·text(형식 오류, UTF-8 아님)는 시작을 `store_error`로 멈춘다. 조용히 버리지 않는다. - **저장은 묶음이다.** 저장 한 번이 마지막 저장 이후 메시지가 고친 문서들과 바뀐 상태 문서들을 저장소의 - **한 커밋**(`pure_draft_commit_all`)으로 공개한다. 모두 보이거나 하나도 보이지 않는다. 저장이 실패하면 마지막 저장 - 이후의 이벤트가 **모두** 되돌려진다(값, text와 리스트 draft, 리스트 되돌리기 기록과 저널, 이벤트·tick 수). 그래서 + **한 커밋**(`pure_draft_commit_all`)과 로그 항목 하나로 공개한다. 모두 보이거나 하나도 보이지 않는다. 저장이 실패하면 + 마지막 저장 이후의 이벤트가 **모두** 되돌려진다(값, text draft, 리스트 되돌리기 기록과 저널, 이벤트·tick 수). 그래서 "성공을 알린 이벤트는 저장됐고, 실패를 알린 이벤트는 흔적이 없다"가 성립한다. 결과 불명(`uncertain`)이나 다른 기록자와의 충돌 뒤에는 앱이 저장소와 어긋나 이후 저장이 충돌로 실패한다. 다시 시작하면 저장소의 상태로 이어진다. - **기본은 이벤트마다 저장.** 키·text·메시지 이벤트는 저장까지 마치고 돌아온다(묶음 크기 1). 초당 60번 오는 |
