Move physical file facts out of the photos table into an objects ledger:
- objects / photo_objects / variant_jobs tables (migration 000008)
- drop storage_key/thumb_key/preview_key/size/width/height/mime_type from
photos (migration 000009); they are derived via joins to the original object
- album-free storage keys: objects/{id}/{role}.{ext}
- explicit GC via pending/ready/deleting states; objects deleted only when no
photo references them
- background worker drives variant generation (durable variant_jobs) and GC
- copy/move are metadata-only; storage objects are shared between photos
- remove unused storage.DeletePrefix
Non-destructive to the external JSON API: size/dimensions/mime and URLs are
still returned, just sourced from the object layer.
OpenSpec change: media-object-layer
40 lines
1.7 KiB
SQL
40 lines
1.7 KiB
SQL
-- Physical objects stored in S3. Keys are opaque and never encode album, photo
|
|
-- or any other metadata entity. The row is the ledger entry that governs the
|
|
-- lifecycle of the corresponding blob.
|
|
CREATE TABLE IF NOT EXISTS objects (
|
|
id text PRIMARY KEY,
|
|
storage_key text NOT NULL UNIQUE,
|
|
mime_type text NOT NULL DEFAULT '',
|
|
size bigint NOT NULL DEFAULT 0,
|
|
width integer NOT NULL DEFAULT 0,
|
|
height integer NOT NULL DEFAULT 0,
|
|
checksum text NOT NULL DEFAULT '',
|
|
state text NOT NULL DEFAULT 'pending'
|
|
CHECK (state IN ('pending', 'ready', 'deleting')),
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS objects_state_created_idx ON objects (state, created_at);
|
|
|
|
-- Binding of a photo to its objects by role. The role is a property of the
|
|
-- binding, not of the object itself.
|
|
CREATE TABLE IF NOT EXISTS photo_objects (
|
|
photo_id text NOT NULL REFERENCES photos(id) ON DELETE CASCADE,
|
|
object_id text NOT NULL REFERENCES objects(id) ON DELETE RESTRICT,
|
|
role text NOT NULL CHECK (role IN ('original', 'thumb', 'preview')),
|
|
PRIMARY KEY (photo_id, role)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS photo_objects_object_idx ON photo_objects (object_id);
|
|
|
|
-- Durable queue of pending variant generation work, one job per photo.
|
|
CREATE TABLE IF NOT EXISTS variant_jobs (
|
|
photo_id text PRIMARY KEY REFERENCES photos(id) ON DELETE CASCADE,
|
|
attempts integer NOT NULL DEFAULT 0,
|
|
next_attempt_at timestamptz NOT NULL DEFAULT now(),
|
|
last_error text NOT NULL DEFAULT '',
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS variant_jobs_due_idx ON variant_jobs (next_attempt_at);
|