@kedem/okdb 1.9.2 → 2.0.0
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 +3 -2
- package/bin/okdb.js +1 -1
- package/docs/deployment.md +155 -0
- package/docs/diagnostics.md +115 -0
- package/docs/embeddings.md +1 -1
- package/docs/functions.md +30 -27
- package/docs/getting-started.md +14 -3
- package/docs/http-api.md +3 -3
- package/docs/http-cluster.md +93 -60
- package/docs/index.md +13 -1
- package/docs/manifest.json +6 -3
- package/docs/pipelines.md +1 -1
- package/docs/process-registry.md +5 -5
- package/docs/processors.md +35 -18
- package/docs/queue.md +112 -42
- package/docs/sync.md +10 -7
- package/docs/ttl.md +1 -1
- package/docs/upgrade-2.0.md +290 -0
- package/okdb-functions-sandbox-worker.js +1 -1
- package/okdb-http-worker-child.js +1 -1
- package/okdb-views-bootstrap-worker.js +1 -1
- package/okdb.js +1 -1
- package/package.json +1 -1
- package/public/layouts/_default.ok.html +1 -1
- package/public/sections/db/modals/create-env-modal.ok.js +1 -1
- package/public/sections/db/modals/demo-env-modal.ok.js +1 -1
- package/public/sections/db/parts/db-overview.ok.js +1 -1
- package/public/sections/engines/engine-ui-utils.js +1 -1
- package/public/sections/engines/parts/engine-declaration-editor.ok.js +1 -1
- package/public/sections/engines/parts/generic-engine-panel.ok.js +1 -1
- package/public/sections/queue/parts/code-panel.ok.js +1 -1
- package/public/sections/queue/parts/job-log-panel.ok.js +1 -1
- package/public/sections/queue/parts/queue-jobs.ok.js +1 -1
- package/public/sections/sync/parts/sync-topology.ok.js +1 -1
- package/public/sections/system/index.ok.html +1 -1
- package/public/sections/system/parts/system-process-panel.ok.js +1 -1
- package/public/sections/system/parts/system-processing-panel.ok.js +1 -1
- package/public/sections/system/parts/system-runtime-overview.ok.js +1 -1
- package/public/setup-app.js +1 -1
- package/public/setup.html +116 -55
- package/types/index.d.ts +33 -3
- package/types/options.d.ts +19 -10
- package/docs/worker-fleet.md +0 -139
- package/okdb-functions-runner-child.js +0 -1
- package/okdb-queue-load-handler.js +0 -1
- package/okdb-queue-spawn-child.js +0 -1
- package/okdb-worker-child.js +0 -1
- package/public/sections/system/parts/system-workers-panel.ok.js +0 -1
package/public/setup.html
CHANGED
|
@@ -44,6 +44,9 @@
|
|
|
44
44
|
--accent2:#16a34a;
|
|
45
45
|
}
|
|
46
46
|
* { box-sizing:border-box; }
|
|
47
|
+
/* Author rule for .field/.check sets display, which beats the UA [hidden]
|
|
48
|
+
rule — so make hidden authoritative for everything on this page. */
|
|
49
|
+
[hidden] { display:none !important; }
|
|
47
50
|
body {
|
|
48
51
|
margin:0;
|
|
49
52
|
min-height:100vh;
|
|
@@ -59,13 +62,13 @@
|
|
|
59
62
|
background: radial-gradient(circle at top, #c8dff5 0%, var(--bg0) 45%, #e8f2ff 100%);
|
|
60
63
|
}
|
|
61
64
|
.shell {
|
|
62
|
-
width:min(
|
|
65
|
+
width:min(440px, 100%);
|
|
63
66
|
display:flex;
|
|
64
67
|
flex-direction:column;
|
|
65
68
|
gap:0;
|
|
66
69
|
}
|
|
67
70
|
.header {
|
|
68
|
-
margin-bottom:
|
|
71
|
+
margin-bottom:24px;
|
|
69
72
|
text-align:center;
|
|
70
73
|
}
|
|
71
74
|
.eyebrow {
|
|
@@ -73,13 +76,8 @@
|
|
|
73
76
|
background:rgba(79,193,255,.12); color:var(--accent); font-size:12px; letter-spacing:.08em; text-transform:uppercase;
|
|
74
77
|
border:1px solid rgba(79,193,255,.18);
|
|
75
78
|
}
|
|
76
|
-
h1 { font-size:
|
|
79
|
+
h1 { font-size:30px; line-height:1.1; margin:14px 0 6px; }
|
|
77
80
|
.subtitle { color:var(--muted); font-size:15px; margin:0; }
|
|
78
|
-
.panels {
|
|
79
|
-
display:grid;
|
|
80
|
-
grid-template-columns:1fr 1fr;
|
|
81
|
-
gap:16px;
|
|
82
|
-
}
|
|
83
81
|
.panel {
|
|
84
82
|
background:rgba(8,16,26,.86);
|
|
85
83
|
border:1px solid rgba(79,193,255,.16);
|
|
@@ -111,6 +109,24 @@
|
|
|
111
109
|
background:linear-gradient(135deg, var(--accent2), #a8f0b4);
|
|
112
110
|
box-shadow:0 10px 30px rgba(126,231,135,.18);
|
|
113
111
|
}
|
|
112
|
+
/* Secondary, low-emphasis action (e.g. the "Join a cluster" entry point) */
|
|
113
|
+
.button.ghost {
|
|
114
|
+
background:transparent; color:var(--muted); border:1px solid rgba(255,255,255,.12);
|
|
115
|
+
box-shadow:none; font-weight:600; font-size:14px; padding:12px 16px;
|
|
116
|
+
}
|
|
117
|
+
.button.ghost:hover { filter:none; color:var(--text); border-color:rgba(79,193,255,.4); }
|
|
118
|
+
[data-theme='light'] .button.ghost { border-color:var(--line); }
|
|
119
|
+
.divider {
|
|
120
|
+
display:flex; align-items:center; gap:12px; color:var(--muted); font-size:12px;
|
|
121
|
+
text-transform:uppercase; letter-spacing:.08em;
|
|
122
|
+
}
|
|
123
|
+
.divider::before, .divider::after { content:''; flex:1; height:1px; background:rgba(255,255,255,.1); }
|
|
124
|
+
[data-theme='light'] .divider::before, [data-theme='light'] .divider::after { background:var(--line); }
|
|
125
|
+
.back {
|
|
126
|
+
align-self:flex-start; background:none; border:none; cursor:pointer; font-family:inherit;
|
|
127
|
+
color:var(--muted); font-size:13px; padding:0; margin-bottom:10px;
|
|
128
|
+
}
|
|
129
|
+
.back:hover { color:var(--text); }
|
|
114
130
|
.error {
|
|
115
131
|
padding:10px 12px; border-radius:10px;
|
|
116
132
|
background:rgba(248,113,113,.12); border:1px solid rgba(248,113,113,.25); color:#fecaca;
|
|
@@ -121,11 +137,24 @@
|
|
|
121
137
|
background:rgba(126,231,135,.1); border:1px solid rgba(126,231,135,.25); color:#a8f0b4;
|
|
122
138
|
font-size:13px;
|
|
123
139
|
}
|
|
124
|
-
@media (max-width:720px) {
|
|
125
|
-
.panels { grid-template-columns:1fr; }
|
|
126
|
-
}
|
|
127
140
|
[data-theme='light'] .panel { background: rgba(255,255,255,.92); border-color: rgba(26,122,184,.2); }
|
|
128
141
|
[data-theme='light'] .field input { background: var(--bg2); border-color: var(--line); }
|
|
142
|
+
|
|
143
|
+
/* Segmented credential toggle */
|
|
144
|
+
.segmented {
|
|
145
|
+
display:flex; border:1px solid rgba(255,255,255,.1); border-radius:12px; overflow:hidden;
|
|
146
|
+
}
|
|
147
|
+
.segmented button {
|
|
148
|
+
flex:1; background:transparent; color:var(--muted); border:none; cursor:pointer;
|
|
149
|
+
font-family:inherit; font-size:13px; font-weight:600; padding:10px 8px; transition:color .15s, background .15s;
|
|
150
|
+
}
|
|
151
|
+
.segmented button + button { border-left:1px solid rgba(255,255,255,.08); }
|
|
152
|
+
.segmented button.seg-active { background:#0b1420; color:var(--text); box-shadow:inset 0 0 0 1px rgba(79,193,255,.35); }
|
|
153
|
+
[data-theme='light'] .segmented { border-color:var(--line); }
|
|
154
|
+
[data-theme='light'] .segmented button.seg-active { background:var(--bg2); }
|
|
155
|
+
.check { display:flex; align-items:flex-start; gap:9px; font-size:13px; color:var(--muted); cursor:pointer; line-height:1.45; }
|
|
156
|
+
.check input { margin-top:2px; cursor:pointer; accent-color:var(--accent2); }
|
|
157
|
+
.hint { font-size:12px; color:var(--muted); line-height:1.5; margin:0; }
|
|
129
158
|
</style>
|
|
130
159
|
</head>
|
|
131
160
|
<body>
|
|
@@ -133,57 +162,89 @@
|
|
|
133
162
|
<header class="header">
|
|
134
163
|
<div class="eyebrow">OKDB First-Time Setup</div>
|
|
135
164
|
<h1>Welcome to OKDB</h1>
|
|
136
|
-
<p class="subtitle">This node has no credentials yet. Create an admin
|
|
165
|
+
<p class="subtitle">This node has no credentials yet. Create an admin to get started.</p>
|
|
137
166
|
</header>
|
|
138
167
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
168
|
+
<!-- Create admin (default view) -->
|
|
169
|
+
<section class="panel" id="createView">
|
|
170
|
+
<div>
|
|
171
|
+
<p class="panel-title">Create your first admin</p>
|
|
172
|
+
<p class="panel-desc">Set up credentials for this node. You'll log in with these after setup.</p>
|
|
173
|
+
</div>
|
|
174
|
+
<form id="createForm" class="stack">
|
|
175
|
+
<div class="field">
|
|
176
|
+
<label for="createName">Username</label>
|
|
177
|
+
<input id="createName" name="name" value="admin" autocomplete="username" placeholder="admin">
|
|
144
178
|
</div>
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
</form>
|
|
165
|
-
</section>
|
|
179
|
+
<div class="field">
|
|
180
|
+
<label for="createPassword">Password</label>
|
|
181
|
+
<input id="createPassword" name="password" type="password" autocomplete="new-password" placeholder="••••••••">
|
|
182
|
+
</div>
|
|
183
|
+
<div class="field">
|
|
184
|
+
<label for="createConfirm">Confirm password</label>
|
|
185
|
+
<input id="createConfirm" name="confirm" type="password" autocomplete="new-password" placeholder="••••••••">
|
|
186
|
+
</div>
|
|
187
|
+
<div class="field">
|
|
188
|
+
<label for="createLabel">Label <span style="color:var(--muted)">(optional)</span></label>
|
|
189
|
+
<input id="createLabel" name="label" value="Initial admin" placeholder="Initial admin">
|
|
190
|
+
</div>
|
|
191
|
+
<div id="createError" class="error" hidden></div>
|
|
192
|
+
<button type="submit" class="button">Create admin and continue</button>
|
|
193
|
+
</form>
|
|
194
|
+
|
|
195
|
+
<div class="divider"><span>or</span></div>
|
|
196
|
+
<button type="button" id="showJoin" class="button ghost">Join an existing cluster</button>
|
|
197
|
+
</section>
|
|
166
198
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
199
|
+
<!-- Join cluster (revealed on demand) -->
|
|
200
|
+
<section class="panel" id="joinView" hidden>
|
|
201
|
+
<div>
|
|
202
|
+
<button type="button" id="backToCreate" class="back">← Back</button>
|
|
203
|
+
<p class="panel-title">Join an existing cluster</p>
|
|
204
|
+
<p class="panel-desc">Connect to a running OKDB node. The credential is used once for the handshake — after that, sync authenticates with Ed25519 keys.</p>
|
|
205
|
+
</div>
|
|
206
|
+
<form id="joinForm" class="stack">
|
|
207
|
+
<div class="field">
|
|
208
|
+
<label for="joinAddress">Remote node address</label>
|
|
209
|
+
<input id="joinAddress" name="address" placeholder="http://node-a.example.com:8484">
|
|
171
210
|
</div>
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
<
|
|
179
|
-
<input id="joinToken" name="token" type="password" placeholder="cluster sync token">
|
|
211
|
+
|
|
212
|
+
<div class="field">
|
|
213
|
+
<label>Authentication</label>
|
|
214
|
+
<div class="segmented" id="joinAuthSeg">
|
|
215
|
+
<button type="button" data-method="token" class="seg-active">Token</button>
|
|
216
|
+
<button type="button" data-method="login">Login</button>
|
|
217
|
+
<button type="button" data-method="none">None</button>
|
|
180
218
|
</div>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
<div class="field" data-auth="token">
|
|
222
|
+
<label for="joinToken">Cluster sync token</label>
|
|
223
|
+
<input id="joinToken" name="token" type="password" placeholder="Bearer token from the cluster admin">
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<div class="field" data-auth="login" hidden>
|
|
227
|
+
<label for="joinUsername">Username</label>
|
|
228
|
+
<input id="joinUsername" name="username" autocomplete="username" placeholder="Username on the remote node">
|
|
229
|
+
<label for="joinPassword" style="margin-top:6px;">Password</label>
|
|
230
|
+
<input id="joinPassword" name="password" type="password" autocomplete="current-password" placeholder="••••••••">
|
|
231
|
+
</div>
|
|
232
|
+
|
|
233
|
+
<div class="field" data-auth="none" hidden>
|
|
234
|
+
<p class="hint">No credential is sent. This only works when the remote node is running in open mode.</p>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<label class="check">
|
|
238
|
+
<input type="checkbox" id="joinDataLink" checked>
|
|
239
|
+
<span>Sync data immediately <span style="opacity:.6">— unchecked links topology only</span></span>
|
|
240
|
+
</label>
|
|
241
|
+
|
|
242
|
+
<div id="joinError" class="error" hidden></div>
|
|
243
|
+
<button type="submit" class="button green">Join cluster</button>
|
|
244
|
+
</form>
|
|
245
|
+
</section>
|
|
186
246
|
</main>
|
|
247
|
+
|
|
187
248
|
<script type="module" src="setup-app.js"></script>
|
|
188
249
|
</body>
|
|
189
250
|
</html>
|
package/types/index.d.ts
CHANGED
|
@@ -74,7 +74,12 @@ export declare class OKDB {
|
|
|
74
74
|
readonly events: EventEmitter;
|
|
75
75
|
|
|
76
76
|
// ── Subsystems ────────────────────────────────────────────────────────────
|
|
77
|
-
readonly http:
|
|
77
|
+
readonly http: {
|
|
78
|
+
/** Start the built-in HTTP server (REST API + admin UI). Single-process only —
|
|
79
|
+
* a `{ workers }` second argument throws HTTP_CLUSTER_REMOVED (okdb 2.0). */
|
|
80
|
+
listen(port: number, host?: string): unknown;
|
|
81
|
+
[key: string]: unknown;
|
|
82
|
+
};
|
|
78
83
|
readonly auth: OKDBAuth;
|
|
79
84
|
readonly log: unknown;
|
|
80
85
|
readonly bus: unknown;
|
|
@@ -100,12 +105,37 @@ export declare class OKDB {
|
|
|
100
105
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
|
101
106
|
open(): Promise<void>;
|
|
102
107
|
close(): Promise<void>;
|
|
108
|
+
/** Resolved role flags. `engines`/`compaction` are fixed for the instance's
|
|
109
|
+
* lifetime; `processors` reflects CURRENT participation (see processors.start/stop). */
|
|
110
|
+
readonly role: { readonly processors: boolean; readonly engines: boolean; readonly compaction: boolean };
|
|
111
|
+
/** Composite load signal for this node (cached 250 ms; safe to poll per claim).
|
|
112
|
+
* score ≥ 1 ≈ at the documented distress limits — the standard admission policy is
|
|
113
|
+
* `() => db.pressure().score < 1`. queuePending is workload context, not in the score. */
|
|
114
|
+
pressure(): {
|
|
115
|
+
writerStallMs: number;
|
|
116
|
+
writerDepth: number;
|
|
117
|
+
maxDurableLag: number;
|
|
118
|
+
queuePending: number;
|
|
119
|
+
loopLagMs: number;
|
|
120
|
+
score: number;
|
|
121
|
+
};
|
|
122
|
+
/** Processing participation control + status.
|
|
123
|
+
* start(): begin participating — un-gate lease claiming; the first call also runs
|
|
124
|
+
* the per-store view boot that open() skips on a non-participating node.
|
|
125
|
+
* stop(): cease participating — finish in-flight quanta, release leases (peers
|
|
126
|
+
* fail over), park registrations. Both idempotent; resolve true on state change. */
|
|
127
|
+
readonly processors: {
|
|
128
|
+
start(): Promise<boolean>;
|
|
129
|
+
stop(): Promise<boolean>;
|
|
130
|
+
status(): { participate: boolean; processors: unknown[] };
|
|
131
|
+
[key: string]: unknown;
|
|
132
|
+
};
|
|
103
133
|
|
|
104
134
|
// ── Environment management ────────────────────────────────────────────────
|
|
105
135
|
readonly default: OKDBEnvironment;
|
|
106
136
|
env(name?: string): OKDBEnvironment;
|
|
107
|
-
/** Open a registered env on demand (
|
|
108
|
-
*
|
|
137
|
+
/** Open a registered env on demand (envs always open lazily in okdb 2.0).
|
|
138
|
+
* Idempotent; throws ENV_NOT_FOUND for an unregistered name. */
|
|
109
139
|
openEnv(name: string): Promise<OKDBEnvironment>;
|
|
110
140
|
createEnvironment(name: string, config?: OKDBEnvironmentConfig): Promise<OKDBEnvironment>;
|
|
111
141
|
removeEnvironment(name: string, options?: { keepSubEnvs?: string[] }): Promise<OKDBRemoveEnvironmentResult>;
|
package/types/options.d.ts
CHANGED
|
@@ -18,7 +18,6 @@ export interface OKDBOptions {
|
|
|
18
18
|
auth?: OKDBAuthOptions;
|
|
19
19
|
sync?: OKDBSyncOptions;
|
|
20
20
|
queue?: OKDBQueueOptions;
|
|
21
|
-
bus?: boolean | Record<string, unknown>;
|
|
22
21
|
functions?: Record<string, unknown>;
|
|
23
22
|
timeMachine?: OKDBTimeMachineOptions;
|
|
24
23
|
runtimeDeclarations?: boolean;
|
|
@@ -28,14 +27,25 @@ export interface OKDBOptions {
|
|
|
28
27
|
* Defaults preserve full-role behavior; passive instances serve reads/writes
|
|
29
28
|
* but skip engines / async processors / compaction scheduling. */
|
|
30
29
|
engines?: boolean;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* (
|
|
36
|
-
*
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
/** okdb 2.0: INITIAL processing participation (claim every unclaimed `single` lease
|
|
31
|
+
* and drain derived work — FTS, views, time-machine, embeddings). Default true.
|
|
32
|
+
* false = not participating: claim nothing; reads/writes + inline processors still
|
|
33
|
+
* run. Participation is dynamic — db.processors.start()/stop() flip it at runtime
|
|
34
|
+
* (open with false + start() later = fast startup). Pass false for ephemeral
|
|
35
|
+
* short-lived processes. */
|
|
36
|
+
processors?: boolean;
|
|
37
|
+
/** okdb 2.0: true = eligible to claim the per-env compaction lease,
|
|
38
|
+
* false = passive (explicit env.compact() still works). */
|
|
39
|
+
compaction?: boolean;
|
|
40
|
+
/** Process-registry participation. false = invisible non-participant;
|
|
41
|
+
* { name } labels this process in the admin's process list. */
|
|
42
|
+
processes?: boolean | { name?: string; kind?: string; register?: boolean; listenAddr?: string | null };
|
|
43
|
+
/** Auto-compaction thresholds; { enabled: false } (or OKDB_AUTO_COMPACT=0) disables. */
|
|
44
|
+
autoCompact?: { enabled?: boolean; [key: string]: unknown };
|
|
45
|
+
/** HTTP API options (e.g. defaultEnv for routes that omit :env). */
|
|
46
|
+
api?: { defaultEnv?: string; [key: string]: unknown };
|
|
47
|
+
/** Admin UI feature; false disables it entirely. */
|
|
48
|
+
admin?: boolean | Record<string, unknown>;
|
|
39
49
|
}
|
|
40
50
|
|
|
41
51
|
export interface OKDBAuthOptions {
|
|
@@ -90,7 +100,6 @@ export interface OKDBTimeMachineOptions {
|
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
export interface OKDBEnvironmentConfig {
|
|
93
|
-
sync?: boolean;
|
|
94
103
|
durability?: 'strict' | 'balanced' | 'fast' | 'custom';
|
|
95
104
|
lmdb?: Record<string, unknown>;
|
|
96
105
|
compression?: boolean;
|
package/docs/worker-fleet.md
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# Workers — background-work placement over the root
|
|
2
|
-
|
|
3
|
-
The worker population is OKDB's **inter-process placement layer**: a set of
|
|
4
|
-
generic worker processes forked over one LMDB root that claim and execute
|
|
5
|
-
background work — processor drains, queue jobs, function runs, and (FTS)
|
|
6
|
-
snapshot bootstraps. There is no intra-process executor (the thread/process
|
|
7
|
-
pool was removed in the runtime-model rework, P4): whoever holds a claim
|
|
8
|
-
drains it **on its own loop in bounded quanta**, yielding between chunks.
|
|
9
|
-
|
|
10
|
-
> **See also:** [Processors](processors.md) for the change-subscription **modes**
|
|
11
|
-
> (`single`/`fanout`/`inline`), registration, and bootstrap — the _guarantee_ each
|
|
12
|
-
> processor declares. This doc covers _where_ that work runs (population & placement);
|
|
13
|
-
> processors.md covers _what_ a processor is.
|
|
14
|
-
|
|
15
|
-
## Quick start
|
|
16
|
-
|
|
17
|
-
```javascript
|
|
18
|
-
const db = new OKDB('./data');
|
|
19
|
-
await db.open();
|
|
20
|
-
|
|
21
|
-
// Declare workers desired state. Root-keyed and idempotent: any instance may call
|
|
22
|
-
// it any number of times; one population results.
|
|
23
|
-
db.workers.ensure({ scale: 'auto' }); // default: min 0, backlog-driven
|
|
24
|
-
|
|
25
|
-
// Tear down: scale:0 persists across restarts.
|
|
26
|
-
await db.workers.destroy();
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
**Zero-config embedded behavior is unchanged** — no `ensure()` call means the
|
|
30
|
-
instance self-processes exactly as before (end-of-tick async on its own loop,
|
|
31
|
-
leases held uncontended).
|
|
32
|
-
|
|
33
|
-
## `workers.ensure({scale})`
|
|
34
|
-
|
|
35
|
-
| `scale` value | Meaning |
|
|
36
|
-
| ------------------ | --------------------------------------------------------------------------------------- |
|
|
37
|
-
| `'auto'` (default) | Supervisor runs; starts at 0 workers; forks on backlog, retires to 0 when idle |
|
|
38
|
-
| `{ min, max }` | Supervisor runs; always keeps at least `min` workers; elastic ceiling `max` |
|
|
39
|
-
| `0` | Hard off — no supervisor, no workers (serverless, test runners, PID-limited containers) |
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
// Auto-scaling (recommended default)
|
|
43
|
-
db.workers.ensure({ scale: 'auto' });
|
|
44
|
-
|
|
45
|
-
// Fixed floor with elastic ceiling
|
|
46
|
-
db.workers.ensure({ scale: { min: 2, max: 8 } });
|
|
47
|
-
|
|
48
|
-
// Hard off
|
|
49
|
-
db.workers.ensure({ scale: 0 });
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Workers are **homogeneous and anonymous**: every worker handles every job class
|
|
53
|
-
(processing drains, queue definitions, function runs, bootstrap jobs). Any
|
|
54
|
-
worker may claim any work unit — the claim IS the dispatcher.
|
|
55
|
-
|
|
56
|
-
## Deployment shapes
|
|
57
|
-
|
|
58
|
-
| shape | how |
|
|
59
|
-
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
60
|
-
| embedded, self-process | default — nothing to configure |
|
|
61
|
-
| embedded + workers | `db.workers.ensure({scale:'auto'})` — instance stops self-claiming when workers are up; resumes when they retire |
|
|
62
|
-
| embedded + HTTP workers | `db.http.listen(port, { workers: N })` after `open()` — forks okdb's own passive HTTP worker entry (child-entry mode; your app never re-executes; built-in routes only) |
|
|
63
|
-
| CLI server | HTTP cluster (primary + N HTTP workers) is the `bin/okdb` default; add workers alongside |
|
|
64
|
-
|
|
65
|
-
## How placement works
|
|
66
|
-
|
|
67
|
-
- **`processing: 'auto'`** (the default) resolves against the workers
|
|
68
|
-
desired-state:
|
|
69
|
-
- `scale:0` or no workers → self-process (today's behavior)
|
|
70
|
-
- `scale:'auto'` at 0 live workers → self-process; forks first worker → stop claiming; population retires to 0 → resume self-claiming
|
|
71
|
-
- `scale:{min,max}` → stop claiming immediately (workers are starting up)
|
|
72
|
-
A configured-but-dead population is **re-forked** by any `'auto'` instance
|
|
73
|
-
(recovery restores separation rather than absorbing the work).
|
|
74
|
-
- Explicit `processing: 'main'` self-processes regardless of workers;
|
|
75
|
-
`'none'` never processes. The legacy `'threads'`/`'processes'` values warn
|
|
76
|
-
once and map to `'auto'` (the executor they selected was removed in P4).
|
|
77
|
-
- **Supervisor**: a per-root `OKDBLock` lease elects one forker. The holder
|
|
78
|
-
forks/retires children to match desired-state, respawns crashes (backoff
|
|
79
|
-
250 ms→4 s, crash-loop guard 5-in-10 s), and recycles wedged workers (stats
|
|
80
|
-
heartbeat silent > 3× interval → SIGKILL + respawn). Supervisor death → its
|
|
81
|
-
children die with it (parent-death watch); any surviving instance acquires the
|
|
82
|
-
lease and re-forks from durable state.
|
|
83
|
-
- **Shmbuf hard gate**: `workers.ensure({ scale: non-zero })` throws
|
|
84
|
-
`WORKERS_SHMBUF_REQUIRED` when the shmbuf native binding is missing (the
|
|
85
|
-
fallback shim is process-local; a multi-process write topology would corrupt
|
|
86
|
-
HLC/clock ordering). A persisted desired > 0 read by a fallback-mode instance
|
|
87
|
-
is treated as 0 + a warning.
|
|
88
|
-
|
|
89
|
-
## Job classes
|
|
90
|
-
|
|
91
|
-
| class | mechanism |
|
|
92
|
-
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
-
| `processing` | the existing per-(env,type) lease + retry; bus `POKE {env,type}` wakes the holder |
|
|
94
|
-
| `queue` | the verb is the placement: `queue.process(type, handler)` — in-process consumer; `queue.worker(type, module)` — the SHARED POOL (durable definition, declarative: no workers yet → jobs wait until `workers.ensure()`); `queue.spawn(type, module)` — dedicated forked child |
|
|
95
|
-
| `functions` | durable request/response rows (`~fn:requests`/`~fn:responses`) + PROC bus fast path (`OKDB_FN_LEGACY_POOL=1` forces the legacy runner pool) |
|
|
96
|
-
| `bootstrap` | snapshot bootstraps route as one-shot worker jobs (v1: FTS only — durable per-index meta); ineligible/closure bootstraps run owner-local |
|
|
97
|
-
|
|
98
|
-
## Admission & elasticity
|
|
99
|
-
|
|
100
|
-
Per worker: `maxLeases` (default 32, `OKDB_WORKERS_MAX_LEASES`), loop-lag gate —
|
|
101
|
-
p99 > 200 ms (`OKDB_WORKERS_LAG_GATE_MS`) for 2 windows → stop claiming (resume
|
|
102
|
-
below half the gate for 2 windows).
|
|
103
|
-
|
|
104
|
-
The supervisor probes every `OKDB_WORKERS_SUPERVISE_MS` (default 10 s) and
|
|
105
|
-
scales on four backlog signals, **all read from durable state** (never this
|
|
106
|
-
instance's in-memory processor lag, which is self-cancelling on a self-claiming
|
|
107
|
-
supervisor and inflates on a claim-gated one):
|
|
108
|
-
|
|
109
|
-
- changelog lag — per-type heads vs the persisted `~proc:state` cursors
|
|
110
|
-
(high `OKDB_WORKERS_LAG_HIGH` default 10 000, low `OKDB_WORKERS_LAG_LOW`
|
|
111
|
-
default 1 000)
|
|
112
|
-
- queue depth (`OKDB_WORKERS_QUEUE_HIGH` default 100, `_LOW` default 10)
|
|
113
|
-
- function backlog — pending `~fn:requests` rows (`OKDB_WORKERS_FN_HIGH`
|
|
114
|
-
default 10)
|
|
115
|
-
- pending routed-bootstrap job rows (any pending row counts as backlog)
|
|
116
|
-
|
|
117
|
-
Scale-up respects `maxWorkers` (for `'auto'`: cores−1 or
|
|
118
|
-
`OKDB_WORKERS_AUTO_MAX`) and holds at +1 until the last-forked worker comes
|
|
119
|
-
online; scale-down floor is `minWorkers` (0 for `'auto'` — the population fully
|
|
120
|
-
retires when idle). Cooldown: `OKDB_WORKERS_SCALE_COOLDOWN_MS` default 5 000 ms.
|
|
121
|
-
|
|
122
|
-
## Ops
|
|
123
|
-
|
|
124
|
-
- Workers are registry citizens: `kind:'worker'` rows under their supervisor,
|
|
125
|
-
with memory/CPU stats **and their processor states** (reported over the
|
|
126
|
-
stats IPC channel) — the registry's `processorsView()` shows the
|
|
127
|
-
claim-holder's row as the truth for each 1-of-N key, so the UI says
|
|
128
|
-
"online on worker pid N", not "paused". Decommission/recommission/kill route
|
|
129
|
-
through the supervisor's reconcile loop (durable slot records).
|
|
130
|
-
- Status honesty: a `processing:'auto'` instance that released its claims
|
|
131
|
-
reports `claimGated: true` (rendered as `delegated`, never `paused`) and
|
|
132
|
-
`heldBy: {pid, hostname}` from the live lease record; a worker-routed
|
|
133
|
-
bootstrap surfaces `progress: {phase:'bootstrap', placement:'worker',
|
|
134
|
-
state, claimedBy}`.
|
|
135
|
-
- Per-drain deadlines (`drainDeadlineMs`, default 60 s) mark a slow processor
|
|
136
|
-
errored (lease retained, in-place retry); a truly wedged worker (silent stats)
|
|
137
|
-
is recycled by the supervisor.
|
|
138
|
-
- Runtime mode switching: `processor.setMode(logicalKey, mode)` /
|
|
139
|
-
`POST /api/processors/:logicalKey/mode` — see `docs/processors.md`.
|