Skip to content

Commit 5f05342

Browse files
authored
Merge pull request #14 from sochdb/release/0.4.8
Release 0.4.8 - Concurrent embedded mode
2 parents 7f658ee + b9667e6 commit 5f05342

File tree

3 files changed

+178
-6
lines changed

3 files changed

+178
-6
lines changed

README.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ Choose the deployment mode that fits your needs.
3838
- ✅ Edge deployments without network
3939
- ✅ No server setup required
4040

41+
**Embedded Concurrent Mode (NEW in v0.4.8):**
42+
- ✅ Web applications (Flask, FastAPI, Django)
43+
- ✅ Multi-process workers (Gunicorn, uWSGI)
44+
- ✅ Hot reloading development servers
45+
- ✅ Multi-reader, single-writer architecture
46+
- ✅ Lock-free reads (~100ns latency)
47+
4148
**Server Mode (gRPC):**
4249
- ✅ Production deployments
4350
- ✅ Multi-language teams (Python, Node.js, Go)
@@ -47,6 +54,59 @@ Choose the deployment mode that fits your needs.
4754

4855
---
4956

57+
## Concurrent Embedded Mode (v0.4.8+)
58+
59+
For web applications that need multiple processes to access the same database:
60+
61+
```python
62+
from sochdb import Database
63+
64+
# Open in concurrent mode - multiple processes can access simultaneously
65+
db = Database.open_concurrent("./app_data")
66+
67+
# Reads are lock-free and can run in parallel (~100ns)
68+
value = db.get(b"user:123")
69+
70+
# Writes are automatically coordinated (~60µs amortized)
71+
db.put(b"user:123", b'{"name": "Alice"}')
72+
73+
# Check if concurrent mode is active
74+
print(f"Concurrent mode: {db.is_concurrent}") # True
75+
```
76+
77+
### Flask Example
78+
79+
```python
80+
from flask import Flask
81+
from sochdb import Database
82+
83+
app = Flask(__name__)
84+
db = Database.open_concurrent("./flask_db")
85+
86+
@app.route("/user/<user_id>")
87+
def get_user(user_id):
88+
# Multiple concurrent requests can read simultaneously
89+
data = db.get(f"user:{user_id}".encode())
90+
return data or "Not found"
91+
92+
@app.route("/user/<user_id>", methods=["POST"])
93+
def update_user(user_id):
94+
# Writes are serialized automatically
95+
db.put(f"user:{user_id}".encode(), request.data)
96+
return "OK"
97+
```
98+
99+
### Performance
100+
101+
| Operation | Standard Mode | Concurrent Mode |
102+
|-----------|---------------|-----------------|
103+
| Read (single process) | ~100ns | ~100ns |
104+
| Read (multi-process) | **Blocked**| ~100ns ✅ |
105+
| Write | ~5ms (fsync) | ~60µs (amortized) |
106+
| Max concurrent readers | 1 | 1024 |
107+
108+
---
109+
50110
## Installation
51111

52112
```bash

pyproject.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "sochdb"
7-
version = "0.4.7"
7+
version = "0.4.8"
88
description = "SochDB is an AI-native database with token-optimized output, O(|path|) lookups, built-in vector search, and durable transactions."
99
readme = "README.md"
10-
license = {text = "Apache-2.0"}
10+
license = {text = "AGPL-3.0-or-later"}
1111
authors = [
12-
{name = "Sushanth", email = "sushanth@sochdb.dev"}
12+
{name = "Sushanth Reddy Vanagala", email = "sushanth@sochdb.dev"}
1313
]
1414
maintainers = [
1515
{name = "Sushanth", email = "sushanth@sochdb.dev"}
@@ -18,7 +18,7 @@ keywords = ["database", "llm", "ai", "vector-search", "embedded", "key-value", "
1818
classifiers = [
1919
"Development Status :: 4 - Beta",
2020
"Intended Audience :: Developers",
21-
"License :: OSI Approved :: Apache Software License",
21+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
2222
"Programming Language :: Python :: 3",
2323
"Programming Language :: Python :: 3.9",
2424
"Programming Language :: Python :: 3.10",

src/sochdb/database.py

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,21 @@ def _setup_bindings(cls):
224224
lib.sochdb_open_with_config.argtypes = [ctypes.c_char_p, C_DatabaseConfig]
225225
lib.sochdb_open_with_config.restype = ctypes.c_void_p
226226

227+
# sochdb_open_concurrent(path: *const c_char) -> *mut DatabasePtr
228+
# Concurrent mode: multi-reader, single-writer for web apps
229+
try:
230+
lib.sochdb_open_concurrent.argtypes = [ctypes.c_char_p]
231+
lib.sochdb_open_concurrent.restype = ctypes.c_void_p
232+
except (AttributeError, OSError):
233+
pass # Not available in older library versions
234+
235+
# sochdb_is_concurrent(ptr: *mut DatabasePtr) -> c_int
236+
try:
237+
lib.sochdb_is_concurrent.argtypes = [ctypes.c_void_p]
238+
lib.sochdb_is_concurrent.restype = ctypes.c_int
239+
except (AttributeError, OSError):
240+
pass
241+
227242
# sochdb_close(ptr: *mut DatabasePtr)
228243
lib.sochdb_close.argtypes = [ctypes.c_void_p]
229244
lib.sochdb_close.restype = None
@@ -909,27 +924,124 @@ class Database:
909924
Provides direct access to a SochDB database file.
910925
This is the recommended mode for single-process applications.
911926
927+
For web applications or multi-process scenarios, use ``Database.open_concurrent()``
928+
instead, which allows multiple processes to access the database simultaneously.
929+
912930
Example:
931+
# Standard mode (single process)
913932
db = Database.open("./my_database")
914933
db.put(b"key", b"value")
915934
value = db.get(b"key")
916935
db.close()
936+
937+
# Concurrent mode (multiple processes, e.g., Flask/FastAPI)
938+
db = Database.open_concurrent("./my_database")
939+
# Multiple workers can now access the database
917940
918941
Or with context manager:
919942
with Database.open("./my_database") as db:
920943
db.put(b"key", b"value")
921944
"""
922945

923-
def __init__(self, path: str, _handle):
946+
def __init__(self, path: str, _handle, _is_concurrent: bool = False):
924947
"""
925948
Initialize a database connection.
926949
927-
Use Database.open() to create instances.
950+
Use Database.open() or Database.open_concurrent() to create instances.
928951
"""
929952
self._path = path
930953
self._handle = _handle
931954
self._closed = False
932955
self._lib = _FFI.get_lib()
956+
self._is_concurrent = _is_concurrent
957+
958+
@property
959+
def is_concurrent(self) -> bool:
960+
"""
961+
Check if database is in concurrent mode.
962+
963+
Concurrent mode allows multiple processes to access the database
964+
simultaneously with lock-free reads and single-writer coordination.
965+
966+
Returns:
967+
True if database is in concurrent mode.
968+
"""
969+
return self._is_concurrent
970+
971+
@classmethod
972+
def open_concurrent(cls, path: str) -> "Database":
973+
"""
974+
Open database in concurrent mode (multi-reader, single-writer).
975+
976+
This mode allows multiple processes to access the database simultaneously:
977+
978+
- **Readers**: Lock-free, concurrent access via MVCC snapshots (~100ns latency)
979+
- **Writers**: Single-writer coordination through atomic locks (~60µs amortized)
980+
981+
Use this for:
982+
983+
- Web applications (Flask, FastAPI, Django, Gunicorn, uWSGI)
984+
- Hot reloading development servers
985+
- Multi-process worker pools (multiprocessing, Celery)
986+
- Any scenario with concurrent read access
987+
988+
Args:
989+
path: Path to the database directory.
990+
991+
Returns:
992+
Database instance in concurrent mode.
993+
994+
Example:
995+
# Flask application with multiple workers
996+
from flask import Flask
997+
from sochdb import Database
998+
999+
app = Flask(__name__)
1000+
db = Database.open_concurrent("./app_data")
1001+
1002+
@app.route("/user/<user_id>")
1003+
def get_user(user_id):
1004+
# Multiple requests can read simultaneously
1005+
data = db.get(f"user:{user_id}".encode())
1006+
return data or "Not found"
1007+
1008+
@app.route("/user/<user_id>", methods=["POST"])
1009+
def update_user(user_id):
1010+
# Writes are serialized automatically
1011+
db.put(f"user:{user_id}".encode(), request.data)
1012+
return "OK"
1013+
1014+
Performance:
1015+
- Read latency: ~100ns (lock-free atomic operations)
1016+
- Write latency: ~60µs amortized (with group commit)
1017+
- Concurrent readers: Up to 1024 per database
1018+
"""
1019+
lib = _FFI.get_lib()
1020+
path_bytes = path.encode("utf-8")
1021+
1022+
# Check if sochdb_open_concurrent is available
1023+
if not hasattr(lib, 'sochdb_open_concurrent'):
1024+
raise DatabaseError(
1025+
"Concurrent mode requires a newer version of the native library. "
1026+
"Please update sochdb to the latest version."
1027+
)
1028+
1029+
handle = lib.sochdb_open_concurrent(path_bytes)
1030+
1031+
if not handle:
1032+
raise DatabaseError(
1033+
f"Failed to open database at {path} in concurrent mode. "
1034+
"Check if the path exists and is accessible."
1035+
)
1036+
1037+
# Track database open event
1038+
try:
1039+
from .analytics import track_database_open
1040+
track_database_open(path, mode="embedded-concurrent")
1041+
except Exception:
1042+
pass
1043+
1044+
return cls(path, handle, _is_concurrent=True)
9331045

9341046
@classmethod
9351047
def open(cls, path: str, config: Optional[dict] = None) -> "Database":

0 commit comments

Comments
 (0)