SQLite Driver
Section titled “SQLite Driver”/hoody/databases is the conventional home for SQLite databases in a Hoody container. It is an ordinary directory in the container’s own filesystem, present from the moment the container is created, and it is where hoody-sqlite resolves a bare database name such as app to /hoody/databases/app.db.
What it is not is a special filesystem. Putting a database there does not add cross-container coordination, and it does not make a file visible to another container. Safety for the writers inside one container comes from SQLite’s own locking, plus the WAL settings hoody-sqlite opens every database with. This page covers what those settings give you and where they stop.
API Endpoints Summary
Section titled “API Endpoints Summary”Database access:
- Hoody SQLite API - HTTP-based KV store access
- SQL Operations - Execute SQL transactions via HTTP
Direct access:
- Hoody Terminal - Use standard
sqlite3command - SSH Access - Reach the database files over SSH
What SQLite’s concurrency model actually allows
Section titled “What SQLite’s concurrency model actually allows”SQLite permits one writer at a time per database file. In the default rollback journal mode a writer also blocks readers. That is the source of the error most people meet first:
# AI-generated code (common pattern)import sqlite3
# Multiple tasks running simultaneouslydef process_task(task_id): conn = sqlite3.connect('/app/data.db') # Same database cursor = conn.cursor() cursor.execute("INSERT INTO tasks VALUES (?, 'completed')", (task_id,)) conn.commit() conn.close()
# Task 1, 2, 3... all running in parallel# Result: "database is locked" when one writer does not wait for anotherTwo things fix that, and neither of them is a filesystem. The first is WAL mode, which lets readers keep reading while a writer writes. The second is a busy timeout, which makes a second writer wait for the first instead of failing immediately. hoody-sqlite sets both on every database it opens.
On a local filesystem this is safe: the operating system’s file locks are what SQLite relies on, and several processes in the same container, the sqlite3 CLI included, can share a database without corrupting it. They serialize rather than run in parallel, and a writer that waits longer than the busy timeout still returns a lock error.
The /hoody/databases directory
Section titled “The /hoody/databases directory”Every container has it from the start:
ls /hoody/databases/# Directory exists and is ready to useNothing is mounted and there is no setup step. The directory is part of the container image, and hoody-sqlite creates any missing parent directory when it creates a database under it, through POST /api/v1/sqlite/db/create or a request carrying create_db_if_missing=true. The two rollback endpoints are the exception: they refuse a missing parent directory rather than creating one.
Its role is naming rather than storage behavior. hoody-sqlite accepts three spellings for the db parameter:
app -> /hoody/databases/app.db./app -> /hoody/databases/app.db/hoody/databases/app.db -> itselfThe service also has a confinement mode that refuses absolute paths outside /hoody/databases. A stock container starts it with --allow-any-absolute-db-path, so a database elsewhere in the filesystem works too.
Because the directory lives in the container’s own filesystem, it is per container. Another container cannot open the file, and a snapshot or copy of the container carries the databases with it like any other files.
How hoody-sqlite opens a database
Section titled “How hoody-sqlite opens a database”The pools the service opens your database files with all use the same settings:
journal_mode WAL readers do not block the writersynchronous NORMAL fsync at checkpoints rather than every commitbusy_timeout 30000 ms a blocked writer waits instead of failing at oncecache_size 64 MBmmap_size 256 MBtemp_store MEMORYwal_autocheckpoint 1000 pagesWrite transactions begin as BEGIN IMMEDIATE, so a writer takes the write lock up front rather than discovering a conflict at commit and having to roll back. Requests the service classifies as read-only stay deferred; the classification is deliberately cautious, so some read-only SQL takes the writer path. Each database pools up to 16 connections and keeps up to 8 of them idle.
A database created through POST /api/v1/sqlite/db/create is opened with WAL and the busy timeout while it is being created, and picks up the rest of these settings when the service next opens it for a request.
The SQLite busy timeout and the service’s default request deadline are both 30 seconds. They are independent: a request that waits for another request’s write to the same database, or for a maintenance operation on it, stops waiting when its deadline passes and gets 503 REQUEST_TIMEOUT. A request can raise its deadline with ?timeout=, up to 300 seconds; that does not change the SQLite busy timeout. When the deadline passes, the request normally fails with 503, though a SQL or KV write that already committed keeps its success response.
Usage patterns
Section titled “Usage patterns”Standard libraries, standard paths
Section titled “Standard libraries, standard paths”Any SQLite library opens these files directly:
import sqlite3conn = sqlite3.connect('/hoody/databases/database.db')conn.execute('PRAGMA journal_mode = WAL')conn.execute('PRAGMA busy_timeout = 30000')cursor = conn.cursor()cursor.execute("INSERT INTO users VALUES (?, ?)", (1, 'Alice'))conn.commit()Set the busy timeout yourself on every connection: it is per connection, and nothing the service does to its own connections carries over to yours. WAL is stored in the database file, so a database hoody-sqlite has opened is already in WAL — setting it again is harmless.
const db = require('better-sqlite3')('/hoody/databases/database.db');db.pragma('journal_mode = WAL');db.pragma('busy_timeout = 30000');db.prepare('INSERT INTO users VALUES (?, ?)').run(1, 'Alice');This works with any language that has SQLite bindings:
- Python (sqlite3 module)
- Node.js (better-sqlite3, sqlite3)
- Go (mattn/go-sqlite3)
- PHP (PDO SQLite)
Native access plus the HTTP API
Section titled “Native access plus the HTTP API”One database is reachable over HTTP and through native sqlite3 at the same time:
# Container's own shell: native sqlite3 (fast, direct access)sqlite3 /hoody/databases/app.db "SELECT * FROM users"
# Anywhere else: hoody-sqlite HTTP APIcurl "https://{project}-{container}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db" \ -H "Content-Type: application/json" \ -d '{"transaction": [{"query": "SELECT * FROM users"}]}'
# Same database, two access pathsEach path suits different work:
- Native access for performance-critical operations inside the container
- HTTP access for other containers, remote monitoring, web dashboards, API integrations
- Both are ordinary SQLite clients, so they queue against the same write lock
See: Hoody SQLite for complete HTTP API documentation.
Capabilities and limits
Section titled “Capabilities and limits”Capabilities
Section titled “Capabilities”Safe sharing inside one container:
- Several processes can open the same database
- WAL keeps readers running while a writer writes
- hoody-sqlite gives its own connections a 30-second busy timeout, so a writer it blocks waits rather than failing at once; native connections set their own, and some conflicts fail immediately whatever it is
No configuration:
- The directory is available in every container
- Nothing to set up or mount
- Works with the standard sqlite3 library
- Bare names resolve under it automatically
Reachable over HTTP:
- The same file is queryable from outside the container through hoody-sqlite
- KV and SQL surfaces sit on one database
Limits
Section titled “Limits”One container, one filesystem:
/hoody/databasesis the container’s own directory, not a shared volume- Containers cannot open each other’s database files
- Cross-container access goes through the hoody-sqlite HTTP API
One writer at a time:
- Writes serialize; they do not run in parallel
- A writer that waits past the busy timeout still gets “database is locked”
- Long transactions make that likelier, whatever the path
Not a replication or backup system:
- Data is not replicated to another host
- Nothing is backed up by default, though scheduled backups can be configured
- There is no failover
Use Cases
Section titled “Use Cases”One service, many workers
Section titled “One service, many workers”A web process and its background workers in the same container, sharing one database:
# Worker processes and the HTTP handler all open:## WAL lets reads continue during writes; the busy timeout absorbs# short overlaps between writers.The usual alternative is a PostgreSQL or MySQL server that every process reaches over the network, which adds a service to operate. Here it is one file, opened directly.
Another container needs the data
Section titled “Another container needs the data”Give the owning container’s sqlite service a URL and query it:
# From any other container, or from your laptopcurl "https://$PROJECT-$CONTAINER-sqlite-1.$SERVER.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db" \ -H "Content-Type: application/json" \ -d '{"transaction": [{"query": "SELECT COUNT(*) FROM events"}]}'The file stays on the owning container’s local filesystem, so this stays inside SQLite’s safe territory. Copying the file onto a shared mount and opening it from both sides does not.
Analytics pipeline
Section titled “Analytics pipeline”Ingestion and queries in the same container, against the same database:
# Ingest metricswhile true; do sqlite3 /hoody/databases/metrics.db \ "INSERT INTO events VALUES (datetime('now'), '$data')"done
# Query metrics at the same timesqlite3 /hoody/databases/metrics.db \ "SELECT COUNT(*) FROM events WHERE timestamp > datetime('now', '-1 hour')"Under WAL the read does not wait for the insert. Set PRAGMA busy_timeout on both connections so a write that collides with a checkpoint waits rather than failing.
Integration with hoody-sqlite
Section titled “Integration with hoody-sqlite”The same databases are reachable over HTTP through hoody-sqlite.
hoodyCLI prerequisite. The CLI tabs below invokehoody db …andhoody kv …. If you haven’t already, install the CLI (npm i -g hoody-sdk) and authenticate (hoody login) so the CLI picks up your Hoody token and default base URL.
SQL transactions
Section titled “SQL transactions”# Execute SQL transaction via CLIhoody db transactions run -c $CONTAINER --db /hoody/databases/app.db \ --transaction '[{"query": "SELECT * FROM logs ORDER BY timestamp DESC LIMIT 100"}]'
# Create a new databasehoody db create -c $CONTAINER --path /hoody/databases/analytics.dbconst containerClient = await client.withContainer({ id: CONTAINER_ID, project_id: PROJECT_ID, server: SERVER});
// Execute SQL transaction. Pass the body (transaction array) as the first// argument and the query params (db) as the second. Each item sets exactly one of// `query` or `statement` (alias `sql`), plus optional `values`. A `query` always// returns a result set. A `statement` returns rows when its SQL produces columns// (SELECT, PRAGMA, or a write with RETURNING), and rowsUpdated otherwise.// Results are returned per item.const result = await containerClient.sqlite.sql.runTransaction( { transaction: [ { query: 'SELECT * FROM logs ORDER BY timestamp DESC LIMIT 100' } ] }, { db: '/hoody/databases/app.db' });console.log(result.data); // Query results# Execute SQL transaction. Hoody Proxy owns Kit auth, so a raw request carries# whatever credential the effective container-or-project policy demands, and# nothing at all by default, since a container with no proxy permissions# configured is open to anyone holding the unguessable URL. $PROXY_TOKEN below is# the bearer-token case; the SDK/CLI attach it for you. Policies can require a# different credential entirely. See /foundation/proxy/permissions/.curl -X POST "https://$PROJECT-$CONTAINER-sqlite-1.$SERVER.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db" \ -H "Authorization: Bearer $PROXY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"transaction": [{"query": "SELECT * FROM logs ORDER BY timestamp DESC LIMIT 100"}]}'One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Runs the query and returns the last 100 log rows. Carries a bearer token because this example assumes the container’s proxy policy requires one; by default none does.
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-sqlite-1.SERVER.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db&method=POST&bearer_token=TOKEN&json={"transaction":[{"query":"SELECT%20*%20FROM%20logs%20ORDER%20BY%20timestamp%20DESC%20LIMIT%20100"}]}&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
KV store operations
Section titled “KV store operations”# Set a key-value pairhoody kv set user:1 -c $CONTAINER --db /hoody/databases/app.db \ --body '{"name": "Alice", "email": "alice@example.com"}'
# Get a value by keyhoody kv get user:1 -c $CONTAINER --db /hoody/databases/app.db// Set value. Pass the key and the value as the first two arguments, and db in the options object.// An object is stored as JSON; a string is stored as text/plain and read back as a string.const db = '/hoody/databases/app.db';await containerClient.sqlite.kv.set('user:1', { name: 'Alice', email: 'alice@example.com' }, { db });
// Get value. Pass the key as the first argument and db in the options object. The SDK returns the// standard { statusCode, message, data } envelope; the stored value is in `data`.const res = await containerClient.sqlite.kv.get('user:1', { db });console.log(res.data); // { name: 'Alice', email: 'alice@example.com' }# $PROXY_TOKEN is the proxy-minted token for this container-proxy path. Send it# only when the container's proxy policy asks for a bearer token. By default no# permissions are configured and no credential is needed at all.# See /foundation/proxy/permissions/.
# Set valuecurl -X PUT "https://$PROJECT-$CONTAINER-sqlite-1.$SERVER.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db" \ -H "Authorization: Bearer $PROXY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'
# Get valuecurl "https://$PROJECT-$CONTAINER-sqlite-1.$SERVER.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db" \ -H "Authorization: Bearer $PROXY_TOKEN"One request, one link
cURL runs inside your container and can wrap any HTTP request into a single GET URL. The call stops being something you need a client for and becomes something you can paste into a browser, send in a chat, bookmark, schedule with cron, or drop into a no-code tool.
Nothing is installed on the machine that opens it. The link does carry whatever credentials the call needs, so treat it as you would treat those credentials.
Slashes, colons and braces pass through as they are. The one character you must
encode is an & inside a value, which happens when the wrapped URL
carries its own query string. Left raw it ends the value early, and the rest is
read as cURL's own parameters, so you get a 200 on a request you did
not make.
How the wrapping works Chaining calls into one link Turning a link into a shortcut
Sets then reads back the same key. Both links carry a bearer token because this example assumes the container’s proxy policy requires one; by default none does.
# Set
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-sqlite-1.SERVER.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db&method=PUT&bearer_token=TOKEN&json={"name":"Alice","email":"alice@example.com"}&response=transparent
# Get
https://PROJECT_ID-CONTAINER_ID-curl-1.SERVER.containers.hoody.com/api/v1/curl/request?url=https://PROJECT_ID-CONTAINER_ID-sqlite-1.SERVER.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db&method=GET&bearer_token=TOKEN&response=transparent The link carries a credential and executes with it, so it is as sensitive as the credential itself — and it passes through the cURL service's request log on the way, not just the target's. Share it only where you would share the secret, and prefer a delegated token with minimal permissions and an expiry: see API tokens.
Native and HTTP together
Section titled “Native and HTTP together”// Inside the container: native sqlite3 for bulk inserts (faster)const db = require('better-sqlite3')('/hoody/databases/app.db');db.pragma('journal_mode = WAL');db.pragma('busy_timeout = 30000');db.prepare('INSERT INTO logs VALUES (?, ?)').run(timestamp, message);
// From another container: HTTP API for remote queries. No Authorization header// here because this container has no proxy permissions configured (the default).// Add the credential its policy requires once you configure one.const response = await fetch( 'https://{project}-{container}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/db?db=/hoody/databases/app.db', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ transaction: [{ query: 'SELECT * FROM logs ORDER BY timestamp DESC LIMIT 100' }] }) });Writes stay on the fast native path while reads remain available over HTTP.
# Hoody SQLite KV Store API (even simpler)# No auth header: this container has no proxy permissions configured (the default).# Set valuecurl -X PUT "https://{project}-{container}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db" \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'
# Get valuecurl "https://{project}-{container}-sqlite-1.{server}.containers.hoody.com/api/v1/sqlite/kv/user:1?db=/hoody/databases/app.db"Everything runs through the API, so the sqlite3 CLI is never used.
See: Hoody SQLite KV Store and SQL Operations
Best Practices
Section titled “Best Practices”Keep SQLite databases in /hoody/databases
Section titled “Keep SQLite databases in /hoody/databases”It is where the tooling looks, where bare names resolve, and where a confined hoody-sqlite is allowed to read:
# Conventional/hoody/databases/production.db/hoody/databases/cache.db/hoody/databases/analytics.dbNever put a SQLite file on a path more than one container mounts, whatever the directory name:
# Risk of corruption: a share two containers both mount/hoody/mounts/permanent/team-share/production.dbSet WAL and a busy timeout on your own connections
Section titled “Set WAL and a busy timeout on your own connections”A library you open yourself sets its own busy timeout; WAL it inherits from the file, because journal_mode is stored there:
import sqlite3conn = sqlite3.connect('/hoody/databases/app.db')conn.execute('PRAGMA journal_mode = WAL') # persists in the fileconn.execute('PRAGMA busy_timeout = 30000') # per connectionjournal_mode is stored in the database file, so it survives. busy_timeout is per connection and has to be set every time.
Keep write transactions short
Section titled “Keep write transactions short”One writer holds the lock for the whole transaction, so the length of your longest write decides how long everyone else waits:
-- Wrong: holding the write lock through slow workBEGIN IMMEDIATE;-- seconds of computationCOMMIT;
-- Better: compute first, then writeBEGIN IMMEDIATE; INSERT ...; COMMIT;Back up databases regularly
Section titled “Back up databases regularly”WAL is not a backup:
# Via snapshot (captures entire container state)POST /api/v1/containers/{id}/snapshots{"alias": "before-migration"}
# Or use sqlite3's backup command, which is safe on a live databasesqlite3 /hoody/databases/production.db ".backup /hoody/storage/backups/backup.db"Prefer .backup over cp on a database that is in use: a plain copy can catch the file mid-transaction. Over HTTP, POST /api/v1/sqlite/maintenance?db=/hoody/databases/production.db with {"op": "vacuum_into", "dest_path": "backup.db"} writes the same kind of consistent copy, /hoody/databases/backup.db, through hoody-sqlite itself. The destination must not exist yet.
Restoring needs the same care. Do not move or copy a backup over a database that is in use: the old -wal file stays behind, and SQLite can pair the restored file with it and corrupt the result. For a database hoody-sqlite serves, POST /api/v1/sqlite/maintenance?db=/hoody/databases/production.db with {"op": "restore", "src_path": "backup.db"} copies a standalone backup in through hoody-sqlite itself, as one write transaction, and the backup is only read. Otherwise, stop every reader and writer, hoody-sqlite included, and let their connections close: a checkpoint alone does not close the pooled connections it holds, and even reads can queue background writes. Then, for a standalone backup of the kind .backup and vacuum_into produce, put the file in place and make sure no old -wal, -shm or journal file is left beside it. A filesystem snapshot is different: restore the database and its sidecars together, as the set they were captured as.
Monitor database size
Section titled “Monitor database size”# Check database sizesdu -sh /hoody/databases/*
# Vacuum to compactsqlite3 /hoody/databases/app.db "VACUUM"
# Turn on auto-vacuum. On a database that was created without it, the# pragma only takes effect after the VACUUM that follows it.sqlite3 /hoody/databases/app.db "PRAGMA auto_vacuum = FULL; VACUUM;"Add indexes for query performance
Section titled “Add indexes for query performance”-- Create indexes for frequently queried columnsCREATE INDEX idx_users_email ON users(email);CREATE INDEX idx_posts_author ON posts(author_id, created_at);
-- Analyze for query plannerANALYZE;Useful Questions
Section titled “Useful Questions”Do I need to do anything to enable /hoody/databases?
Section titled “Do I need to do anything to enable /hoody/databases?”No. The directory ships in every container. Start using it:
sqlite3 /hoody/databases/myapp.db "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)"Can two containers share one database file?
Section titled “Can two containers share one database file?”No. /hoody/databases belongs to the container it is in, so another container cannot open the file at all. Putting the file on a storage share both containers mount is worse than unsupported: network filesystems do not reliably honor the locks SQLite depends on, and that is how a SQLite database gets corrupted rather than merely locked.
When another container needs the data, query the owning container’s sqlite service over HTTP. The file stays on that container’s local filesystem, where SQLite’s locking works.
Does this work with PostgreSQL or MySQL?
Section titled “Does this work with PostgreSQL or MySQL?”No. Everything here is SQLite’s file-level protocol. Run PostgreSQL or MySQL as services in containers and connect to them over the network.
What if my database gets really large (>10GB)?
Section titled “What if my database gets really large (>10GB)?”Size is not the constraint the directory imposes; the container’s disk is. Keep databases under 100GB for reasonable maintenance times. For larger datasets, consider:
- Sharding across multiple databases
- Indexing aggressively
- Moving to a container with more storage
- Running a dedicated PostgreSQL container
Do I have to use WAL mode?
Section titled “Do I have to use WAL mode?”hoody-sqlite opens its own connections in WAL, and journal_mode is a property of the database file, so a database it has opened stays in WAL for every later client. For a database you create yourself with another library, set it once:
PRAGMA journal_mode = WAL;Does the HTTP API require /hoody/databases?
Section titled “Does the HTTP API require /hoody/databases?”On a stock container, no. hoody-sqlite is started with --allow-any-absolute-db-path, so an absolute path elsewhere works:
# Conventionalcurl "...?db=/hoody/databases/app.db"
# Also works on a stock containercurl "...?db=/home/user/data/test.db"Without that flag the service refuses absolute paths outside /hoody/databases.
Troubleshooting
Section titled “Troubleshooting””Database is locked” errors
Section titled “”Database is locked” errors”The error means a conflicting lock was not released in time — usually the busy timeout elapsing, though SQLite also returns it at once for a conflict that waiting cannot resolve. The usual causes:
-
Long-running transactions:
-- Wrong: holding the write lock too longBEGIN EXCLUSIVE;-- Complex operations taking secondsCOMMIT;-- Correct: break into smaller transactionsBEGIN; INSERT ...; COMMIT;BEGIN; INSERT ...; COMMIT; -
Busy timeout too low, or never set:
conn = sqlite3.connect('/hoody/databases/app.db')conn.execute('PRAGMA busy_timeout = 30000') # 30 seconds -
Rollback journal instead of WAL:
PRAGMA journal_mode = WAL; -
Sustained write concurrency: writes serialize, so past a certain rate the queue never drains. Batch them, or move to a database server.
Database file not found
Section titled “Database file not found”For sqlite3: cannot open database, check the directory, create the database if it is missing, and check permissions:
# Verify directory existsls -la /hoody/databases/
# Create database if neededsqlite3 /hoody/databases/newdb.db "CREATE TABLE test (id INTEGER)"
# Check permissions: the file must be readable and writable# by the user of the process opening itls -la /hoody/databases/newdb.dbSlower queries than expected
Section titled “Slower queries than expected”-
Enable WAL mode:
PRAGMA journal_mode = WAL;PRAGMA synchronous = NORMAL; -
Use indexes:
CREATE INDEX idx_query ON table(column);ANALYZE; -
Increase cache size:
PRAGMA cache_size = -64000; -- 64MB cache -
Batch operations:
BEGIN;-- Multiple INSERTsCOMMIT;
How writers queue
Section titled “How writers queue”Under WAL, readers and one writer proceed together; a second writer waits on the write lock until the first commits or the busy timeout expires.
Time Process A Database Process B─────────────────────────────────────────────────────────────────────T1 BEGIN IMMEDIATE write lock -> A BEGIN IMMEDIATET2 INSERT INTO users WAL append waiting on the lockT3 INSERT INTO posts WAL append waitingT4 COMMIT lock released lock -> BT5 (done) WAL append INSERT INTO logsT6 SELECT (no wait) readers unblocked COMMITNo reader appears in the waiting column above: that is what WAL buys against a writer. What it does not buy is parallel writes, on any path, and a reader can still wait for maintenance or for SQL that drops or alters a table, which take the database exclusively.
What’s Next
Section titled “What’s Next”Storage:
- Container Storage → -
/hoody/storageand the container filesystem - Mount Locally → - Access container files via SFTP/WebDAV
- Cloud Storage → - Connect remote storage backends
- Shared Storage → - Share directories between containers
- /ramdisk → - RAM-backed storage
Database access:
- Hoody SQLite KV Store → - HTTP-based key-value operations
- SQL Operations → - Execute transactions via HTTP
- Hoody Terminal → - Use sqlite3 command directly