Metadata service, direct uploads and the post-upload pipeline
coreadvancedA file-storage product is two systems that must not be one. Object storage holds bytes: it is cheap, effectively unbounded, and knows nothing about who owns a file or what it is called. A metadata service holds everything else — ownership, folder structure, versions, sharing rules, scan status, timestamps — in a database that can be queried, indexed and transacted. Keeping them separate is what makes "list my files", "who has access to this", and "show me last week's version" cheap queries rather than scans over a bucket. The upload flow then has an unusual shape, because you want the bytes to go straight from the client to object storage without passing through your servers, while keeping the decision about whether the upload is allowed on your servers. A signed URL does exactly that: your metadata service authorises the request, creates a pending record, and hands back a short-lived credential scoped to one object key; the client uploads directly, in parts if the file is large; and completion is confirmed either by the client calling back or by a storage event, at which point the metadata record moves from pending to available. Everything expensive happens after that, in the background — virus scanning, thumbnail and preview generation, text extraction, checksum verification — because none of it should hold up the upload, and a file that is stored but not yet scanned has a state, not a problem. Lifecycle rules then age objects into cheaper storage classes and delete them on schedule, which is the part that keeps a storage product's cost from growing with its total history rather than its useful data.
Think of it as
A warehouse and its index cards. The warehouse takes pallets and gives back a location code; it is very good at storing things and completely unable to answer "what did the accounting department put in here last March". The index cards answer that, and they can be sorted, cross-referenced and copied without touching a single pallet. Deliveries go straight to the loading dock rather than through the office — but the office issues the docket that lets the dock accept them, and nothing is considered received until the card is written. Inspection happens after the pallet is on a shelf, not while the truck waits.
What we're doing: Trace a 4 GB upload from authorisation to availability, including the parts that go wrong.
- 12
- Part-level retry is the reason multipart matters at this size. A single-request upload that fails at 90% has to start again; here one 5 MB part is retried and the other 859 are untouched.
- 19
- The storage event, not the client, is what moves the record forward. A client that finished uploading and then crashed, lost connectivity, or was closed by the user would otherwise leave a completed object with a `pending` row forever.
- 27
- This sweep is not optional housekeeping. Incomplete multipart uploads keep consuming storage that no listing shows, so a design without this job pays for bytes it cannot see and cannot explain.
Why this works: The trace shows the division of labour that makes the design work: your servers handle authorisation, records and orchestration — all small, fast, transactional operations — while four gigabytes never touch them. Everything slow is either the client's problem (uploading parts) or asynchronous (scanning), so no request in your system is ever waiting on a large file.
Proxying uploads and downloads through your application servers
Wrong
Better
What you see: Application servers run out of memory or connections during ordinary use, and scaling them up helps only until a few more large uploads arrive. Egress costs are double what they should be, because every downloaded byte is paid for twice — once out of storage, once out of your servers.
Why: A file upload occupies a worker for as long as the client's network takes, which is unbounded and unrelated to how much work your code is doing. Signed URLs move the transfer to a service built for it, leaving your servers doing the part they are good at: deciding whether the transfer is allowed and recording that it happened.
- Client
- leads to Metadata service (start upload)
- leads to Object storage (PUT bytes directly)
- Metadata service — authorises, records pending, issues a signed URL
- leads to Client (signed URL)
- leads to Background pipeline
- Object storage — receives the bytes directly, in parts if large
- leads to Storage event
- leads to Lifecycle rules
- Storage event — object created — the authoritative completion signal
- leads to Metadata service (mark uploaded)
- Background pipeline — scan, checksum, thumbnails, text extraction
- leads to State: available
- State: available
- Lifecycle rules — age to colder classes, expire on schedule
Which system answers which question
The metadata record's lifecycle
Remember: Split the system in two: object storage holds bytes by key, and a metadata service holds ownership, hierarchy, versions, sharing and scan state where they can be queried and transacted. Authorise on your servers and transfer off them, using short-lived signed URLs and multipart uploads so a failed part is retried rather than a whole file. Record `pending` before issuing the URL, confirm completion from a storage event rather than a client callback, sweep abandoned uploads, run scanning and derivative generation in the background with a safe default state, and let lifecycle rules age and expire objects so cost tracks useful data.
See also: multipart upload and signed urls · object storage vs application servers · separating metadata from blobs · multipart chunking async and progress tracking · ttls lifecycle rules and archival pipelines · common vulnerability classes

