MemCP for Microservices: Difference between revisions
Wikiservice (talk | contribs) (Refresh MemCP documentation: accuracy, operational guidance, performance profile and maintained API reference) |
Wikiservice (talk | contribs) (Refresh MemCP documentation: accuracy, operational guidance, performance profile and maintained API reference) |
||
| Line 23: | Line 23: | ||
The repository's <code>apps/keyvalue.scm</code> example creates a table and parses fixed SELECT/upsert statements once during startup. Each request creates a session, binds the key/value, and evaluates the prepared formula. That avoids parsing and planning the same statement on every request while keeping caller values separate from SQL text. | The repository's <code>apps/keyvalue.scm</code> example creates a table and parses fixed SELECT/upsert statements once during startup. Each request creates a session, binds the key/value, and evaluates the prepared formula. That avoids parsing and planning the same statement on every request while keeping caller values separate from SQL text. | ||
< | <pre> | ||
(set item_get (parse_sql "keyvalue" | (set item_get (parse_sql "keyvalue" | ||
"SELECT value FROM kv WHERE key = @key")) | "SELECT value FROM kv WHERE key = @key")) | ||
| Line 29: | Line 29: | ||
"INSERT INTO kv(key, value) VALUES (@key, @value) | "INSERT INTO kv(key, value) VALUES (@key, @value) | ||
ON DUPLICATE KEY UPDATE value = @value")) | ON DUPLICATE KEY UPDATE value = @value")) | ||
</ | </pre> | ||
The complete example installs a request handler and starts its own HTTP listener: | The complete example installs a request handler and starts its own HTTP listener: | ||
< | <pre> | ||
(createdatabase "keyvalue" true) | (createdatabase "keyvalue" true) | ||
(eval (parse_sql "keyvalue" | (eval (parse_sql "keyvalue" | ||
| Line 56: | Line 56: | ||
(serve 1266 (lambda (req res) (http_handler req res))) | (serve 1266 (lambda (req res) (http_handler req res))) | ||
</ | </pre> | ||
Run the maintained repository version with <code>./memcp apps/keyvalue.scm</code>, store a value using <code>curl --data-binary value http://localhost:1266/key</code>, and retrieve it with <code>curl http://localhost:1266/key</code>. This demonstration intentionally omits production authentication and limits; do not expose it unchanged. | Run the maintained repository version with <code>./memcp apps/keyvalue.scm</code>, store a value using <code>curl --data-binary value http://localhost:1266/key</code>, and retrieve it with <code>curl http://localhost:1266/key</code>. This demonstration intentionally omits production authentication and limits; do not expose it unchanged. | ||
Latest revision as of 12:14, 28 August 2026
MemCP for Microservices
MemCP can combine database storage, SQL-over-HTTP, and custom Scheme handlers in one process. This removes an application-to-database network hop and can simplify small services, but it is an architectural option rather than a latency guarantee.
Deployment patterns
| Pattern | Benefit | Trade-off |
|---|---|---|
| Ordinary service plus MySQL protocol | Familiar drivers, connection pools, and separation of concerns | Network/serialization hop remains |
| Service plus SQL over HTTP | Minimal client dependency and streamable JSONL | General SQL endpoint needs strict network and grant controls |
| Embedded Scheme handler | Prepared plans and storage access in one process; custom JSON or WebSocket API | Application failure and database failure share a process and deployment lifecycle |
Good candidates include small internal APIs, real-time dashboards, local/edge services, event lookup, and low-latency state whose data model remains relational. A large product with independent teams, many language-specific libraries, complex application logic, or strict process-isolation requirements may be easier to operate with a conventional separate service.
Preparing work outside the request path
The repository's apps/keyvalue.scm example creates a table and parses fixed SELECT/upsert statements once during startup. Each request creates a session, binds the key/value, and evaluates the prepared formula. That avoids parsing and planning the same statement on every request while keeping caller values separate from SQL text.
(set item_get (parse_sql "keyvalue" "SELECT value FROM kv WHERE key = @key")) (set item_set (parse_sql "keyvalue" "INSERT INTO kv(key, value) VALUES (@key, @value) ON DUPLICATE KEY UPDATE value = @value"))
The complete example installs a request handler and starts its own HTTP listener:
(createdatabase "keyvalue" true) (eval (parse_sql "keyvalue" "CREATE TABLE IF NOT EXISTS kv(key TEXT, value TEXT, UNIQUE KEY PRIMARY(key))")) (set item_get (parse_sql "keyvalue" "SELECT value FROM kv WHERE key = @key")) (set item_set (parse_sql "keyvalue" "INSERT INTO kv(key,value) VALUES (@key,@value) ON DUPLICATE KEY UPDATE value=@value")) (define http_handler (lambda (req res) (begin (set session (newsession)) (session "key" (req "path")) (if (equal? (req "method") "GET") (begin (set resultrow (lambda (row) ((res "print") (row "value")))) (eval item_get)) (begin (session "value" ((req "body"))) (eval item_set) ((res "print") "ok")))))) (serve 1266 (lambda (req res) (http_handler req res)))
Run the maintained repository version with ./memcp apps/keyvalue.scm, store a value using curl --data-binary value http://localhost:1266/key, and retrieve it with curl http://localhost:1266/key. This demonstration intentionally omits production authentication and limits; do not expose it unchanged.
Use SQL constraints and one-statement atomic patterns even inside an embedded handler. In-process access removes transport overhead; it does not remove concurrent requests, transaction conflicts, authorization, or durability decisions.
Historical HTTP microbenchmark
An early AMD Ryzen 9 7900X3D run used ApacheBench against a tiny embedded endpoint with concurrency 10. It reported 1,000,000 completed requests, zero failures, 42.083 seconds, 23,762.78 requests/s, 0.421 ms mean request latency (0.042 ms across concurrent requests), p95 1 ms and maximum 2 ms. The author also observed that ab saturated one client core while MemCP used roughly 20% across other cores.
The original record does not include the exact command, commit, response validation beyond ApacheBench's failure count, server build or repeat runs. It demonstrates that the embedded path can be very small, but it is not a database-query or current-release throughput guarantee. A new comparison must include the handler code, successful body validation, client/server CPU profiles and several raw samples.
Production checklist
Choose each table ENGINE according to data-loss tolerance, configure total and persistent RAM budgets, and provide health checks, backups, monitoring, and restart validation. Set a strong root password before exposing either the HTTP or MySQL port. Persistent data should use safe unless the documented risks of another engine are explicitly acceptable.
Handler latency depends on query shape, compilation and plan-cache state, concurrency, memory pressure, and storage reloads. Benchmark the full authenticated request path with successful-response validation instead of quoting a fixed sub-millisecond figure.
Run embedded services under a supervisor, use --no-repl, implement external liveness/readiness checks, limit bodies and queues, propagate cancellation, and test restart/restore. Scaling the process horizontally requires an explicit ownership, replication, or routing design; the current local shard engine is not an automatic multi-node database.
See In-Database WebApps and REST Services, Persistency and Performance Guarantees, Memory Management and Eviction, Security and Authentication, and Performance Measurement.