Storage And Metadata
SQLite metadata, disk layout, reconciliation, cache behavior, and multipart internals.
Storage And Metadata
SQLite
SQLite is opened through the pure-Go modernc.org/sqlite driver. Runtime DSNs include:
_pragma=journal_mode(WAL)_pragma=busy_timeout(5000)_pragma=synchronous(NORMAL)_pragma=foreign_keys(ON)
Migrations are stored in migrations/migration.go and recorded in schema_migrations.
Tables
users
Stores Bearer and SigV4 credential metadata.
Important columns:
iddisplay_nameaccess_key_idsecret_hashsigv4_access_key_idsigv4_secret_keyroleis_activecreated_atupdated_at
Bearer secrets are stored as SHA-256 hashes. SigV4 secret keys are stored raw for HMAC verification but are cleared from ordinary repository reads.
buckets
Stores bucket metadata:
nameowner_idcreated_at
objects
Stores object metadata:
idbucket_namekeysizeetagcontent_typestorage_pathcreated_atupdated_at
UNIQUE(bucket_name, key) allows object upserts. The storage_path points to an opaque backing file path under the data directory.
multipart_uploads
Tracks multipart upload sessions:
idbucket_namekeycontent_typestatuscreated_atstatus_updated_at
Valid statuses are active, completing, and aborted.
multipart_parts
Tracks uploaded parts:
upload_idpart_numbersizeetagstorage_pathcreated_at
Primary key is (upload_id, part_number).
object_activity
Stores activity events:
idactionbucket_nameobject_keysizeetagactor_user_idcreated_at
Disk Layout
The storage engine resolves the configured data directory to an absolute path and creates:
data/
.tmp/
multipart/
{upload-id}/
{part-number}.tmp-{uuid}
{uuid}.tmp
{bucket}/
{uuid}
Object keys are not final filenames. A key such as photos/2026/a.jpg can be stored as a row with key = photos/2026/a.jpg and storage_path = photos/{uuid}.
Object Writes
Object writes are atomic from the perspective of metadata:
- The request body is copied to a temporary file.
- The temporary file is synced and renamed to a unique final path.
- Metadata is inserted or upserted.
- On overwrite, the old backing file is deleted only after the new metadata row is committed.
If the metadata commit fails, the new backing file is deleted.
Deletes
Object deletion removes metadata first and then deletes the backing file. Missing objects are treated as successful no-op deletes on the S3 single-object delete path.
Bucket emptying and Management bucket deletion use the shared objectops.EmptyBucket helper to enumerate all objects, delete metadata, and remove backing files.
Reconciliation
At startup, storage reconciliation:
- Removes non-multipart files under
.tmp. - Walks bucket directories and deletes any backing file not referenced by object metadata.
- Prunes empty directories under bucket roots.
- Removes multipart temp directories that do not have corresponding upload IDs in SQLite.
This preserves the rule that SQLite metadata is the source of truth.
Multipart Storage
Part uploads are written to unique files under:
data/.tmp/multipart/{upload-id}/{part-number}.tmp-{uuid}
Re-uploading a part creates a new file and updates part metadata. The old part file is deleted after metadata update succeeds.
Completion assembles requested part files into a new temp file, syncs it, renames it to a UUID object path, and commits the object metadata atomically with upload deletion.
Stale Multipart Cleanup
A background goroutine runs every configured cleanup interval. It:
- Lists stale uploads older than the configured TTL.
- Claims active stale uploads as
abortedbefore deletion. - Gives non-active uploads an extra TTL-sized grace period.
- Deletes upload metadata before deleting uploaded part files.
- Logs cleanup errors and continues.
Default TTL is 24h; default cleanup interval is 1h.
Metadata Cache
When FBS_METADATA_CACHE_SIZE or --metadata-cache-size is greater than zero, bucket and object repositories are wrapped in a metadata cache. The default budget is 512 MiB. Set the budget to 0 to disable cache wrappers.
Cache invalidation is handled by repository wrappers and explicit multipart completion cache updates.