@@ -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