AdvancedObject storageUploadsACLsQuotasBackground jobs

Cloud File Storage

Secure resumable uploads, hierarchy, sharing, versioning, and background scanning.

Product overview

Store large file bytes durably while providing tenant-safe folders, resumable uploads, versions, sharing, quota enforcement, scanning, and synchronized metadata.

Main actors

User, Workspace admin, External recipient, Security operator.

Assumptions

  • Object storage holds bytes; PostgreSQL owns namespace and permissions
  • Workspace is a tenant boundary
  • Files may have many immutable versions
  • Clients upload directly with constrained grants

Non-goals

  • Collaborative document editing
  • Filesystem POSIX semantics
  • Backup of arbitrary machines
  • Content inspection beyond safety/policy

Requirements

Must-have

  • Folders and file metadata
  • Resumable upload/download
  • Version history
  • Member and link sharing
  • Quota and malware scan

Should-have

  • Trash/restore
  • Thumbnails
  • Desktop sync change cursor
  • Audit export

Future scope

  • Block-level delta sync
  • End-user encryption
  • Document collaboration
  • Cross-workspace transfer

Non-functional decisions

ConcernDecision
DurabilityPublished versions use durable versioned storage and checked metadata; deletion is delayed.
ConsistencyNamespace, quota reservation, and ACL are transactional; scanning/preview and sync notifications converge.
SecurityNo raw bucket access; grants are narrow and files stay quarantined until checks complete.
ScalabilityLarge bytes bypass application servers; metadata pages use keyset cursors.
PrivacyTenant keys, access logging, retention, and regional placement are explicit.

User roles

RoleAccess boundary
MemberWorkspace files granted by inherited ACL
Workspace adminMembership, policy, quota, audit; content access is separately governed
External recipientOnly token-scoped shared object
Security operatorQuarantine metadata and controlled sample access

Core workflows

Upload
Authorize parent folder and reserve quota → Create multipart session and object key → Upload parts directly → Finalize/check checksum → Create quarantined file version → Scan/type-check → Publish version and notify clients
Share
Choose file/folder and permissions → Create revocable hashed token or member ACL → Recipient presents token → Authorize current state → Issue short-lived download URL → Audit download where policy requires

Domain model

EntitiesRelationship
Workspace → NodeNode is file or folder in one tenant namespace
File Node → FileVersionImmutable object metadata; one current published version
Node → ACLDirect grant with documented inheritance resolution
ShareLink → NodeHashed, expiring, revocable capability
UploadSession → QuotaReservationBounds unfinished bytes
Workspace → ChangeOrdered metadata log drives sync

Database design

PostgreSQL remains the transactional source of truth. Add specialized stores only for the workloads called out below.

nodes — Tenant namespace

nodes ----- id UUID PK workspace_id UUID FK parent_id UUID NULL kind TEXT name TEXT current_version_id UUID NULL deleted_at TIMESTAMPTZ version INT Constraints: UNIQUE(workspace_id, parent_id, normalized_name) WHERE deleted_at IS NULL; folder cannot parent itself Indexes: (workspace_id, parent_id, name, id); (workspace_id, deleted_at)

file_versions — Immutable byte object reference

file_versions ------------- id UUID PK workspace_id UUID file_id UUID FK object_key TEXT size_bytes BIGINT checksum TEXT status TEXT mime_detected TEXT Constraints: UNIQUE(object_key); size_bytes >= 0 Indexes: (workspace_id, file_id, created_at DESC); (status, created_at)

acl_entries — Explicit grants

acl_entries ----------- workspace_id UUID node_id UUID principal_type TEXT principal_id UUID permission TEXT Constraints: PK(node_id, principal_type, principal_id, permission) Indexes: (workspace_id, principal_id)

workspace_changes — Monotonic sync feed

workspace_changes ----------------- seq BIGSERIAL workspace_id UUID node_id UUID kind TEXT node_version INT created_at TIMESTAMPTZ Constraints: PK(workspace_id, seq) logically/partitioned Indexes: (workspace_id, seq)

Transaction and retention boundaries

  • Reserve quota and create upload session together
  • Finalize creates quarantined version; scan publishes current_version and change record atomically
  • Move checks destination name uniqueness and prevents descendant cycles
  • Trash is soft delete; object purge follows retention and legal hold asynchronously

API design

GET /v1/nodes/:id/children?cursor=… POST /v1/uploads POST /v1/uploads/:id/complete POST /v1/nodes/:id/downloads PATCH /v1/nodes/:id GET /v1/changes?cursor=…
EndpointPurpose and permissionIdempotency / status
GET /v1/nodes/:id/children?cursor=…List authorized child nodes
Node viewer
Not required
200, 403
POST /v1/uploadsReserve quota and create multipart grant
Parent folder editor
Required
201, 403, 413
POST /v1/uploads/:id/completeVerify object and queue scan
Session owner
Parts/checksum bound
202, 409, 422
POST /v1/nodes/:id/downloadsIssue short-lived download
Current ACL/share authorization
Not required
201, 403, 423
PATCH /v1/nodes/:idRename/move with expected version
Node editor and destination editor
Not required
200, 409, 412
GET /v1/changes?cursor=…Ordered tenant sync changes
Member; results authorization-filtered
Not required
200, 410

List endpoints use opaque keyset cursors unless a small bounded collection is explicit. Unknown objects return 404 where 403 would leak existence.

Architecture

System context

Cloud File Storage system context
Cloud File Storage system context

Main application architecture

Cloud File Storage application architecture
Cloud File Storage application architecture

Critical workflow sequence

Cloud File Storage workflow sequence
Cloud File Storage workflow sequence

Event model

EventProducer → consumersDelivery
UploadFinalizedUpload API → Scanner/type detectorOutbox; unique version stage
FileVersionPublishedScanner coordinator → Preview, search, syncVersioned; consumers dedupe version id
NodeDeletedMetadata API → Sync, search removal, lifecycleTombstone retained through cursor window

Failure modes

Object without metadata

Failure
Upload bytes finish but finalize never runs.

Mitigation
Session/object prefix expiry job deletes unclaimed objects and releases quota.

Metadata without object

Failure
Finalize races object-store visibility/failure.

Mitigation
Verify server-side object size/checksum before version transaction; quarantined state never downloads.

Quota race

Failure
Concurrent uploads exceed workspace allowance.

Mitigation
Lock quota counter and reserve expected bytes before grants.

Unsafe file

Failure
Malware scan fails or times out.

Mitigation
Remain quarantined; bounded retries then explicit blocked/manual state.

ACL stale cache

Failure
Revoked user still downloads.

Mitigation
Authorization epoch in cache/token; download grant is short-lived and issued after current check.

Folder cycle

Failure
Concurrent moves make a node its descendant.

Mitigation
Lock affected ancestry or use materialized path/version check; reject ambiguous concurrent move.

Consistency and concurrency

Strong consistency / transactions

  • Namespace uniqueness
  • quota reservation
  • current published version
  • ACL/share revocation

Eventual convergence

  • Preview
  • search index
  • device sync delivery
  • physical object purge

Concurrency controls

  • Unique active sibling name constraint
  • Quota counter locked per workspace
  • Node optimistic version for move/rename
  • Upload complete keyed by session/checksum
  • Avoid distributed locks; route tenant metadata writes to its database owner

Caching and background work

Safe to cache

  • Immutable published versions through CDN
  • Thumbnails
  • Folder pages briefly with node version

Never a cache authority

  • Quarantined bytes
  • Long-lived ACL decision
  • Quota available during reservation

Content-addressed/versioned bytes use long TTL. Metadata cache keys include tenant/node version and ACL epoch; revocation bumps epoch.

Background jobs

  • Scan and type-detect
  • Thumbnail/preview
  • Orphan multipart cleanup
  • Search indexing
  • Trash retention purge
  • Quota reconciliation

Security boundaries

Security claims here are design controls, not certification. Threat modeling and jurisdiction-specific review remain required.

  • Presigned operations bind bucket key, method, size/checksum, and expiry
  • Detect type from bytes; scan/re-encode risky formats
  • ACL checks include ancestors and current tenant membership
  • Share tokens have high entropy, hashes at rest, expiry, and revocation
  • Archive extraction guards path traversal, zip bombs, and file count
  • Remote import blocks SSRF and metadata addresses
  • Audit shares, downloads, admin actions, and export

Observability

Logs

  • Upload session, quota reservation, object verification, scan verdict, and publish transitions
  • ACL/share evaluation result with tenant, node, principal type, and policy epoch—never token value
  • Move/rename/delete change sequence and optimistic version conflict

Metrics

  • Upload completion, checksum rejection, and orphan-session rate
  • Scan queue age, verdict distribution, and preview failure rate
  • Reserved-versus-stored quota drift and reconciliation corrections
  • Sync cursor lag, change-log retention misses, and download authorization denials

Traces

  • Trace upload from quota reservation through direct-object finalize, scan, publish, and sync event
  • Trace shared download through token/ACL resolution and short-lived storage grant

Alerts

  • Quarantined files remain unresolved beyond policy window
  • Quota invariant or metadata/object reconciliation drifts
  • ACL revocation propagation or sync change feed stalls

Testing strategy

LayerWhat it proves
UnitACL inheritance, path/name normalization, quota arithmetic, cursor
IntegrationTenant FKs, name race, quota lock, object finalize
ContractObject store multipart and scanner verdict
ConcurrencySame-name upload, folder move, quota reservation
FailureOrphan object, scanner crash, stale ACL cache
End-to-end/loadUpload → publish → share → revoke → restore; large multipart metadata pressure

Do not mock PostgreSQL constraints, transaction isolation, object-store semantics, or provider contract fixtures in the tests intended to prove those boundaries.

Deployment evolution

Stage 1
Metadata API, PostgreSQL, object storage, queue, scanner/preview workers, CDN
Stage 2
API replicas, isolated scanner pool, search, Redis, read replica, sync gateway
Stage 3
Workspace-sharded metadata cells and region-pinned buckets; global routing directory

Scale evolution

These are architecture scenarios, not throughput claims. Each transition should follow measured workload and reliability pressure.

ScenarioArchitecture may evolve towardDecision rule
1K usersOne API, PostgreSQL for the folder tree, ACLs, and quotas, object storage with presigned uploads, a scan workerKeep bytes out of the API with direct uploads; commit file metadata only after the stored object is verified
100K usersKeep bytes direct-to-storage; scale scan/preview independently; add metadata read replicas and searchScale scanning and previews independently when their backlog delays file availability; metadata keeps one primary
1M+ usersPartition workspaces into regional cells with one metadata writer; replicate immutable bytes under residency policyMove workspaces into regional cells for residency or metadata write load; immutable objects replicate safely