Storage¶
Strategy¶
Storage is opt-in via FACE_API_STORAGE_ENABLED=true. When it is enabled:
- Every uploaded image is stored in S3 under
originals/{image_id}/{filename}. - Every processing result creates a
ProcessingJobrow in PostgreSQL. - Masked images from the
/maskendpoint are stored underprocessed/{image_id}/{job_id}.{format}. - Storage write failures trigger cleanup and rollback attempts, but they do not fail public API responses directly.
When it is disabled, the API operates statelessly with zero persistence overhead.
Database¶
Engine: PostgreSQL 18 via SQLAlchemy 2.0 async + asyncpg
Schema¶
images table¶
| Column | Type | Description |
|---|---|---|
| id | UUID (PK) | Auto-generated |
| filename | VARCHAR(255) | Original upload filename |
| content_type | VARCHAR(100) | MIME type |
| size_bytes | BIGINT | File size |
| width | INT | Image width in pixels |
| height | INT | Image height in pixels |
| s3_key | VARCHAR(500) | S3 object key for original |
| created_at | TIMESTAMPTZ | Upload timestamp |
processing_jobs table¶
| Column | Type | Description |
|---|---|---|
| id | UUID (PK) | Auto-generated |
| image_id | UUID (FK) | References images.id ON DELETE CASCADE |
| job_type | VARCHAR(20) | detect or mask |
| status | VARCHAR(50) | completed (currently only value) |
| confidence_threshold | FLOAT | Detection threshold used |
| nms_threshold | FLOAT | NMS threshold used |
| blur_method | VARCHAR(50) | Null for detect jobs |
| blur_strength | INT | Null for detect jobs |
| padding | FLOAT | Null for detect jobs |
| output_format | VARCHAR(20) | jpeg/png/webp, null for detect |
| output_quality | INT | 1-100, null for detect |
| face_count | INT | Number of faces detected |
| detections | JSONB | Full detection results |
| processed_s3_key | VARCHAR(500) | S3 key for masked image |
| processed_size_bytes | BIGINT | Masked image file size |
| inference_ms | FLOAT | SCRFD inference time |
| total_ms | FLOAT | Total request processing time |
| created_at | TIMESTAMPTZ | Job creation timestamp |
Relationships¶
Image→ProcessingJob(1:N, cascade delete)- Deleting an image removes the DB rows first and records storage cleanup tasks so object deletion can be retried if immediate cleanup fails.
S3 Object Layout¶
{bucket}/
├── originals/
│ └── {image_id}/
│ └── {original_filename}
└── processed/
└── {image_id}/
└── {job_id}.{format}
Key Format Examples¶
originals/a1b2c3d4-e5f6-7890-abcd-ef1234567890/photo.jpg
processed/a1b2c3d4-e5f6-7890-abcd-ef1234567890/f0e1d2c3-b4a5-6789-0abc-def123456789.jpeg
Visibility model¶
- Original uploads stay private and are served through private download URLs.
- Processed assets may be exposed through a public host when
FACE_API_S3_PUBLIC_ENDPOINTis configured. - Admin and API responses should therefore treat
original_urlandprocessed_urldifferently.
Configuration¶
| Variable | Default | Description |
|---|---|---|
FACE_API_STORAGE_ENABLED |
false |
Enable/disable all storage |
FACE_API_DATABASE_URL |
postgresql+asyncpg://postgres:postgres@localhost:5432/faceapi |
PostgreSQL connection |
FACE_API_S3_ENDPOINT |
http://localhost:9000 |
S3 endpoint (MinIO for dev) |
FACE_API_S3_ACCESS_KEY |
— | S3 access key |
FACE_API_S3_SECRET_KEY |
— | S3 secret key |
FACE_API_S3_BUCKET |
face-api |
S3 bucket name |
FACE_API_S3_REGION |
us-east-1 |
S3 region |
FACE_API_S3_PUBLIC_ENDPOINT |
— | Public base URL for processed assets when exposed separately |
For the local Docker Compose stack, MinIO credentials default to minioadmin / minioadmin unless overridden in .env.