System Graph

Asset Upload Initialization

Frontend
  │
  │ POST /assets
  │ Idempotency-Key: req-123
  ▼
Go Asset API
  │
  ├─ authenticate / authorize
  │
  ├─ generate candidate
  │     asset_id   = 123
  │     object_key = assets/123/original.jpg
  │
  ├─ atomic upsert ───────────────→ MongoDB
  │                                  │
  │                                  │ assets
  │                                  │ {
  │                                  │   asset_id: "123",
  │                                  │   owner_id: "user-7",
  │                                  │   idempotency_key: "req-123",
  │                                  │   object_key:
  │                                  │     "assets/123/original.jpg",
  │                                  │   status: "UPLOADING"
  │                                  │ }
  │                                  │
  │                                  │ UNIQUE
  │                                  │ (owner_id, idempotency_key)
  │                                  │
  │                                  ├─ new request
  │                                  │    → insert candidate
  │                                  │
  │                                  └─ retry
  │                                       → return existing asset
  │
  ├─ status = UPLOADING
  │     → generate fresh pre-signed PUT URL
  │       for asset.object_key
  │
  ├─ status = PROCESSING / READY
  │     → no upload URL
  │
  └─ return
       {
         asset_id,
         status,
         upload_url?
       }

Asset Upload Event Publishing

Frontend
  │
  │ PUT image bytes using pre-signed URL
  ▼
 S3
  │
  ├─ verify signature + expiration
  ├─ store: assets/123/original.jpg
  │    
  └─ emit ObjectCreated
       │
       ▼
      SQS
       │
       │ stores S3 event message
       │ {
       │   bucket: ...,
       │   object_key: assets/123/original.jpg,
       │   sequencer: ...
       │ }
       │
       ▼
Lambda bridge
  │
  ├─ poll SQS: message becomes temporarily invisible (SQS visibility timeout)
  │
  ├─ parse S3 event
  │
  ├─ derive asset_id
  │     assets/123/original.jpg
  │          ↓
  │     asset_id = 123
  │
  ├─ derive stable event_id
  │     from S3 event identity
  │
  ├─ build AssetUploaded event
  │     {
  │       event_id: evt-A,
  │       asset_id: 123,
  │       object_key: assets/123/original.jpg
  │     }
  │
  └─ publish to Kafka
       │
       ├─ publish succeeds
       │     │
       │     ▼
       │  Lambda returns SUCCESS (Delete SQS message automatically)
       │
       └─ publish fails/timeout
             │
             ▼
          Lambda returns ERROR (SQS message becomes visible again)
                                   │
                                   └─ after too many failures → SQS DLQ

Asset Processing Consumer

Kafka
  │
  │ key = asset_id = 123
  │
  │ AssetUploaded
  │ partition = 7
  │ offset    = 314
  │ {
  │   event_id: evt-A,
  │   asset_id: 123,
  │   object_key: assets/123/original.jpg
  │ }
  ▼
ECS Asset processor
  │
  ├─ atomically claim event ─────────────→ MongoDB
  │                                        │
  │                                        │ consumer_inbox
  │                                        │ {
  │                                        │   consumer: "asset-processor",
  │                                        │   event_id: "evt-A",
  │                                        │   state: "PROCESSING",
  │                                        │   lease_owner: "worker-7",
  │                                        │   lease_until: ...
  │                                        │ }
  │                                        │
  │                                        │ unique:
  │                                        │ (consumer, event_id)
  │                                        │
  │                                        ├─ COMPLETED
  │                                        │     → duplicate already handled
  │                                        │     → commit Kafka offset
  │                                        │
  │                                        ├─ PROCESSING + valid lease
  │                                        │     → another worker owns it
  │                                        │     → DO NOT process
  │                                        │     → DO NOT commit offset
  │                                        │
  │                                        └─ missing / expired lease
  │                                              → acquire lease
  │                                              → continue
  │
  ├─ update asset ───────────────────────→ MongoDB
  │                                        │
  │                                        └─ asset.status = PROCESSING
  │
  ├─ GET original asset ────────────────→ S3
  │
  ├─ validate / resize / classify / transform
  │
  ├─ PUT derived assets ────────────────→ S3
  │                                        │
  │                                        └─ deterministic object keys
  │                                           assets/123/thumb.jpg
  │
  ├─ retryable failure
  │      │
  │      ├─ release lease / let lease expire
  │      ├─ keep inbox state non-COMPLETED
  │      └─ DO NOT commit Kafka offset
  │               │
  │               ▼
  │             retry
  │
  ├─ permanent failure
  │      │
  │      └─ MongoDB transaction
  │           │
  │           ├─ asset.status = FAILED
  │           │
  │           └─ consumer_inbox
  │              {
  │                 event_id: "evt-A",
  │                 state: "COMPLETED"
  │              }
  │
  │          transaction commits
  │               │
  │               └─ commit Kafka offset
  │
  └─ success
         │
         ├─ generate AssetReady event
         │     event_id     = evt-B
         │     asset_id     = 123
         │     causation_id = evt-A
         │
         └─ MongoDB transaction
              │
              ├─ asset.status = READY
              │
              ├─ consumer_inbox
              │   {
              │      event_id: "evt-A",
              │      state: "COMPLETED"
              │   }
              │
              └─ insert outbox_events
                  {
                     event_id: "evt-B",
                     type: "AssetReady",
                     asset_id: "123",
                     causation_id: "evt-A",
                     published: false
                  }
                   
               transaction commits

Assets Upload & Download

Storage Horizontal Scaling

MongoDB sharding

With MongoDB, horizontal scaling usually means sharding.

MongoDB cluster
 ├─ Shard 1
 ├─ Shard 2
 └─ Shard 3
 
Shard 1 replica set
  ├─ Tokyo
  ├─ Tokyo
  └─ Singapore

Shard 2 replica set
  ├─ Tokyo
  ├─ Singapore
  └─ Sydney

You decide or configure how data is distributed

S3 distributed scaling

AWS handles the underlying horizontal scaling. Normally, no cross-region replication. (CDN needs to come in)