hikoutei 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.d.ts +2 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.d.ts.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.js +5 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmMappedObservation.js.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPolling.d.ts +52 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPolling.d.ts.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPolling.js +195 -13
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPolling.js.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingFastPath.d.ts +24 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingFastPath.d.ts.map +1 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingFastPath.js +124 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingFastPath.js.map +1 -0
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingInspection.js +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingInspection.js.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingPersistence.d.ts +15 -2
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingPersistence.d.ts.map +1 -1
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingPersistence.js +27 -3
- package/dist/adapter/persistence/providers/mikro-orm/observation/MikroOrmUserInputPollingPersistence.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/errors.d.ts +1 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/errors.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/errors.js +1 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/errors.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/index.d.ts +2 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/index.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/index.js +1 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/index.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.d.ts +3 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.js +9 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperation.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.js +137 -20
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/effect/effectOperationScript.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.js +82 -34
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/observation/observationOperation.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/batchAppendOperation.d.ts +31 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/batchAppendOperation.d.ts.map +1 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/batchAppendOperation.js +567 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/batchAppendOperation.js.map +1 -0
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.d.ts +12 -17
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.js +27 -213
- package/dist/adapter/sheets/providers/apps-script-gateway/operations/write/fastAppendOperation.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.js +81 -8
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationClient.js.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.d.ts +1 -6
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.d.ts.map +1 -1
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.js +15 -9
- package/dist/adapter/sheets/providers/apps-script-gateway/transport/operationSyncGateway.js.map +1 -1
- package/dist/application/orm/mapping/projection.js +1 -1
- package/dist/application/orm/mapping/projection.js.map +1 -1
- package/dist/application/orm/persistence/flush/flushCoordinator.d.ts.map +1 -1
- package/dist/application/orm/persistence/flush/flushCoordinator.js +1 -0
- package/dist/application/orm/persistence/flush/flushCoordinator.js.map +1 -1
- package/dist/application/orm/persistence/lifecycle/entityLifecycle.js +1 -1
- package/dist/application/orm/persistence/lifecycle/entityLifecycle.js.map +1 -1
- package/dist/application/sync/gateway/SyncGatewayBootstrap.d.ts +3 -1
- package/dist/application/sync/gateway/SyncGatewayBootstrap.d.ts.map +1 -1
- package/dist/application/sync/gateway/SyncGatewayBootstrap.js +3 -1
- package/dist/application/sync/gateway/SyncGatewayBootstrap.js.map +1 -1
- package/dist/application/sync/gateway/conflictProjection.d.ts +13 -0
- package/dist/application/sync/gateway/conflictProjection.d.ts.map +1 -0
- package/dist/application/sync/gateway/conflictProjection.js +58 -0
- package/dist/application/sync/gateway/conflictProjection.js.map +1 -0
- package/dist/application/sync/gateway/conflictProjectionRegistration.d.ts +11 -0
- package/dist/application/sync/gateway/conflictProjectionRegistration.d.ts.map +1 -0
- package/dist/application/sync/gateway/conflictProjectionRegistration.js +70 -0
- package/dist/application/sync/gateway/conflictProjectionRegistration.js.map +1 -0
- package/dist/application/sync/gateway/coordinator/CoordinatedSyncGateway.d.ts +102 -0
- package/dist/application/sync/gateway/coordinator/CoordinatedSyncGateway.d.ts.map +1 -0
- package/dist/application/sync/gateway/coordinator/CoordinatedSyncGateway.js +221 -0
- package/dist/application/sync/gateway/coordinator/CoordinatedSyncGateway.js.map +1 -0
- package/dist/application/sync/gateway/coordinator/asyncMutex.d.ts +51 -0
- package/dist/application/sync/gateway/coordinator/asyncMutex.d.ts.map +1 -0
- package/dist/application/sync/gateway/coordinator/asyncMutex.js +65 -0
- package/dist/application/sync/gateway/coordinator/asyncMutex.js.map +1 -0
- package/dist/application/sync/gateway/coordinator/coordinatorTelemetry.d.ts +31 -0
- package/dist/application/sync/gateway/coordinator/coordinatorTelemetry.d.ts.map +1 -0
- package/dist/application/sync/gateway/coordinator/coordinatorTelemetry.js +13 -0
- package/dist/application/sync/gateway/coordinator/coordinatorTelemetry.js.map +1 -0
- package/dist/application/sync/gateway/syncGateway.d.ts +26 -6
- package/dist/application/sync/gateway/syncGateway.d.ts.map +1 -1
- package/dist/application/sync/gateway/syncGateway.js +10 -2
- package/dist/application/sync/gateway/syncGateway.js.map +1 -1
- package/dist/application/sync/gateway/transportClassification.d.ts +62 -0
- package/dist/application/sync/gateway/transportClassification.d.ts.map +1 -0
- package/dist/application/sync/gateway/transportClassification.js +133 -0
- package/dist/application/sync/gateway/transportClassification.js.map +1 -0
- package/dist/application/sync/inbound/autoSystemConflictResolution.d.ts +26 -0
- package/dist/application/sync/inbound/autoSystemConflictResolution.d.ts.map +1 -0
- package/dist/application/sync/inbound/autoSystemConflictResolution.js +335 -0
- package/dist/application/sync/inbound/autoSystemConflictResolution.js.map +1 -0
- package/dist/application/sync/outbound/effects/AdaptiveEffectBatchController.d.ts +50 -0
- package/dist/application/sync/outbound/effects/AdaptiveEffectBatchController.d.ts.map +1 -0
- package/dist/application/sync/outbound/effects/AdaptiveEffectBatchController.js +97 -0
- package/dist/application/sync/outbound/effects/AdaptiveEffectBatchController.js.map +1 -0
- package/dist/application/sync/outbound/effects/SyncEffectSupervisor.d.ts +4 -1
- package/dist/application/sync/outbound/effects/SyncEffectSupervisor.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectSupervisor.js +35 -1
- package/dist/application/sync/outbound/effects/SyncEffectSupervisor.js.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorker.d.ts +14 -2
- package/dist/application/sync/outbound/effects/SyncEffectWorker.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorker.js +190 -31
- package/dist/application/sync/outbound/effects/SyncEffectWorker.js.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.d.ts +25 -2
- package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.js +25 -2
- package/dist/application/sync/outbound/effects/SyncEffectWorkerConstants.js.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.d.ts +14 -7
- package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.js +118 -14
- package/dist/application/sync/outbound/effects/SyncEffectWorkerDispatch.js.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.d.ts +30 -9
- package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.js +102 -18
- package/dist/application/sync/outbound/effects/SyncEffectWorkerRouting.js.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.d.ts.map +1 -1
- package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.js +49 -14
- package/dist/application/sync/outbound/effects/SyncEffectWorkerTransitions.js.map +1 -1
- package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.d.ts.map +1 -1
- package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.js +97 -17
- package/dist/application/sync/outbound/reconciliation/ReconciliationScanner.js.map +1 -1
- package/dist/application/sync/service/SyncServiceBootstrap.d.ts +17 -0
- package/dist/application/sync/service/SyncServiceBootstrap.d.ts.map +1 -1
- package/dist/application/sync/service/SyncServiceBootstrap.js +125 -10
- package/dist/application/sync/service/SyncServiceBootstrap.js.map +1 -1
- package/dist/application/sync/service/contracts.d.ts +2 -0
- package/dist/application/sync/service/contracts.d.ts.map +1 -1
- package/dist/application/sync/telemetry/syncTiming.d.ts +6 -0
- package/dist/application/sync/telemetry/syncTiming.d.ts.map +1 -1
- package/dist/application/sync/telemetry/syncTiming.js +2 -0
- package/dist/application/sync/telemetry/syncTiming.js.map +1 -1
- package/dist/domain/conflict/index.d.ts +1 -1
- package/dist/domain/conflict/index.d.ts.map +1 -1
- package/dist/domain/conflict/index.js +1 -1
- package/dist/domain/conflict/index.js.map +1 -1
- package/dist/domain/conflict/transitions.d.ts +3 -0
- package/dist/domain/conflict/transitions.d.ts.map +1 -1
- package/dist/domain/conflict/transitions.js +3 -2
- package/dist/domain/conflict/transitions.js.map +1 -1
- package/dist/domain/model/constants.d.ts +1 -0
- package/dist/domain/model/constants.d.ts.map +1 -1
- package/dist/domain/model/constants.js +1 -0
- package/dist/domain/model/constants.js.map +1 -1
- package/dist/infrastructure/storage/index.d.ts +4 -2
- package/dist/infrastructure/storage/index.d.ts.map +1 -1
- package/dist/infrastructure/storage/index.js +2 -1
- package/dist/infrastructure/storage/index.js.map +1 -1
- package/dist/infrastructure/storage/sqlite/migrateSchema.d.ts.map +1 -1
- package/dist/infrastructure/storage/sqlite/migrateSchema.js +59 -1
- package/dist/infrastructure/storage/sqlite/migrateSchema.js.map +1 -1
- package/dist/infrastructure/storage/sqlite/schema.d.ts +12 -1
- package/dist/infrastructure/storage/sqlite/schema.d.ts.map +1 -1
- package/dist/infrastructure/storage/sqlite/schema.js +47 -3
- package/dist/infrastructure/storage/sqlite/schema.js.map +1 -1
- package/dist/infrastructure/storage/sqlite/schemaTypes.d.ts +2 -2
- package/dist/infrastructure/storage/sqlite/schemaTypes.d.ts.map +1 -1
- package/dist/infrastructure/storage/state/canonical/canonicalCommit.js +1 -1
- package/dist/infrastructure/storage/state/canonical/canonicalCommit.js.map +1 -1
- package/dist/infrastructure/storage/state/mapped/mappedPersistenceSql.d.ts +1 -0
- package/dist/infrastructure/storage/state/mapped/mappedPersistenceSql.d.ts.map +1 -1
- package/dist/infrastructure/storage/state/mapped/mappedPersistenceSql.js +1 -1
- package/dist/infrastructure/storage/state/mapped/mappedPersistenceSql.js.map +1 -1
- package/dist/infrastructure/storage/state/observation/observationAudit.js +5 -1
- package/dist/infrastructure/storage/state/observation/observationAudit.js.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriter.d.ts +10 -0
- package/dist/infrastructure/storage/state/resolution/resolutionWriter.d.ts.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriter.js +106 -20
- package/dist/infrastructure/storage/state/resolution/resolutionWriter.js.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.d.ts +9 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.d.ts.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.js +3 -0
- package/dist/infrastructure/storage/state/resolution/resolutionWriterContracts.js.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.d.ts +11 -0
- package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.d.ts.map +1 -1
- package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.js +35 -0
- package/dist/infrastructure/storage/state/resolution/resolutionWriterSql.js.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutbox.d.ts +12 -4
- package/dist/infrastructure/storage/sync/outbound/effectOutbox.d.ts.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutbox.js +46 -15
- package/dist/infrastructure/storage/sync/outbound/effectOutbox.js.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.d.ts +24 -0
- package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.d.ts.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.js +1 -0
- package/dist/infrastructure/storage/sync/outbound/effectOutboxContracts.js.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.d.ts +9 -7
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.d.ts.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.js +44 -8
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSql.js.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.d.ts +3 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.d.ts.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.js +35 -2
- package/dist/infrastructure/storage/sync/outbound/effectOutboxSupport.js.map +1 -1
- package/dist/infrastructure/storage/sync/outbound/reconciliationSql.js +1 -0
- package/dist/infrastructure/storage/sync/outbound/reconciliationSql.js.map +1 -1
- package/dist/infrastructure/storage/sync/shared/spreadsheetAuthority.d.ts +31 -0
- package/dist/infrastructure/storage/sync/shared/spreadsheetAuthority.d.ts.map +1 -0
- package/dist/infrastructure/storage/sync/shared/spreadsheetAuthority.js +92 -0
- package/dist/infrastructure/storage/sync/shared/spreadsheetAuthority.js.map +1 -0
- package/dist/infrastructure/storage/sync/shared/writerLease.js +3 -1
- package/dist/infrastructure/storage/sync/shared/writerLease.js.map +1 -1
- package/docs/advanced-sheets-gateway-concurrency-problem.md +434 -0
- package/docs/architecture.md +53 -11
- package/docs/ci.md +31 -14
- package/docs/google-sheets-sync-scaling-strategy.md +459 -0
- package/docs/quick-start.md +15 -1
- package/docs/sync-bulk-write-benchmark.md +520 -0
- package/docs/sync-observability.md +39 -3
- package/docs/write-and-synchronization-flow.md +76 -17
- package/package.json +1 -1
|
@@ -1,5 +1,386 @@
|
|
|
1
1
|
# Sync bulk-write benchmark
|
|
2
2
|
|
|
3
|
+
## Black-box server and Locust workload
|
|
4
|
+
|
|
5
|
+
The test-only server harness treats Hikoutei as a server-side library rather
|
|
6
|
+
than calling the sync worker directly from a benchmark script:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
node --env-file=.env .local/hikoutei-load-server.mjs
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
It binds to `127.0.0.1:8787` by default and reports a run-specific persistent
|
|
13
|
+
SQLite path, System_State/User_Input tab names, and JSONL log path from
|
|
14
|
+
`GET /health`. It does not delete the database or Sheet data on shutdown.
|
|
15
|
+
|
|
16
|
+
Run the mixed workload from another terminal:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
locust -f .local/locustfile.py \
|
|
20
|
+
--host http://127.0.0.1:8787 \
|
|
21
|
+
--users 20 \
|
|
22
|
+
--spawn-rate 5 \
|
|
23
|
+
--run-time 5m \
|
|
24
|
+
--headless
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The Locust user weights are read 40%, create 20%, application update 25%, and
|
|
28
|
+
User_Input simulation 15%. The User_Input task calls the test-only
|
|
29
|
+
`/__test/user-input` endpoint, which changes the real Sheet and leaves the
|
|
30
|
+
normal polling service to apply the value to SQLite. The server records HTTP,
|
|
31
|
+
flush, Gateway, polling, error, and over-30-second events in the JSONL log;
|
|
32
|
+
the over-30-second observer never stops the server or workload. Inspect
|
|
33
|
+
`GET /metrics` after Locust stops, then stop the server with `Ctrl-C`; the
|
|
34
|
+
persistent run data remains for inspection.
|
|
35
|
+
|
|
36
|
+
## 2026-08-03 — black-box Locust run and configuration diagnosis
|
|
37
|
+
|
|
38
|
+
- Server run: `load-1785732937019-23536468`
|
|
39
|
+
- Log: `.local/hikoutei-load-load-1785732937019-23536468.jsonl`
|
|
40
|
+
- Workload: `.local/locustfile.py` mixed CRUD/User_Input tasks
|
|
41
|
+
- Final HTTP log: **1,508** requests; 1,201 successful responses and 307
|
|
42
|
+
failures, including 264 creates, 582 reads, 375 updates, and 209 User_Input
|
|
43
|
+
requests
|
|
44
|
+
- Gateway log: **444** requests; 246 successes, 190 operation failures, and 8
|
|
45
|
+
timeouts; 4 requests exceeded 30 seconds
|
|
46
|
+
- Final local state snapshot: **264** SQLite entity rows, 332 applied effects,
|
|
47
|
+
7 blocked candidates, 601 pending effects, and 234 processing effects
|
|
48
|
+
|
|
49
|
+
The test did create visible tabs, but the server read a different
|
|
50
|
+
`TYPED_SHEETS_GATEWAY_SHEET_ID` from the local `.env` than the spreadsheet ID
|
|
51
|
+
provided for the intended target. On the spreadsheet actually used by the
|
|
52
|
+
server, the following tabs were visible: `TS_Load_load-1785732937019-23536468_System`
|
|
53
|
+
(265 rows including the header) and `TS_Load_load-1785732937019-23536468_Input`
|
|
54
|
+
(26 rows including the header). The apparent missing table was therefore a
|
|
55
|
+
configuration-target mismatch, not a provisioning omission. The intended sheet
|
|
56
|
+
ID must be placed in `.env` before the next run; the shared secret must also be
|
|
57
|
+
valid for that deployed gateway/sheet configuration.
|
|
58
|
+
|
|
59
|
+
The run was not a clean functional pass. Concurrent User_Input simulation and
|
|
60
|
+
polling contended on the Apps Script observation lock, producing repeated
|
|
61
|
+
`Could not acquire the sync observation gateway lock` errors; the resulting
|
|
62
|
+
failed effects then blocked later ORM writes with `user_input projection is
|
|
63
|
+
blocked: latest effect is failed`. The persistent SQLite file and actual test
|
|
64
|
+
Sheet data were retained for diagnosis.
|
|
65
|
+
|
|
66
|
+
## 2026-08-03 — lock-refactor Locust run
|
|
67
|
+
|
|
68
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
69
|
+
- Server run: `load-1785737483461-793bddd2`
|
|
70
|
+
- Exact server command: `node --env-file=.env .local/hikoutei-load-server.mjs`
|
|
71
|
+
- Exact Locust command: `/usr/local/bin/locust -f .local/locustfile.py --host http://127.0.0.1:8787`
|
|
72
|
+
(user count and duration were controlled from the Locust UI)
|
|
73
|
+
- Log: `.local/hikoutei-load-load-1785737483461-793bddd2.jsonl`
|
|
74
|
+
- Database: `.local/hikoutei-load-load-1785737483461-793bddd2.sqlite`
|
|
75
|
+
- Backend: local Node `v24.3.0`, persistent SQLite/MikroORM, deployed Apps Script
|
|
76
|
+
gateway, and the real target Sheet
|
|
77
|
+
- Scenario: mixed read/create/update/User_Input workload; HTTP activity ran from
|
|
78
|
+
`2026-08-03T06:12:25Z` through `2026-08-03T06:17:44Z` (about 5m 19s)
|
|
79
|
+
- Setup: one provisioning Gateway call, 4,013 ms, excluded from steady-state
|
|
80
|
+
counts below
|
|
81
|
+
|
|
82
|
+
| Metric | No-setup / steady-state result | Full recorded run |
|
|
83
|
+
| --- | ---: | ---: |
|
|
84
|
+
| HTTP requests | 4,883 workload requests | 4,907 through workload stop, including 21 health, 2 metrics, and 1 unknown request |
|
|
85
|
+
| HTTP success/failure | 3,119 / 1,764 workload responses | 3,142 / 1,765 responses |
|
|
86
|
+
| Gateway requests | 763 sync calls; 718 success / 45 failure | 764 including setup |
|
|
87
|
+
| HTTP latency | p50 5.16 ms; p95 2,820.89 ms | max 60,004.28 ms |
|
|
88
|
+
| Gateway latency | p50 2,312 ms; p95 30,314 ms | max 60,004 ms; 35 over 30 s |
|
|
89
|
+
|
|
90
|
+
Workload response breakdown: create 505 successful and 522 failed; read 1,957
|
|
91
|
+
successful; update 582 successful and 603 failed; User_Input 75 returned 202,
|
|
92
|
+
598 returned 404 before the row was projected, and 41 returned 500. Gateway
|
|
93
|
+
failures during the workload were 22 invalid responses, 15 `method_not_allowed`
|
|
94
|
+
responses, 6 timeouts, and 2 remote operation failures. No
|
|
95
|
+
`Could not acquire the sync observation gateway lock` message occurred during
|
|
96
|
+
the workload window. One such lock error appeared later while background
|
|
97
|
+
polling continued after Locust stopped.
|
|
98
|
+
|
|
99
|
+
The dominant workload failure was projection poisoning: 1,124 HTTP operation
|
|
100
|
+
errors reported `user_input projection is blocked: latest effect is
|
|
101
|
+
blocked_candidate`. The workload also recorded 7 polling errors for invalid
|
|
102
|
+
observed projection evidence. A post-load metrics snapshot showed 505 entity
|
|
103
|
+
rows, 656 applied outbox effects, 25 blocked candidates, 61 failed effects,
|
|
104
|
+
1,264 pending effects, and 177 processing effects; this snapshot includes
|
|
105
|
+
background synchronization after the Locust workload stopped. The Locust UI
|
|
106
|
+
process remained open without new workload requests, and the server remained
|
|
107
|
+
alive for diagnosis. All Sheet, SQLite, and JSONL data were retained.
|
|
108
|
+
|
|
109
|
+
## 2026-08-03 — pending User_Input retry Locust run
|
|
110
|
+
|
|
111
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
112
|
+
- Server run: `load-1785738645774-79e64130`
|
|
113
|
+
- Exact server command: `node --env-file=.env .local/hikoutei-load-server.mjs`
|
|
114
|
+
- Exact Locust command: `/usr/local/bin/locust -f .local/locustfile.py --host http://127.0.0.1:8787`
|
|
115
|
+
(user count and duration were controlled from the Locust UI)
|
|
116
|
+
- Log: `.local/hikoutei-load-load-1785738645774-79e64130.jsonl`
|
|
117
|
+
- Database: `.local/hikoutei-load-load-1785738645774-79e64130.sqlite`
|
|
118
|
+
- Backend: local Node `v24.3.0`, persistent SQLite/MikroORM, deployed Apps Script
|
|
119
|
+
gateway, and the real target Sheet
|
|
120
|
+
- Scenario: mixed read/create/update/User_Input workload with bounded User_Input
|
|
121
|
+
retry/backoff; workload ran from `2026-08-03T06:31:37Z` through
|
|
122
|
+
`2026-08-03T06:36:58Z` (about 5m 21s)
|
|
123
|
+
- Setup: one provisioning call and pre-load worker warm-up excluded from the
|
|
124
|
+
steady-state Gateway counts
|
|
125
|
+
|
|
126
|
+
| Metric | No-setup / steady-state result | Locust result |
|
|
127
|
+
| --- | ---: | ---: |
|
|
128
|
+
| Requests | 7,754 CRUD/User_Input requests | 7,774 total requests |
|
|
129
|
+
| Success/failure | 4,519 / 3,235 workload responses | fail ratio **41.61%** |
|
|
130
|
+
| Gateway | 1,173 calls; 1,165 success / 8 failure | p50 2,132 ms; p95 6,624 ms |
|
|
131
|
+
| HTTP latency | aggregate median 10 ms; p95 2,300 ms | max 52,409 ms |
|
|
132
|
+
| Slow Gateway calls | 12 over 30 seconds | max 52,407 ms |
|
|
133
|
+
|
|
134
|
+
Locust's stopped report had 3,116 successful reads, 1,590 creates with 1,386
|
|
135
|
+
failures, 1,924 updates with 1,698 failures, and 1,124 User_Input attempts
|
|
136
|
+
with 151 failures. Of the User_Input failures, 148 reached the bounded retry
|
|
137
|
+
deadline and 3 were HTTP 500 responses. The harness therefore stopped counting
|
|
138
|
+
most projection-lag 404 responses as immediate Locust failures, but those rows
|
|
139
|
+
still did not become successful Sheet edits: the server log recorded 943 404s
|
|
140
|
+
and 188 successful 202 edits.
|
|
141
|
+
|
|
142
|
+
The main failure remained projection poisoning. The server recorded 3,084
|
|
143
|
+
`user_input projection is blocked: latest effect is blocked_candidate` errors,
|
|
144
|
+
matching the create/update failures. During the workload, the Gateway had 3
|
|
145
|
+
remote operation failures, 4 invalid responses, and 1 `method_not_allowed`
|
|
146
|
+
response; polling also recorded 2 observation-lock acquisition failures and 7
|
|
147
|
+
invalid observed-evidence errors. The retry queue improved classification of
|
|
148
|
+
asynchronous row lag, but it cannot repair a projection whose effect is already
|
|
149
|
+
blocked. A post-load metrics snapshot showed 204 entity rows, 360 applied
|
|
150
|
+
outbox effects, 25 blocked candidates, 2 failed effects, 397 pending effects,
|
|
151
|
+
and 93 processing effects. The Locust state was stopped with zero users; the
|
|
152
|
+
server and all generated data remain available for diagnosis.
|
|
153
|
+
|
|
154
|
+
Compared with the previous lock-refactor run, the retry queue reduced User_Input
|
|
155
|
+
failures reported by Locust from immediate row-not-found failures to 148
|
|
156
|
+
retry-deadline failures, but the underlying blocked-candidate cascade remains
|
|
157
|
+
the next bottleneck. The two observation-lock failures also confirm that the
|
|
158
|
+
remaining contention is in the retained full-observation path, not the
|
|
159
|
+
lock-free values-only read.
|
|
160
|
+
|
|
161
|
+
## 2026-08-03 — Sync_Conflicts and automatic system-wins Locust run
|
|
162
|
+
|
|
163
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
164
|
+
- Server command: `node --env-file=.env .local/hikoutei-load-server.mjs`
|
|
165
|
+
- Locust command: `/usr/local/bin/locust -f .local/locustfile.py --host http://127.0.0.1:8787` (user count and duration were controlled from the Locust UI)
|
|
166
|
+
- Server run: `load-1785748381328-41f5654c`
|
|
167
|
+
- Log: `.local/hikoutei-load-load-1785748381328-41f5654c.jsonl`
|
|
168
|
+
- Database: `.local/hikoutei-load-load-1785748381328-41f5654c.sqlite`
|
|
169
|
+
- Backend: local Node `v24.3.0`, persistent SQLite/MikroORM, deployed Apps Script gateway, and the real target Sheet
|
|
170
|
+
- Scenario: mixed read/create/update/User_Input workload with mandatory
|
|
171
|
+
`System_State`, `User_Input`, and `Sync_Conflicts` projections
|
|
172
|
+
- Workload window: `2026-08-03T09:13:59.580Z`–`2026-08-03T09:19:17.434Z`
|
|
173
|
+
(about 5m 18s); setup provisioning took 5,436 ms and is excluded below
|
|
174
|
+
- Dataset/result: 456 SQLite entity rows and 127 captured conflicts
|
|
175
|
+
|
|
176
|
+
| Metric | No-setup / steady-state result | Full server record |
|
|
177
|
+
| --- | ---: | ---: |
|
|
178
|
+
| Application HTTP requests | 2,648 workload requests | 2,669 including 21 health requests before the workload endpoint closed |
|
|
179
|
+
| HTTP outcome | 2,068 2xx / 246 projection-lag 404 / 334 500 | 2,089 2xx / 246 404 / 334 500 |
|
|
180
|
+
| Locust-effective failure ratio | **334 / 2,648 = 12.61%** | 404 responses were marked success by the bounded retry queue |
|
|
181
|
+
| Gateway requests | 428 sync calls; 308 success / 120 failure | 429 including setup |
|
|
182
|
+
| Gateway latency | p50 4,344 ms; p95 34,246 ms | max 60,011 ms; 92 over 30 s; 4 over 60 s |
|
|
183
|
+
|
|
184
|
+
Application breakdown was 456 successful creates and 93 failed creates, 1,044
|
|
185
|
+
successful reads, 526 successful updates and 130 failed updates, and 42
|
|
186
|
+
successful User_Input changes, 246 projection-lag 404s, and 111 failed
|
|
187
|
+
User_Input requests. The main errors were 223 requests blocked by a previously
|
|
188
|
+
failed `user_input` effect, 90 invalid Gateway JSON responses, 17
|
|
189
|
+
`Use a signed POST request` responses, and 4 Gateway timeouts. During the
|
|
190
|
+
workload window there were 2 polling evidence errors and 1 effect-worker
|
|
191
|
+
postcondition/claim mismatch. The 404s are not counted as Locust failures by
|
|
192
|
+
`locustfile.py`; they remain bounded pending projection retries.
|
|
193
|
+
|
|
194
|
+
The new conflict path itself behaved correctly in SQLite: all **127/127**
|
|
195
|
+
`sync_conflict` rows ended `RESOLVED`, all **127/127**
|
|
196
|
+
`acknowledge_system` commands ended `applied` with role `sync_operator`, and
|
|
197
|
+
there were **0** active candidate pointers. No `latest effect is
|
|
198
|
+
blocked_candidate` cascade appeared in the server errors; the remaining
|
|
199
|
+
application failures were caused by failed Gateway/materialization effects.
|
|
200
|
+
The durable outbox retained the remote work for recovery. At the post-load
|
|
201
|
+
metrics snapshot (after background synchronization continued), the outbox was
|
|
202
|
+
`applied=390`, `pending=1,267`, `processing=60`, `failed=375`,
|
|
203
|
+
`blocked_candidate=4`, and `superseded=127` at the
|
|
204
|
+
`2026-08-03T09:26:09Z` metrics snapshot. The 127 conflict-audit effects
|
|
205
|
+
were `30 applied`, `77 pending`, and `20 failed`, so SQLite resolution was
|
|
206
|
+
committed even though the remote `Sync_Conflicts` projection had not fully
|
|
207
|
+
converged.
|
|
208
|
+
|
|
209
|
+
This is a functional improvement over the preceding retry-queue run: the
|
|
210
|
+
unresolved conflict/candidate cascade was eliminated and the observed failure
|
|
211
|
+
ratio was lower, but it is **not a clean remote-delivery benchmark**. The
|
|
212
|
+
remaining bottleneck is Apps Script instability/latency (`invalid JSON`,
|
|
213
|
+
`method_not_allowed`, operation failures, and timeouts), which poisoned
|
|
214
|
+
System_State/User_Input effects and left a large durable backlog. The server
|
|
215
|
+
and all Sheet, SQLite, and JSONL artifacts were retained; the server continued
|
|
216
|
+
running after Locust stopped, so later background metrics are explicitly not
|
|
217
|
+
part of the steady-state workload numbers.
|
|
218
|
+
|
|
219
|
+
A later background-only inspection at `2026-08-03T09:50:42Z` found that the
|
|
220
|
+
server was still alive but the remote projection had not converged: SQLite now
|
|
221
|
+
contained **333** resolved conflicts (up from 127 at the workload snapshot),
|
|
222
|
+
with zero active candidate pointers. This increase represents the same
|
|
223
|
+
User_Input edits being observed again while their canonical reconcile effects
|
|
224
|
+
remain failed/pending; it is not evidence that SQLite left conflicts open, but
|
|
225
|
+
it does show that a continuously running worker cannot drain a permanently
|
|
226
|
+
unavailable remote projection. The outbox then stood at `applied=794`,
|
|
227
|
+
`pending=887`, `processing=64`, `failed=553`, `blocked_candidate=4`, and
|
|
228
|
+
`superseded=333`; the server was still handling a long Gateway append when the
|
|
229
|
+
20-second before/after check was taken. This follow-up is diagnostic and is not
|
|
230
|
+
part of the steady-state workload result above.
|
|
231
|
+
|
|
232
|
+
## 2026-08-02 — performance branch baseline
|
|
233
|
+
|
|
234
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
235
|
+
- Base: `origin/develop` at `63ebddc`, including the merged contract PRs #155 and #156
|
|
236
|
+
- Environment: macOS arm64, Node `v24.3.0`, npm `11.4.2`
|
|
237
|
+
|
|
238
|
+
### Local implementation verification (synthetic, no live Sheets)
|
|
239
|
+
|
|
240
|
+
These commands exercise the in-process SQLite authority plus fake/in-memory
|
|
241
|
+
Apps Script gateways. They are correctness and contract checks, not latency
|
|
242
|
+
measurements: no real spreadsheet, credentials, or Apps Script quota is
|
|
243
|
+
involved, so timings are not comparable to the live baselines below.
|
|
244
|
+
|
|
245
|
+
- `npm test` — 30 files, 197 tests passed
|
|
246
|
+
- `npm run typecheck` — passed
|
|
247
|
+
- `npm run typecheck:test` — passed
|
|
248
|
+
- `npm run build` — passed
|
|
249
|
+
- `npm pack --dry-run` — passed
|
|
250
|
+
- `git diff --check` — passed
|
|
251
|
+
|
|
252
|
+
The regression suite now covers the adaptive/outbound work directly with
|
|
253
|
+
synthetic fixtures: the values-only preflight escalation rules, formula/merged/
|
|
254
|
+
error deferral to the periodic safety full scan, safety-scan scheduling and
|
|
255
|
+
coalescing, backlog convergence across worker passes, no full read on unchanged
|
|
256
|
+
adaptive passes, response-loss backoff, and the new internal polling timing
|
|
257
|
+
phases emitted through the diagnostics sink (canonical state read, values-only
|
|
258
|
+
read, fast comparison, full metadata observation, persistence, overall, and safety-scan lag).
|
|
259
|
+
|
|
260
|
+
### 2026-08-02 — local synthetic latency sample
|
|
261
|
+
|
|
262
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
263
|
+
- Exact command: `./node_modules/.bin/vite-node scripts/bench-local-sync-performance.ts`
|
|
264
|
+
- Backend: in-process SQLite/MikroORM plus `FakeSyncSheetGateway`; no network or Apps Script quota
|
|
265
|
+
- Dataset: 66 unchanged User_Input rows; 7 steady-state samples per mode
|
|
266
|
+
- Setup: entity seeding, effect delivery, and first warm-up pass excluded
|
|
267
|
+
|
|
268
|
+
| Scenario | No-setup / steady-state result | Gateway calls during run |
|
|
269
|
+
| --- | ---: | --- |
|
|
270
|
+
| Adaptive values-only polling | mean **1.604 ms** (min 1.271 / max 2.582) | 7 values-only reads, 0 full snapshots |
|
|
271
|
+
| Full metadata polling | mean **5.642 ms** (min 5.228 / max 6.656) | 0 values-only reads, 7 full snapshots |
|
|
272
|
+
| 66-row outbound update | SQLite flush **68.988 ms**; delivery **216.973 ms**; 66 applied | 8 `applyEffects` calls |
|
|
273
|
+
|
|
274
|
+
The local adaptive sample is about **3.5× faster** than the local full path in
|
|
275
|
+
this no-network fixture, but it is not comparable to the historical live
|
|
276
|
+
27.7-second versus 2.2-second measurements below. The useful correctness signal
|
|
277
|
+
is that unchanged adaptive samples used no full metadata reads. The live
|
|
278
|
+
measurement below confirms the same behavior through the deployed gateway.
|
|
279
|
+
|
|
280
|
+
### 2026-08-02 — live Apps Script polling smoke benchmark
|
|
281
|
+
|
|
282
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
283
|
+
- Exact command: `LIVE_POLL_ONLY=1 node --env-file=.env .local/user-input-polling-live.mjs`
|
|
284
|
+
- Slow-request guard: synchronization Gateway requests with `durationMs > 30,000` fail the benchmark; provisioning and cleanup requests are recorded but excluded from this failure gate
|
|
285
|
+
- Backend: deployed Apps Script gateway and real Google Sheet
|
|
286
|
+
- Dataset: 1 entity row, System_State + User_Input projections
|
|
287
|
+
- Setup: temporary-tab provisioning and first safety scan are reported separately;
|
|
288
|
+
steady-state polling excludes provisioning
|
|
289
|
+
|
|
290
|
+
| Scenario | No-setup / steady-state result | Evidence |
|
|
291
|
+
| --- | ---: | --- |
|
|
292
|
+
| Initial unchanged adaptive poll | **2,504 ms** | `mode=adaptive`, `fullMetadataTables=0`, 1 values-only read, 0 full snapshots |
|
|
293
|
+
| Remote edit request | **1,663.4 ms** | Apps Script `setValue()` + `flush()` |
|
|
294
|
+
| Changed adaptive poll | **3,859 ms** | 1,580 ms values-only read + 2,262 ms full metadata read; SQLite status became `approved` |
|
|
295
|
+
| First ORM insert flush | **12.39 ms** | SQLite/outbox commit |
|
|
296
|
+
| Insert remote delivery | **5,185.8 ms** | 2 effects applied |
|
|
297
|
+
|
|
298
|
+
The service's automatic initial safety scan took **2,567 ms** and used one full
|
|
299
|
+
metadata request. The latency guard inspected **8 synchronization Gateway
|
|
300
|
+
requests** and found **0** over the 30-second threshold. Temporary Sheet tabs
|
|
301
|
+
were cleaned up after the run. This live run proves the current harness reaches
|
|
302
|
+
the deployed gateway and that unchanged polling avoids the full metadata request
|
|
303
|
+
while changed polling escalates and persists correctly. The JSON result includes
|
|
304
|
+
`gatewayLatencyGuard` with the threshold and guarded-request summary. A
|
|
305
|
+
functional failure (for example, the SQLite value is not `approved`) is reported
|
|
306
|
+
separately from a `slow_sync_gateway_operation` latency failure.
|
|
307
|
+
|
|
308
|
+
#### Post-guard verification rerun
|
|
309
|
+
|
|
310
|
+
- Commands:
|
|
311
|
+
- `LIVE_POLL_ONLY=1 node --env-file=.env .local/user-input-polling-live.mjs`
|
|
312
|
+
- `LIVE_CRUD_ONLY=1 node --env-file=.env .local/user-input-polling-live.mjs`
|
|
313
|
+
- `node --env-file=.env .local/user-input-polling-live.mjs`
|
|
314
|
+
- Dataset: 1 row per isolated run; provisioning excluded from steady-state values
|
|
315
|
+
- Functional result: all runs completed; User_Input status became `approved`; all
|
|
316
|
+
CRUD deliveries applied 2 effects per operation; no conflicts
|
|
317
|
+
|
|
318
|
+
| Run | No-setup / steady-state result | Guard result |
|
|
319
|
+
| --- | --- | --- |
|
|
320
|
+
| `LIVE_POLL_ONLY=1` | Initial poll **2,304 ms**; changed poll **5,345 ms**; insert delivery **6,194.66 ms** | 8 guarded requests, 0 slow |
|
|
321
|
+
| `LIVE_CRUD_ONLY=1` | Initial poll **2,153 ms**; update delivery **8,515.16 ms**; delete delivery **6,372.73 ms** | 9 guarded requests, 0 slow |
|
|
322
|
+
| Full harness | Initial poll **2,728 ms**; steady polls **3,141.38 / 2,489.48 / 2,227.51 ms**; update delivery **7,593.92 ms**; delete delivery **7,249.12 ms** | 15 guarded requests, 0 slow |
|
|
323
|
+
|
|
324
|
+
#### Quota observation for this verification batch
|
|
325
|
+
|
|
326
|
+
The three live runs above made **38 external Apps Script Web App POSTs** in total:
|
|
327
|
+
32 synchronization requests, 3 provisioning requests, and 3 cleanup requests. Every observed gateway request returned HTTP 200; no 403/429,
|
|
328
|
+
`RESOURCE_EXHAUSTED`, or quota-related gateway error occurred. The 30-second
|
|
329
|
+
gateway guard also found 0 slow synchronization requests.
|
|
330
|
+
|
|
331
|
+
This is an observed sample, not a remaining-quota counter. The current gateway
|
|
332
|
+
uses Apps Script `SpreadsheetApp`, not the Google Sheets REST API, so the Sheets
|
|
333
|
+
REST request quota cannot be inferred from these POST counts. Apps Script
|
|
334
|
+
service/runtime quotas are account- and project-dependent, and Google does not
|
|
335
|
+
return their remaining amount through this harness. The exact remaining quota
|
|
336
|
+
must be checked in the linked Google Cloud/API or Apps Script execution/usage
|
|
337
|
+
console.
|
|
338
|
+
|
|
339
|
+
### 2026-08-02 — progressive live bidirectional verification
|
|
340
|
+
|
|
341
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
342
|
+
- Exact command: `node --env-file=.env .local/progressive-bidirectional-live.mjs`
|
|
343
|
+
- Backend: deployed Apps Script gateway and real Google Sheet
|
|
344
|
+
- Matrix: cumulative **1 → 10 → 25 → 66 → 100 rows** in one isolated service
|
|
345
|
+
- Each stage performed both directions: app flush/effect delivery, bulk User_Input
|
|
346
|
+
edit, changed polling, SQLite verification, and one unchanged polling pass
|
|
347
|
+
- Policy: 30-second Gateway requests were recorded but never stopped the matrix;
|
|
348
|
+
all stages completed before the final result was reported
|
|
349
|
+
|
|
350
|
+
| Rows | Added | App flush | Outbound delivery | Changed poll | Unchanged poll | Max Gateway request | Result |
|
|
351
|
+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
|
352
|
+
| 1 | 1 | 12.25 ms | 6,304.94 ms | 4,003.09 ms | 1,768.76 ms | 3,510 ms | passed |
|
|
353
|
+
| 10 | 9 | 13.18 ms | 13,100.62 ms | 10,130.85 ms | 2,356.88 ms | 8,121 ms | passed |
|
|
354
|
+
| 25 | 15 | 16.71 ms | 16,375.68 ms | 7,866.09 ms | 2,037.32 ms | 8,462 ms | passed |
|
|
355
|
+
| 66 | 41 | 34.96 ms | 78,406.70 ms | 16,210.92 ms | 1,845.36 ms | 19,087 ms | passed |
|
|
356
|
+
| 100 | 34 | 23.89 ms | 120,235.71 ms | 27,529.99 ms | 1,881.89 ms | 26,694 ms | passed |
|
|
357
|
+
|
|
358
|
+
All stages persisted the expected row count, changed polling applied every
|
|
359
|
+
expected row with one full-metadata escalation and zero conflicts, and unchanged
|
|
360
|
+
polling stayed on the values-only path. The matrix made **47 synchronization
|
|
361
|
+
Gateway requests** (49 including one provisioning and one cleanup request),
|
|
362
|
+
found **0** over 30 seconds, and had no functional or cleanup failure. The 66- and 100-row worker/stage totals exceeded 30 seconds because they
|
|
363
|
+
contained multiple Gateway requests; no individual synchronization Gateway
|
|
364
|
+
request exceeded the threshold.
|
|
365
|
+
|
|
366
|
+
This is a one-row smoke benchmark, not a 66-row throughput result. Network
|
|
367
|
+
latency, Apps Script execution variance, Sheet lock contention, and quota remain
|
|
368
|
+
caveats. The progressive result above is still a single 100-row cumulative run,
|
|
369
|
+
not a repeated production-load benchmark. Historical live comparison baselines
|
|
370
|
+
from earlier branches remain:
|
|
371
|
+
|
|
372
|
+
| Scenario | Setup | No-setup / steady-state result | Source |
|
|
373
|
+
| --- | ---: | ---: | --- |
|
|
374
|
+
| Full snapshot polling, 66 rows | excluded | 27,652 ms steady state | 2026-07-27 phase trace below |
|
|
375
|
+
| Values-only polling, 66 rows | excluded | 2,240 ms steady state | 2026-07-27 lightweight polling below |
|
|
376
|
+
| Outbound 20-order stage | excluded | 28,182 ms / 5.0 rows/s | 2026-07-27 timing run below |
|
|
377
|
+
| Outbound 370-order stage | excluded | 36,865 ms / 30.1 rows/s | 2026-07-27 progressive run below |
|
|
378
|
+
|
|
379
|
+
SQLite remains the application authority and Sheets the asynchronous projection:
|
|
380
|
+
full metadata fidelity, response-loss recovery, and the periodic safety full scan
|
|
381
|
+
stay required while the values-only fast path avoids remote full reads on
|
|
382
|
+
unchanged passes.
|
|
383
|
+
|
|
3
384
|
## 2026-07-24 — raw Apps Script write
|
|
4
385
|
|
|
5
386
|
- Branch: `benchmark/apps-script-bulk-write`
|
|
@@ -1034,3 +1415,142 @@ events or canonical writes.
|
|
|
1034
1415
|
- The result strongly indicates that the current Gateway validation and
|
|
1035
1416
|
metadata path, rather than raw `setValues()` throughput, is the dominant
|
|
1036
1417
|
bottleneck.
|
|
1418
|
+
|
|
1419
|
+
## 2026-08-03 — clean Locust smoke after adaptive-sync implementation
|
|
1420
|
+
|
|
1421
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
1422
|
+
- Command:
|
|
1423
|
+
`locust -f .local/locustfile.py --host http://127.0.0.1:8787 --headless
|
|
1424
|
+
--users 10 --spawn-rate 2 --run-time 60s --csv
|
|
1425
|
+
.local/locust-20260803-231300-clean2 --html
|
|
1426
|
+
.local/locust-20260803-231300-clean2.html --only-summary`
|
|
1427
|
+
- Server: `.local/hikoutei-load-server.mjs`, Node.js 24.3, local SQLite,
|
|
1428
|
+
deployed Apps Script Gateway, fresh run ID `load-20260803-231003-clean2`
|
|
1429
|
+
- Dataset/scenario: fresh SQLite and fresh `System_State`, `User_Input`, and
|
|
1430
|
+
`Sync_Conflicts` projection tabs; 10 mixed Locust users; 2 users/second
|
|
1431
|
+
ramp; 60-second workload window.
|
|
1432
|
+
- No-setup/steady-state scope: server provisioning and startup were excluded;
|
|
1433
|
+
the table below starts after `/health` became ready and includes only the
|
|
1434
|
+
Locust workload window. The separate drain snapshot includes background
|
|
1435
|
+
worker/polling traffic after Locust stopped.
|
|
1436
|
+
|
|
1437
|
+
| Workload | Requests | Failures | p50 | p95 | Maximum | Throughput |
|
|
1438
|
+
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
1439
|
+
| `GET /users/:id` | 134 | 0 (0%) | 2 ms | 4 ms | 6 ms | 2.29/s |
|
|
1440
|
+
| `POST /users` | 81 | 0 (0%) | 5 ms | 6 ms | 19 ms | 1.38/s |
|
|
1441
|
+
| `PATCH /users/:id` | 76 | 0 (0%) | 5 ms | 8 ms | 22 ms | 1.30/s |
|
|
1442
|
+
| `POST /__test/user-input` | 38 | 8 (21.05%) | 2.2 s | 31.0 s | 31.9 s | 0.65/s |
|
|
1443
|
+
| **Aggregated** | **339** | **8 (2.36%)** | **4 ms** | **2.3 s** | **31.9 s** | **5.78/s** |
|
|
1444
|
+
|
|
1445
|
+
The server-side Gateway snapshot after the workload and approximately two
|
|
1446
|
+
minutes of background drain contained 100 Gateway requests, 34 failures, p50
|
|
1447
|
+
3.71 s, p95 33.47 s, and maximum 60.00 s. The failures were dominated by
|
|
1448
|
+
intermittent non-JSON/timeout Gateway responses. The SQLite outbox had 306
|
|
1449
|
+
`pending`, 8 `processing`, 2 `applied`, and 2 `superseded` effects at the
|
|
1450
|
+
snapshot; it had not converged, so this is not a successful drain benchmark.
|
|
1451
|
+
|
|
1452
|
+
Compared with the earlier 2,648-request run (12.61% HTTP failures, Gateway
|
|
1453
|
+
p50 4.34 s, p95 34.25 s, maximum 60.01 s), the clean smoke had a lower HTTP
|
|
1454
|
+
failure rate for entity create/read/update traffic, but its User_Input path
|
|
1455
|
+
still failed and the outbox did not drain. The runs are not a production
|
|
1456
|
+
performance comparison until the Gateway deployment is stable and the
|
|
1457
|
+
background outbox converges under the same workload.
|
|
1458
|
+
|
|
1459
|
+
Artifacts:
|
|
1460
|
+
|
|
1461
|
+
- `.local/locust-20260803-231300-clean2_stats.csv`
|
|
1462
|
+
- `.local/locust-20260803-231300-clean2_failures.csv`
|
|
1463
|
+
- `.local/locust-20260803-231300-clean2.html`
|
|
1464
|
+
- `.local/hikoutei-load-load-20260803-231003-clean2.jsonl`
|
|
1465
|
+
|
|
1466
|
+
Caveats: the Gateway returned intermittent HTTP 404/non-JSON responses and
|
|
1467
|
+
60-second client timeouts during the run; Python 3.9 emitted Locust's
|
|
1468
|
+
end-of-life warning; and the remote deployment was not independently
|
|
1469
|
+
re-deployed during this measurement.
|
|
1470
|
+
|
|
1471
|
+
## 2026-08-03 — Gateway baseline transport gate (stopped)
|
|
1472
|
+
|
|
1473
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
1474
|
+
- Command:
|
|
1475
|
+
`node --env-file=.env --input-type=module <gateway-baseline-probe>`
|
|
1476
|
+
- Exact probe: 100 sequential (`concurrency=1`) signed `applyOperations` calls
|
|
1477
|
+
using a read-only function that returned the bound spreadsheet ID and an
|
|
1478
|
+
integer nonce; no Sheet rows or tabs were created or changed.
|
|
1479
|
+
- Environment: current built `dist/`, configured Apps Script Gateway URL,
|
|
1480
|
+
configured shared secret, configured spreadsheet ID; secrets and IDs are not
|
|
1481
|
+
recorded here.
|
|
1482
|
+
- No-setup/steady-state scope: every request was measured after the client was
|
|
1483
|
+
constructed; there was no application or Sheet setup work in the request
|
|
1484
|
+
loop.
|
|
1485
|
+
|
|
1486
|
+
| Requests | Success | Failure | Failure rate | p50/p95/maximum | Duration |
|
|
1487
|
+
| ---: | ---: | ---: | ---: | --- | ---: |
|
|
1488
|
+
| 100 | 0 | 100 | **100%** | not applicable (all HTTP 405) | 425,373 ms |
|
|
1489
|
+
|
|
1490
|
+
Every failure decoded as `invalid_sync_gateway_response` with HTTP 405 and the
|
|
1491
|
+
message `Code.gs response was not valid JSON`. This exceeds the 1% stop
|
|
1492
|
+
criterion, so no further live sync/load refactor or User_Input migration should
|
|
1493
|
+
be accepted until the deployed Gateway URL, redirect/method preservation,
|
|
1494
|
+
permissions, deployment version, and quota/runtime path are isolated. This
|
|
1495
|
+
probe is diagnostic only and does not establish Sheet convergence.
|
|
1496
|
+
|
|
1497
|
+
Artifact: `.local/gateway-baseline-100-20260804.json`.
|
|
1498
|
+
|
|
1499
|
+
## 2026-08-04 — Gateway baseline transport gate (redeployed, passed)
|
|
1500
|
+
|
|
1501
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
1502
|
+
- Command: ephemeral signed no-op probe using the built
|
|
1503
|
+
`AppsScriptOperationClient`; Gateway credentials were supplied only through
|
|
1504
|
+
process environment and are not recorded.
|
|
1505
|
+
- Exact probe: 100 sequential (`concurrency=1`) signed `applyOperations` calls
|
|
1506
|
+
using the same read-only function as the stopped baseline. No Sheet rows or
|
|
1507
|
+
tabs were created or changed.
|
|
1508
|
+
- Environment: redeployed Apps Script Web App `/exec`, current built `dist/`,
|
|
1509
|
+
60-second client timeout. Setup was excluded from the measured loop.
|
|
1510
|
+
|
|
1511
|
+
| Requests | Success | Failure | Failure rate | p50 | p95 | Maximum | Duration |
|
|
1512
|
+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
1513
|
+
| 100 | 100 | 0 | **0%** | 1,956 ms | 4,511 ms | 20,994 ms | 254,786 ms |
|
|
1514
|
+
|
|
1515
|
+
All responses were valid HTTP 200 JSON envelopes. Every request followed the
|
|
1516
|
+
expected `POST /exec` 302 redirect to the Apps Script `macros/echo` endpoint,
|
|
1517
|
+
which returned HTTP 200. Compared with the previous 100% failure baseline, the
|
|
1518
|
+
redeployment fixed the transport gate. This probe proves transport and signed
|
|
1519
|
+
no-op execution only; it does not establish Sheet write convergence or remove
|
|
1520
|
+
the need for the subsequent single-append, replay, and concurrency checks.
|
|
1521
|
+
|
|
1522
|
+
Artifact: `.local/gateway-baseline-100-20260804-fixed.json`.
|
|
1523
|
+
|
|
1524
|
+
## 2026-08-04 — Code.gs-only write correctness gate (passed)
|
|
1525
|
+
|
|
1526
|
+
- Branch: `perf/adaptive-sync-performance`
|
|
1527
|
+
- Command: `node .local/gateway-write-verification-live.mjs` with Gateway
|
|
1528
|
+
credentials supplied only through ephemeral process environment; credentials
|
|
1529
|
+
are not recorded.
|
|
1530
|
+
- Backend: redeployed Apps Script Web App `/exec`, built `dist/`, no Advanced
|
|
1531
|
+
Sheets Service or manifest activation.
|
|
1532
|
+
- Scenario: create a uniquely named temporary tab, append one row, replay the
|
|
1533
|
+
same effect, submit a same-effect payload mismatch, submit a duplicate
|
|
1534
|
+
identity, simulate response loss after the remote append response, replay the
|
|
1535
|
+
uncertain effect, inspect row/receipt counts, and delete the temporary tab and
|
|
1536
|
+
newly created receipt tab. Setup and cleanup were outside the correctness
|
|
1537
|
+
assertions; no existing user tab was retained or modified.
|
|
1538
|
+
|
|
1539
|
+
| Check | Result |
|
|
1540
|
+
| --- | --- |
|
|
1541
|
+
| Single append | `applied`, receipt-backed visible hash/revision |
|
|
1542
|
+
| Exact replay | `applied`, target rows 2 including header, one receipt per effect |
|
|
1543
|
+
| Payload mismatch | rejected with `operation_failed` |
|
|
1544
|
+
| Duplicate identity | rejected with `operation_failed` |
|
|
1545
|
+
| Simulated response loss | classified as network uncertainty |
|
|
1546
|
+
| Response-loss replay | `applied`, no duplicate target/receipt row |
|
|
1547
|
+
| Cleanup | temporary target and receipt tabs deleted |
|
|
1548
|
+
|
|
1549
|
+
This is the first live write-path verification of the built-in
|
|
1550
|
+
`SpreadsheetApp` implementation. It proves the default single-`Code.gs` path can
|
|
1551
|
+
append, replay, reject unsafe reuse, and recover a lost response without the
|
|
1552
|
+
Advanced Sheets Service. It does not yet establish multi-user throughput or
|
|
1553
|
+
eliminate the documented two-flush crash window between target and receipt
|
|
1554
|
+
writes.
|
|
1555
|
+
|
|
1556
|
+
Artifact: `.local/gateway-write-verification-20260804.json`.
|
|
@@ -10,7 +10,7 @@ API 서버는 JSON 한 줄 로그를 출력한다.
|
|
|
10
10
|
|
|
11
11
|
| 이벤트 | 의미 |
|
|
12
12
|
| --- | --- |
|
|
13
|
-
| `typed_sheets_sync_worker_report` | 한 번의 worker(백그라운드 처리기) pass가 로컬 outbox를 어떻게 처리했는지 보여준다. `selected`, `claimed`, `applied`, `failed`, `requeued`, `responseLossRecovered`, `expiredLeasesRecovered`를 확인한다. |
|
|
13
|
+
| `typed_sheets_sync_worker_report` | 한 번의 worker(백그라운드 처리기) pass가 로컬 outbox를 어떻게 처리했는지 보여준다. `selected`, `claimed`, `applied`, `failed`, `deferred`, `requeued`, `responseLossRecovered`, `expiredLeasesRecovered`를 확인한다. 모호한 전송은 outbox의 `delivery_uncertain`, `uncertain_since`, `next_probe_at`, `dispatch_id`를 함께 확인한다. |
|
|
14
14
|
| `typed_sheets_gateway_request` | Node가 Apps Script에 보낸 하나의 요청이다. `requestId`, `operation`, `effectCount`, `effectIds`, `requestBytes`, `durationMs`, `httpStatus`, `clientErrorCode`, `remoteErrorCode`, `effectStatuses`를 확인한다. |
|
|
15
15
|
| `typed_sheets_sync_worker_error` | worker pass 자체가 예외로 종료된 경우다. |
|
|
16
16
|
|
|
@@ -25,6 +25,33 @@ Apps Script 실행 기록에는 다음 이벤트가 같은 `requestId`로 남는
|
|
|
25
25
|
로그에는 shared secret(공유 비밀키), signature(서명), 셀 값, 전체 payload(요청
|
|
26
26
|
내용)를 기록하지 않는다. `effectId`는 요청 양 끝을 연결하기 위한 식별자만 기록한다.
|
|
27
27
|
|
|
28
|
+
## 폴링 타이밍 단계 (진단 전용)
|
|
29
|
+
|
|
30
|
+
인바운드 `User_Input` 폴링은 진단용 타이밍 싱크(`onTiming`, `scope: polling`)를
|
|
31
|
+
통해 단계별 소요 시간을 내보낸다. 이 값들은 애플리케이션이나 원격 동작에
|
|
32
|
+
영향을 주지 않는 계측 전용 값이며, 서버나 벤치마크가 이 싱크를 연결한 경우에만
|
|
33
|
+
관찰된다. 폴링은 append/update/delete 작업을 수반하지 않으므로 모든 단계는 빈
|
|
34
|
+
작업 종류와 0인 카운트로 보고된다.
|
|
35
|
+
|
|
36
|
+
| 단계 (`scope: polling`) | 의미 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| `canonical_state_read` | 비교 기준이 되는 정규 SQLite 상태를 읽는 구간이다. |
|
|
39
|
+
| `values_only_read` | 적응형 preflight가 값 전용(values-only) 원격 읽기로 변경 후보를 찾는 구간이다. |
|
|
40
|
+
| `fast_comparison` | 값 전용 읽기 결과를 정규 상태와 비교해 전체 메타데이터로 올릴 테이블을 가리는 구간이다. |
|
|
41
|
+
| `full_metadata_observation` | 변경/모호/스키마 사례나 주기적 안전 전수 스캔으로 올라간 테이블의 수식/병합/오류 메타데이터를 보존하는 스냅샷 읽기 구간이다. |
|
|
42
|
+
| `persistence` | 수락된 관측과 엔터티 변경, 격리(quarantine) 행을 SQLite에 기록하는 구간이다. |
|
|
43
|
+
| `polling_total` | 폴링 pass 전체 소요 시간이다. |
|
|
44
|
+
| `safety_scan_lag` | 예정된 안전 전수 스캔이 시작 시점에 얼마나 늦었는지(밀리초) 기록한다. 스캔 실패 전에도 기록된다. |
|
|
45
|
+
|
|
46
|
+
같은 pass의 폴링 보고서는 모드(`mode`), 안전 전수 스캔 여부(`safetyFullScan`),
|
|
47
|
+
전체 메타데이터로 처리한 테이블 수(`fullMetadataTables`), preflight가 훑은/
|
|
48
|
+
변경된 행 수(`fastPathRowsScanned`, `fastPathChangedRows`)와 안전 전수 스캔 지연
|
|
49
|
+
(`safetyScanLagMs`)을 함께 보고한다. `safetyScanLagMs`는 적응형 pass에서는 0이고,
|
|
50
|
+
첫 전수 스캔에는 이전 완료 시점이 없어 0이며, writer lease 경쟁이나 긴 polling
|
|
51
|
+
간격으로 예정 시각을 넘긴 전수 스캔에서 양수로 기록된다.
|
|
52
|
+
`values_only_read`가 짧고 `full_metadata_observation`이 거의 없으면 적응형
|
|
53
|
+
preflight가 원격 전체 읽기를 건너뛰고 있다는 뜻이다.
|
|
54
|
+
|
|
28
55
|
## 판별 순서
|
|
29
56
|
|
|
30
57
|
1. Node 로그에 `typed_sheets_gateway_request` 자체가 없는 effect가 있으면
|
|
@@ -40,8 +67,9 @@ Apps Script 실행 기록에는 다음 이벤트가 같은 `requestId`로 남는
|
|
|
40
67
|
`postcondition_failed` 중 하나면 Apps Script가 응답한 원격 오류다.
|
|
41
68
|
이 경우 `requestId`로 Apps Script 실행 기록을 대조한다.
|
|
42
69
|
4. Apps Script가 `finished`를 `ok: true`로 남겼는데 Node가 timeout이면,
|
|
43
|
-
시트 반영 후 HTTP 응답만 유실됐을 가능성이 있다.
|
|
44
|
-
`
|
|
70
|
+
시트 반영 후 HTTP 응답만 유실됐을 가능성이 있다. outbox가
|
|
71
|
+
`delivery_uncertain`으로 남고 `next_probe_at`이 설정되는지 확인한 뒤,
|
|
72
|
+
due probe의 `readEffectPostcondition(원격 반영 확인)` 결과와
|
|
45
73
|
`responseLossRecovered`를 확인한다.
|
|
46
74
|
5. Apps Script와 Node 양쪽에 `finished`/성공 기록이 있고도 outbox가
|
|
47
75
|
`pending`(대기)으로 남으면, 응답을 로컬 SQLite에 기록하는 단계와
|
|
@@ -65,6 +93,14 @@ node --env-file-if-exists=.local/typed-sheets-api-server/.env \
|
|
|
65
93
|
시트 처리 비용 또는 요청/응답 크기 제한을 의심할 수 있다. 배치 크기와 무관하게
|
|
66
94
|
`lock_timeout`이 반복되면 동시 실행 또는 락 점유 시간을 먼저 확인한다.
|
|
67
95
|
|
|
96
|
+
하나의 `applyEffects` 호출이 인정하는 효과 수는 게이트웨이 배치 한계(현재 20)로
|
|
97
|
+
묶여 있다. 워커가 더 많은 효과를 한 번에 claim해도 각 물리 경로는 이 한계
|
|
98
|
+
단위로 잘려 별도 요청으로 나가므로 요청당 `effectCount`는 이 한계를 넘지
|
|
99
|
+
않는다. 응답 손실이나 사후조건 미반영으로 한 pass가 작업을 requeue만
|
|
100
|
+
반복하면, 즉시 재시도하는 대신 상한이 있는 지터 지연으로 물러나고 정방향
|
|
101
|
+
진행이 회복되면 곧바로 원래 간격으로 돌아온다. 이 동안 임대 만료와 복구는
|
|
102
|
+
효과를 계속 살려 둔다.
|
|
103
|
+
|
|
68
104
|
Apps Script 실행 기록은 해당 프로젝트의 권한이 필요하다. 프로젝트 소유자나
|
|
69
105
|
편집자가 아니라면 라이브러리가 원격 실행 로그를 자동으로 읽을 수 없으므로,
|
|
70
106
|
소유자에게 실행 기록 또는 Cloud Logging(보존형 로그) 접근 권한을 요청하거나
|