Skip to content

Storage

Strategy

Storage is opt-in via FACE_API_STORAGE_ENABLED=true. When it is enabled:

  1. Every uploaded image is stored in S3 under originals/{image_id}/{filename}.
  2. Every processing result creates a ProcessingJob row in PostgreSQL.
  3. Masked images from the /mask endpoint are stored under processed/{image_id}/{job_id}.{format}.
  4. 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

  • ImageProcessingJob (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_ENDPOINT is configured.
  • Admin and API responses should therefore treat original_url and processed_url differently.

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.