Skip to content

Commit 87902da

Browse files
committed
Alignment
1 parent 7348286 commit 87902da

1 file changed

Lines changed: 208 additions & 0 deletions

File tree

docs/server-architecture.md

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
# JsonDocStore Server Sketch
2+
3+
This branch explores a server-based architecture for `jsondocstore`.
4+
5+
## Goal
6+
7+
- Keep one JSON file per document.
8+
- Keep indexes on disk.
9+
- Expose the store through a small native server process.
10+
- Let Python and JavaScript use thin client libraries instead of native bindings.
11+
12+
## High-Level Model
13+
14+
Components:
15+
16+
- `jsondocstored`: native server, ideally written in C
17+
- Python client package
18+
- JavaScript client package
19+
20+
The server owns:
21+
22+
- document reads and writes
23+
- index maintenance
24+
- locking
25+
- crash recovery
26+
27+
Clients only send requests and decode responses.
28+
29+
## On-Disk Layout
30+
31+
```text
32+
store/
33+
docs/
34+
user-1.json
35+
user-2.json
36+
indexes/
37+
role.idx
38+
email.idx
39+
meta/
40+
schema.json
41+
journal.log
42+
lock
43+
```
44+
45+
Notes:
46+
47+
- `docs/` remains the source of truth for document contents.
48+
- `indexes/` stores persistent secondary indexes.
49+
- `meta/schema.json` defines indexed fields and format version.
50+
- `meta/journal.log` supports recovery after crashes.
51+
52+
## Transport
53+
54+
Preferred order:
55+
56+
1. Unix domain socket for local usage
57+
2. TCP for optional remote usage
58+
59+
A local socket keeps the first version simpler and safer than exposing TCP by default.
60+
61+
## Wire Protocol
62+
63+
Use newline-delimited JSON messages.
64+
65+
Request examples:
66+
67+
```json
68+
{"op":"get","key":"user-1"}
69+
{"op":"insert","key":"user-1","doc":{"username":"alice","role":"admin"}}
70+
{"op":"update","key":"user-1","doc":{"username":"alice","role":"user"}}
71+
{"op":"delete","key":"user-1"}
72+
{"op":"query_by","field":"role","value":"admin"}
73+
{"op":"create_index","field":"role"}
74+
{"op":"delete_index","field":"role"}
75+
{"op":"list"}
76+
{"op":"list_indexes"}
77+
```
78+
79+
Response examples:
80+
81+
```json
82+
{"ok":true,"result":{"username":"alice","role":"admin"}}
83+
{"ok":true,"result":{"user-1":{"username":"alice","role":"admin"}}}
84+
{"ok":false,"error":"Document not found: user-1"}
85+
```
86+
87+
Reasons for this choice:
88+
89+
- easy to debug
90+
- trivial in Python and JavaScript
91+
- no custom binary framing needed at first
92+
93+
## Process Model
94+
95+
First version:
96+
97+
- one server process per store
98+
- single writer
99+
- requests handled sequentially
100+
101+
That is enough to guarantee consistent document and index updates without introducing concurrency complexity too early.
102+
103+
Later improvements:
104+
105+
- concurrent readers
106+
- request queue
107+
- worker threads
108+
109+
## Operations
110+
111+
`insert`
112+
113+
1. validate key
114+
2. validate document
115+
3. compute indexed values
116+
4. append intent to journal
117+
5. write `docs/<key>.json` atomically
118+
6. update affected index files
119+
7. mark journal entry committed
120+
121+
`update`
122+
123+
1. read current document
124+
2. compute old and new indexed values
125+
3. append intent to journal
126+
4. rewrite `docs/<key>.json` atomically
127+
5. update only affected index entries
128+
6. mark journal entry committed
129+
130+
`delete`
131+
132+
1. read current document
133+
2. append intent to journal
134+
3. remove index entries
135+
4. delete the JSON file
136+
5. mark journal entry committed
137+
138+
## Indexing
139+
140+
For each indexed field, persist:
141+
142+
- field value
143+
- document key
144+
145+
Conceptually:
146+
147+
```text
148+
role=admin -> [user-1, user-7]
149+
role=user -> [user-2, user-3]
150+
```
151+
152+
The first implementation can use one index file per field. The server should still update only the affected field entries during `insert`, `update`, and `delete`.
153+
154+
## Recovery
155+
156+
On startup:
157+
158+
1. acquire store lock
159+
2. load schema
160+
3. inspect journal
161+
4. repair or roll forward incomplete operations
162+
5. open socket and accept requests
163+
164+
Also provide:
165+
166+
- `rebuild_indexes`
167+
- `verify_store`
168+
169+
Those are maintenance commands, not normal client operations.
170+
171+
## Client Libraries
172+
173+
Python package:
174+
175+
- `pip install jsondocstore`
176+
- thin client over socket communication
177+
178+
JavaScript package:
179+
180+
- `npm install jsondocstore`
181+
- thin client over the same protocol
182+
183+
Both clients should mirror the same API shape:
184+
185+
- `get(key)`
186+
- `insert(key, doc)`
187+
- `update(key, doc)`
188+
- `delete(key)`
189+
- `queryBy(field, value)`
190+
- `createIndex(field)`
191+
- `deleteIndex(field)`
192+
- `list()`
193+
- `listIndexes()`
194+
195+
## Why This Direction
196+
197+
Pros:
198+
199+
- one native implementation
200+
- easier Python and JavaScript distribution
201+
- one document per JSON file remains intact
202+
- indexes can be persistent and real
203+
204+
Cons:
205+
206+
- requires a running process
207+
- local deployment is more operationally complex than an embedded library
208+
- protocol and process lifecycle become part of the product

0 commit comments

Comments
 (0)