gitcaskDocsGitHub

Cost model

Every user-visible latency is a sum of sequential bucket requests, so round trips are the performance design.

The latency primitives

The bucket is the only durable primitive. Single-object CAS is its only transaction.

PrimitiveCostNotes
GET or PUT of a small object60–80 ms p50/p99One request is one round trip
Conditional GET (If-None-Match) answering 30415–18 msThe cheapest "is anything new?"
HEAD≈ GETNever added on a happy path
404FreeProbe, don’t list
LISTSlow, paged, eventually-ishNever on a hot path
CAS overwrite of one objectSerialized, ~1 write/sA throughput cap; 412 is the normal contention signal
Range read of a big object~100 MB/s per connectionStripe for more; bulk bytes on their own pool

Depth before count

Two PUTs in parallel cost one round trip. A healthy push is 6 requests in 4 rounds.

Round 1GET manifest.pb
Round 2PUT packPUT idxPUT log
Round 3CAS manifest.pb
Round 4PUT pending/<o>/<r>
A healthy push: round 1 a manifest freshness GET, round 2 the pack, index and log PUTs in parallel, round 3 the manifest CAS, round 4 the pending-marker PUT.

The pending marker follows the CAS so an uncommitted push cannot wake maintenance. Its failure never changes the committed push.

Budgets per operation

Happy path: sequential depth, then total store requests.

OperationDepthRequests
Any read (info/refs, ls-refs, API refs and resolve)1 conditional GET (or 0 within wal.freshness_ttl)1
Cold refs sync1 manifest GET, then 1 round of checkpoint refs ∥ log tail segmentsNo checkpoint: 1 + tail (2 with one segment); checkpoint: 2 + tail
PushFreshness GET → pack PUT ∥ idx PUT ∥ log PUT → manifest CAS → best-effort pending-marker PUTRequest: 6; already-synced publish: 5
Ref write APIFreshness GET → optional tag pack PUT ∥ idx PUT ∥ log PUT → manifest CAS → pending-marker PUTRef-only: 4; annotated tag: 6
Commit or merge write APIFreshness GET → new-object pack PUT ∥ idx PUT ∥ log PUT → manifest CAS → pending-marker PUTCommit or merge commit: 6; fast-forward: 4; already merged: 1
CheckpointConditional GET → refs PUT ∥ checkpoint PUT → manifest CAS3 rounds, 4 requests
Lease acquire1 GET → 1 CAS put (or 1 Create when absent)2
Maintainer pending pass1 bounded LIST, then each repository keeps its existing unit depthExisting requests per repository; up to maintenance.workers repositories overlap
Authentication (introspect mode)0 on a cache hit; 1 HTTP request to the introspection URL per miss0 store requests

Cost follows pushes

A push writes a pending/ marker and the maintainer lists only those. Nothing lists repos/, so idle cost does not grow with repository count.

RepositoriesStore requests over 5 idle minutes
100118
1,000118
50,000120

Architectureshows the marker's path through the maintainer.

Rules of thumb

Let the conditional write be the read

A 412 on Create means it exists; a 412 on Update means someone moved it. Don't GET first.

Verify on the failure path

Probes run only after a failed Create or CAS. The happy path never pays for rare cases.

Batch at the CAS

Group commit (wal.batch_window) turns N concurrent pushes into one log PUT and one CAS.

Carry state in the manifest

Every request fetches it anyway, so a reader never needs a second request to know what to fetch.

Jitter every retry

A synchronized retry storm serializes itself on a 1 write/s object.

Acceptance bars

ClaimBar
Cold instance is useful in secondsls-remoteof any repository < 1 s on a fresh instance
Cache is a cacheStop the server, delete cache.dir, start it: every repository clones again from the bucket
PushAcknowledged only after the bucket ACKs; one CAS per batch
ConsistencyPush then fetch anywhere sees it; concurrent pushers: exactly one winner
Cost modelA maintainer pass touches only repositories with a pending marker; no periodic LIST of repos/ anywhere
Transient store errors5xx or throttling on any store operation is retried with backoff and never surfaces as a failed push on its own