Repository navigation
Share LFS multipart upload sessions across replicas - #328
PaxMachinaOne merged 7 commits into
Conversation
Multipart upload sessions lived in the memory of the proxy pod that started them. With more than one replica behind the Service, the part and complete calls could reach another pod and get 404 upload_not_found, and the started S3 multipart upload was never aborted. The session is now a small JSON record in the LFS bucket at <namespace>/@lfs-uploads/<id>.json with the upload id, the parts and the checksum state, written again after every part, so any replica can continue an upload. A part or complete call on an expired session aborts the S3 multipart upload. Each replica with the HTTP API also removes expired sessions and aborts their uploads, and aborts LFS multipart uploads older than the session TTL plus one hour. Responses stay the same, apart from 502 s3_upload_failed when the session cannot be read from S3. Fixes KafScale#321
novatechflow
left a comment
There was a problem hiding this comment.
Review completed on f43dc3d. The review/fix/regression cycle found no remaining blockers in the multipart flow.
Validation: full go test -race ./..., full go vet ./..., the published Go API gate, and hosted CI/CodeQL pass. The authentication gate covers 100% of its required statements (736/736 across 15 files). Regression coverage uses real embedded etcd, shared fake S3, and a TCP Kafka acknowledgement fixture, including delayed RPCs, lost responses, stale owners, cancellation, preservation of an acknowledged blob after failed metadata deletion, and stale discard rejection before any S3 cleanup. Dedicated live MinIO/kind multipart fault injection was not run locally.
Release-note draft updated, with matching operations/API docs:
- Multipart sessions and authoritative checksums are shared through trusted etcd, allowing requests across replicas and pod restarts. etcd is required even with static brokers; operator and Compose deployments already configure it.
- Revision/owner fences, bounded cancellation cleanup, immutable per-part hashes, and identical-byte retries prevent stale writes and conflicting retries. Conflicting bytes return 409
part_conflict. - Completion requires every part exactly once in order and checks the expected checksum before S3 finalization. Lost successful S3 completion responses can be recovered; publication/discard markers prevent cleanup from deleting possibly Kafka-referenced blobs. Kafka retries remain at-least-once.
- Expiry/orphan cleanup honors persisted deadlines, protects active uploads through TTL changes, and fails closed on ownership-read failure. Required S3 permissions and etcd prefix permissions are documented.
- Restart uploads from earlier unsigned S3 sessions or development etcd records without durable part identities (410
upload_restart_required).
Summary
Fixes #321. Authoritative multipart sessions and checksums live in trusted etcd, so requests can move between LFS proxy replicas or survive a pod restart. S3 remains blob storage. Per-upload leased locks and revision/owner comparisons fence stale session writes and deletes, and lock-key/lease cleanup remains bounded after cancellation.
Part identities are persisted before S3 writes. All retries for a part must use identical bytes; conflicting bytes return 409
part_conflict, and failed attempts do not advance the checksum. Completion requires every part exactly once in order and checks the expected checksum before finalizing S3. A lost successful S3 completion response can be recovered by checking the existing unique object key and size. Durable publication/discard markers prevent stale cleanup from deleting possibly Kafka-referenced blobs. Kafka retry semantics remain at-least-once.Expiry uses persisted deadlines. The orphan sweep checks a trusted upload index, preserves active sessions through TTL changes, and performs no abort when ownership cannot be read. Session records/indexes are updated atomically. Record-size headroom supports metadata for all 10,000 S3 parts within default etcd request limits.
Deployment and release notes
KAFSCALE_PROXY_ETCD_ENDPOINTS, even with static brokers. Operator and Docker Compose deployments already configure it.upload_restart_required). Unknown future session versions are preserved rather than modified.docs/operations.md.s3:ListBucketMultipartUploadsands3:AbortMultipartUpload; ambiguous-completion recovery usess3:GetObject.docs/releases/unreleased.md, operations documentation, and multipart API responses are updated.Validation
go vet ./...and the published Go API compatibility gate pass.go test -race ./...,go vet ./..., and the published API gate pass on implementation commit78f6ca5. Follow-upf43dc3dadds a stale-discard regression for the 100% security coverage gate.f43dc3d, including the full race suite, 100% authentication coverage, Kafka/librdkafka/schema-registry/consumer-group end-to-end tests, Kubernetes API tests, and nested modules. Dedicated MinIO/kind multipart fault-injection QA was not run locally.