Skip to content

Commit ce07c11

Browse files
committed
Add daemon proxy for php-fpm multi-process support
Stoolap uses exclusive file locking, so only one OS process can open a database at a time. This adds a transparent daemon proxy that auto-forks when loaded under php-fpm/CGI/Apache, allowing all worker processes to share database access through shared memory IPC. Architecture: - Daemon auto-forks from php-fpm master on first load - Per-connection 8MB shared memory (request + response buffers) - Cache-line-split control block eliminates false sharing - Spin-then-wait: fast spin phase catches sub-μs responses, futex (Linux) / __ulock (macOS) for blocking without CPU burn - Elastic thread pool (8 initial, grows to 64) with 256KB stacks - Per-PID socket paths for isolation between fpm instances - Auto-lifecycle: daemon exits when php-fpm master dies IPC overhead (bare-metal Linux x86_64): - 0.2μs bare roundtrip (futex) - 0.9μs SELECT by primary key through daemon - Sub-2μs for prepared statement execute/query Zero configuration required - works out of the box. 139 tests pass in both direct and daemon modes.
1 parent e4b8b73 commit ce07c11

File tree

7 files changed

+3760
-347
lines changed

7 files changed

+3760
-347
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,41 @@ try {
359359
| `array` / `object` | `JSON` | Auto-encoded/decoded |
360360
| `DateTimeInterface` | `TIMESTAMP` | Converted to nanoseconds |
361361

362+
## PHP-FPM / Web Server Support
363+
364+
Stoolap uses exclusive file locking, so only one OS process can open a database at a time. The PHP extension solves this transparently for multi-process environments (php-fpm, Apache mod_php) with a built-in daemon proxy.
365+
366+
### How It Works
367+
368+
When loaded under php-fpm, CGI, or Apache, the extension automatically forks a background daemon process on first load. PHP workers communicate with the daemon via shared memory + futex/ulock for near-zero IPC overhead:
369+
370+
- **Shared memory**: 8MB per connection, zero-copy request/response buffers
371+
- **futex (Linux) / __ulock (macOS)**: single-syscall wakeup, ~0.5μs bare roundtrip
372+
- **Spin-then-wait**: fast spin phase catches sub-microsecond responses, kernel wait for longer queries without CPU burn
373+
- **Auto-lifecycle**: daemon starts with php-fpm, exits when php-fpm stops
374+
- **Zero configuration**: works out of the box, no setup required
375+
376+
### IPC Overhead
377+
378+
| Operation | Direct (CLI) | Daemon (FPM) | Overhead |
379+
|---|---|---|---|
380+
| SELECT by PK | 0.5 μs | 1.0 μs | ~0.5 μs |
381+
| UPDATE by PK | 0.8 μs | 1.2 μs | ~0.5 μs |
382+
| Prepared execute | 0.6 μs | 1.1 μs | ~0.5 μs |
383+
| Prepared queryOne | 1.1 μs | 1.7 μs | ~0.6 μs |
384+
385+
Analytical queries (GROUP BY, JOIN, window functions) are unaffected since they're dominated by engine time.
386+
387+
### Environment Variables
388+
389+
| Variable | Values | Description |
390+
|---|---|---|
391+
| `STOOLAP_DAEMON` | `1` / `on` | Force daemon mode (useful for testing) |
392+
| `STOOLAP_DAEMON` | `0` / `off` | Force direct mode (disable daemon) |
393+
| `STOOLAP_DAEMON_DEBUG` | `1` | Enable daemon stderr logging |
394+
395+
When not set, daemon mode is auto-detected from the SAPI name (fpm, cgi, apache).
396+
362397
## Benchmark
363398

364399
Run the included benchmark (Stoolap vs SQLite):

ext/config.m4

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,8 @@ if test "$PHP_STOOLAP" != "no"; then
3535

3636
PHP_ADD_LIBRARY_WITH_PATH(stoolap, $STOOLAP_LIB_DIR, STOOLAP_SHARED_LIBADD)
3737
PHP_SUBST(STOOLAP_SHARED_LIBADD)
38+
STOOLAP_SHARED_LIBADD="$STOOLAP_SHARED_LIBADD -Wl,-rpath,$STOOLAP_LIB_DIR -lpthread"
3839

39-
dnl Add rpath so library is found at runtime
40-
STOOLAP_SHARED_LIBADD="$STOOLAP_SHARED_LIBADD -Wl,-rpath,$STOOLAP_LIB_DIR"
41-
42-
PHP_NEW_EXTENSION(stoolap, stoolap.c, $ext_shared)
40+
PHP_NEW_EXTENSION(stoolap, stoolap.c stoolap_daemon.c, $ext_shared)
4341
PHP_ADD_MAKEFILE_FRAGMENT
4442
fi

ext/php_stoolap.h

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,6 @@ typedef struct StoolapValue {
2525
} v;
2626
} StoolapValue;
2727

28-
/* Stoolap C API functions */
29-
extern const char *stoolap_version(void);
30-
31-
extern int32_t stoolap_open(const char *dsn, StoolapDB **out_db);
32-
extern int32_t stoolap_open_in_memory(StoolapDB **out_db);
33-
extern int32_t stoolap_clone(const StoolapDB *db, StoolapDB **out_db);
34-
extern int32_t stoolap_close(StoolapDB *db);
35-
extern const char *stoolap_errmsg(const StoolapDB *db);
36-
37-
extern int32_t stoolap_exec(StoolapDB *db, const char *sql, int64_t *rows_affected);
38-
extern int32_t stoolap_exec_params(StoolapDB *db, const char *sql, const StoolapValue *params, int32_t params_len, int64_t *rows_affected);
39-
extern int32_t stoolap_query(StoolapDB *db, const char *sql, StoolapRows **out_rows);
40-
extern int32_t stoolap_query_params(StoolapDB *db, const char *sql, const StoolapValue *params, int32_t params_len, StoolapRows **out_rows);
41-
42-
extern int32_t stoolap_prepare(StoolapDB *db, const char *sql, StoolapStmt **out_stmt);
43-
extern int32_t stoolap_stmt_exec(StoolapStmt *stmt, const StoolapValue *params, int32_t params_len, int64_t *rows_affected);
44-
extern int32_t stoolap_stmt_query(StoolapStmt *stmt, const StoolapValue *params, int32_t params_len, StoolapRows **out_rows);
45-
extern void stoolap_stmt_finalize(StoolapStmt *stmt);
46-
extern const char *stoolap_stmt_errmsg(const StoolapStmt *stmt);
47-
48-
extern int32_t stoolap_begin(StoolapDB *db, StoolapTx **out_tx);
49-
extern int32_t stoolap_begin_with_isolation(StoolapDB *db, int32_t isolation, StoolapTx **out_tx);
50-
extern int32_t stoolap_tx_exec(StoolapTx *tx, const char *sql, int64_t *rows_affected);
51-
extern int32_t stoolap_tx_exec_params(StoolapTx *tx, const char *sql, const StoolapValue *params, int32_t params_len, int64_t *rows_affected);
52-
extern int32_t stoolap_tx_query(StoolapTx *tx, const char *sql, StoolapRows **out_rows);
53-
extern int32_t stoolap_tx_query_params(StoolapTx *tx, const char *sql, const StoolapValue *params, int32_t params_len, StoolapRows **out_rows);
54-
extern int32_t stoolap_tx_stmt_exec(StoolapTx *tx, const StoolapStmt *stmt, const StoolapValue *params, int32_t params_len, int64_t *rows_affected);
55-
extern int32_t stoolap_tx_stmt_query(StoolapTx *tx, const StoolapStmt *stmt, const StoolapValue *params, int32_t params_len, StoolapRows **out_rows);
56-
extern int32_t stoolap_tx_commit(StoolapTx *tx);
57-
extern int32_t stoolap_tx_rollback(StoolapTx *tx);
58-
extern const char *stoolap_tx_errmsg(const StoolapTx *tx);
59-
60-
extern int32_t stoolap_rows_fetch_all(StoolapRows *rows, uint8_t **out_buf, int64_t *out_len);
61-
extern void stoolap_rows_close(StoolapRows *rows);
62-
extern const char *stoolap_rows_errmsg(const StoolapRows *rows);
63-
extern void stoolap_buffer_free(uint8_t *buf, int64_t len);
28+
/* Stoolap FFI — see stoolap_daemon.h for full declarations */
6429

6530
#endif

0 commit comments

Comments
 (0)