aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-26 07:47:57 +0100
committereunhhu <[email protected]>2026-09-26 07:47:57 +0100
commit0c47bc2bc2497771740410bcd965d4d4fd0e3524 (patch)
tree85e9c24eb25f7f01f465a23cbe3995133e472152
parent4ebd1cd9108b135c1216bc6ae17832b77dcacaf3 (diff)
downloadpure-0c47bc2bc2497771740410bcd965d4d4fd0e3524.tar.gz
pure-0c47bc2bc2497771740410bcd965d4d4fd0e3524.zip
Let a burst hold up to 256 documents
A burst ended once 16 keys had joined it, so 64 writers needed four commits a round and some waited through several (p50 17.4 ms). The pending documents now grow on demand up to 256 keys, which also makes PureApp 1.3 KB smaller (3,128 bytes). With 64 connections a commit now carries 26 events on average instead of 12 and the median PUT waits 9.2 ms; throughput moves less, since each commit writes more and the SD card's syncs take most of the time.
-rw-r--r--docs/design/008-module-format.md4
-rw-r--r--prototypes/connections/module.c8
-rw-r--r--prototypes/connections/module.h8
-rw-r--r--prototypes/connections/test-module.c6
4 files changed, 18 insertions, 8 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 379518d..c800a8a 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -125,7 +125,7 @@ v5는 값을 가진 채 합류하는 분기(조건식)와, 리스트 항목마�
## 실행
-`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(4,400 B, `PureModule`은 상수
+`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(3,128 B, `PureModule`은 상수
블롭을 포함해 28,600 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트
위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함) 밀고, 지우기는 범위
안의 커서를 범위 처음으로 모은다. 모든 이벤트에서 text draft를 잡아 두고, 실패하면 이벤트 전 버전으로 돌린다.
@@ -182,7 +182,7 @@ text·리스트가 남을 때만 위치를 유지한다.
frame 이벤트는 다음 이벤트나 `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 변경은 사라진다.
- **지연 저장(그룹 커밋).** `pure_app_defer(app, true)`면 이벤트는 바로 적용되고, 호스트가 입력을 몰아 처리한 뒤
`pure_app_save` 한 번으로 저장한다. 이벤트는 그 뒤 저장이 성공해야 확정이고, 그 전에는 응답을 내보내지 않는
- 것이 호스트의 몫이다. 한 묶음에서 키 하나의 문서는 한 번만 열린다. 같은 키의 두 번째 메시지나 16개 키를 넘는
+ 것이 호스트의 몫이다. 한 묶음에서 키 하나의 문서는 한 번만 열린다. 같은 키의 두 번째 메시지나 256개 키를 넘는
메시지는 먼저 묶음을 저장하므로 문서 리비전은 저장마다 한 칸씩 움직인다(응답에 알린 리비전 = 저장된 리비전).
Pi 5의 SD(ext4)에서 이벤트 600개: 이벤트마다 저장 6,282 ms(10.47 ms/이벤트), 8개씩 973 ms, 64개씩 341 ms,
한 번에 194 ms. `pure-run --store 경로 --batch N`으로 잰다.
diff --git a/prototypes/connections/module.c b/prototypes/connections/module.c
index f8ebdd3..ebb746f 100644
--- a/prototypes/connections/module.c
+++ b/prototypes/connections/module.c
@@ -1038,6 +1038,7 @@ void pure_app_stop(PureApp *app) {
pure_draft_close(app->list_drafts[i]);
}
pure_draft_close(app->stored_values);
+ free(app->pending);
pure_undo_free(&app->undo);
free(app->arena.bytes);
free(app->view_arena.bytes);
@@ -1164,6 +1165,13 @@ PureResult pure_app_message(PureApp *app, uint32_t input, const char *key, int64
PureResult saved = pure_app_save(app);
if (saved != PURE_OK) return saved;
}
+ if (app->pending_count == app->pending_capacity) {
+ unsigned capacity = app->pending_capacity ? 2 * app->pending_capacity : 16;
+ struct PurePending *grown = realloc(app->pending, capacity * sizeof(*grown));
+ if (!grown) return PURE_ALLOCATION;
+ app->pending = grown;
+ app->pending_capacity = capacity;
+ }
PureResult opened = app->data.open(app->data.context, 0, key, &app->document, &app->revision);
if (opened != PURE_OK) { app->document = NULL; return opened; }
if (base >= 0 && base != app->revision) { pure_app_finish(app); return PURE_CONFLICT; }
diff --git a/prototypes/connections/module.h b/prototypes/connections/module.h
index c5d4f52..c9fb606 100644
--- a/prototypes/connections/module.h
+++ b/prototypes/connections/module.h
@@ -19,7 +19,7 @@ enum {
PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 40,
PURE_MODULE_FIELDS = 32, PURE_MODULE_COLLECTIONS = 1, PURE_MODULE_CONSTANTS = 64,
PURE_MODULE_CONSTANT_BYTES = 8192, PURE_MODULE_RECORDS = 8, PURE_MODULE_RECORD_FIELDS = 64,
- PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8, PURE_APP_PENDING = 16
+ PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8, PURE_APP_PENDING = 256
};
typedef enum {
@@ -147,7 +147,7 @@ typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena;
* before it returns (a frame waits for the next one). Deferred, events apply
* at once and a host saves a burst with one commit: an event is durable once
* a later save succeeds, and each key's document joins a burst once (a second
- * message for it saves the burst first). */
+ * message for it, or one past PURE_APP_PENDING keys, saves the burst first). */
typedef struct {
const PureModule *module;
PureData data;
@@ -164,8 +164,8 @@ typedef struct {
double durable_values[PURE_MODULE_STATES];
PureVersion durable_texts[PURE_MODULE_STATES], durable_lists[PURE_MODULE_STATES];
uint64_t durable_ticks, durable_events;
- struct { void *document; char key[65]; bool edited; } pending[PURE_APP_PENDING];
- unsigned pending_count;
+ struct PurePending { void *document; char key[65]; bool edited; } *pending; /* grows to PURE_APP_PENDING */
+ unsigned pending_count, pending_capacity;
PureFault fault;
uint32_t fault_pc;
uint64_t ticks, events;
diff --git a/prototypes/connections/test-module.c b/prototypes/connections/test-module.c
index e1d1ccf..6f48bc8 100644
--- a/prototypes/connections/test-module.c
+++ b/prototypes/connections/test-module.c
@@ -1697,8 +1697,10 @@ static void deferred_messages(void) {
pure_app_stop(&app); /* the last edit was never saved */
CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK && app.values[0] == 4 + PURE_APP_PENDING);
CHECK(pure_app_message(&app, 0, "k0", -1, (PureSpan){0}) == PURE_OK && app.revision == 2 && replied(&app, "abc"));
- CHECK(pure_app_message(&app, 0, "m16", -1, (PureSpan){0}) == PURE_NOT_FOUND);
- CHECK(pure_app_message(&app, 0, "m15", -1, (PureSpan){0}) == PURE_OK && replied(&app, "y"));
+ snprintf(key, sizeof(key), "m%d", PURE_APP_PENDING);
+ CHECK(pure_app_message(&app, 0, key, -1, (PureSpan){0}) == PURE_NOT_FOUND);
+ snprintf(key, sizeof(key), "m%d", PURE_APP_PENDING - 1);
+ CHECK(pure_app_message(&app, 0, key, -1, (PureSpan){0}) == PURE_OK && replied(&app, "y"));
pure_app_stop(&app);
remove_store(path);
}