diff options
| author | 2026-02-21 20:21:55 +0800 | |
|---|---|---|
| committer | 2026-02-21 20:21:55 +0800 | |
| commit | b480c5c4dfaddb9b137f50b1a7651e3bce2ecb98 (patch) | |
| tree | 4952eb430959bbfb5eb95fe0bbcf1675fff02d34 /objectstore/packed | |
| parent | objectstore/packed: Best-effort touchCandidate (diff) | |
| signature | No signature | |
objectstore/packed: Improve delta base caching
Diffstat (limited to 'objectstore/packed')
| -rw-r--r-- | objectstore/packed/delta_apply.go | 86 | ||||
| -rw-r--r-- | objectstore/packed/delta_base.go | 40 | ||||
| -rw-r--r-- | objectstore/packed/delta_plan.go | 58 | ||||
| -rw-r--r-- | objectstore/packed/read_header.go | 4 |
4 files changed, 108 insertions, 80 deletions
diff --git a/objectstore/packed/delta_apply.go b/objectstore/packed/delta_apply.go index a067525d..6d5c736d 100644 --- a/objectstore/packed/delta_apply.go +++ b/objectstore/packed/delta_apply.go @@ -4,27 +4,33 @@ import ( "fmt" deltaapply "codeberg.org/lindenii/furgit/format/delta/apply" + packfmt "codeberg.org/lindenii/furgit/format/pack" "codeberg.org/lindenii/furgit/objecttype" ) // deltaResolveContent resolves one object's content bytes from its pack location. func (store *Store) deltaResolveContent(start location) (objecttype.Type, []byte, error) { - plan, err := store.deltaPlanFor(start) + chain, err := store.deltaBuildChain(start) if err != nil { return objecttype.TypeInvalid, nil, err } + return store.deltaResolveChain(chain) +} - baseType, out, err := store.deltaResolveBase(plan) +// deltaResolveChain resolves one object chain into content bytes. +func (store *Store) deltaResolveChain(chain deltaChain) (objecttype.Type, []byte, error) { + ty, out, nextDelta, err := store.deltaResolveChainStart(chain) if err != nil { return objecttype.TypeInvalid, nil, err } - for i := len(plan.frames) - 1; i >= 0; i-- { - frame := plan.frames[i] - pack, err := store.openPack(frame.packName) + + for i := nextDelta; i >= 0; i-- { + node := chain.deltas[i] + pack, err := store.openPack(node.loc.packName) if err != nil { return objecttype.TypeInvalid, nil, err } - delta, err := inflateAt(pack, frame.dataOffset, -1) + delta, err := inflateAt(pack, node.dataOffset, -1) if err != nil { return objecttype.TypeInvalid, nil, err } @@ -32,13 +38,75 @@ func (store *Store) deltaResolveContent(start location) (objecttype.Type, []byte if err != nil { return objecttype.TypeInvalid, nil, err } + store.cacheMu.Lock() + store.deltaCache.add( + deltaBaseKey{packName: node.loc.packName, offset: node.loc.offset}, + ty, + out, + ) + store.cacheMu.Unlock() } - if int64(len(out)) != plan.declaredSize { + + if int64(len(out)) != chain.declaredSize { return objecttype.TypeInvalid, nil, fmt.Errorf( "objectstore/packed: resolved content size mismatch: got %d want %d", len(out), - plan.declaredSize, + chain.declaredSize, + ) + } + if ty != chain.baseType { + return objecttype.TypeInvalid, nil, fmt.Errorf( + "objectstore/packed: resolved content type mismatch: got %d want %d", + ty, + chain.baseType, + ) + } + return ty, out, nil +} + +// deltaResolveChainStart finds the nearest cached chain node or inflates the +// innermost base object. It returns the starting bytes and the next delta index +// to apply in reverse order. +func (store *Store) deltaResolveChainStart(chain deltaChain) (objecttype.Type, []byte, int, error) { + for i, node := range chain.deltas { + store.cacheMu.RLock() + ty, out, ok := store.deltaCache.get( + deltaBaseKey{packName: node.loc.packName, offset: node.loc.offset}, ) + store.cacheMu.RUnlock() + if ok { + return ty, out, i - 1, nil + } + } + + store.cacheMu.RLock() + ty, out, ok := store.deltaCache.get( + deltaBaseKey{packName: chain.baseLoc.packName, offset: chain.baseLoc.offset}, + ) + store.cacheMu.RUnlock() + if ok { + return ty, out, len(chain.deltas) - 1, nil + } + + pack, meta, err := store.entryMetaAt(chain.baseLoc) + if err != nil { + return objecttype.TypeInvalid, nil, 0, err } - return baseType, out, nil + if !packfmt.IsBaseObjectType(meta.ty) { + return objecttype.TypeInvalid, nil, 0, fmt.Errorf("objectstore/packed: delta chain base is not a base object") + } + base, err := inflateAt(pack, meta.dataOffset, meta.size) + if err != nil { + return objecttype.TypeInvalid, nil, 0, err + } + + store.cacheMu.Lock() + store.deltaCache.add( + deltaBaseKey{packName: chain.baseLoc.packName, offset: chain.baseLoc.offset}, + meta.ty, + base, + ) + store.cacheMu.Unlock() + + return meta.ty, base, len(chain.deltas) - 1, nil } diff --git a/objectstore/packed/delta_base.go b/objectstore/packed/delta_base.go deleted file mode 100644 index b1113be7..00000000 --- a/objectstore/packed/delta_base.go +++ /dev/null @@ -1,40 +0,0 @@ -package packed - -import ( - "fmt" - - packfmt "codeberg.org/lindenii/furgit/format/pack" - "codeberg.org/lindenii/furgit/objecttype" -) - -// deltaResolveBase materializes the base object body for one delta plan. -func (store *Store) deltaResolveBase(plan deltaPlan) (objecttype.Type, []byte, error) { - cacheKey := deltaBaseKey{ - packName: plan.baseLoc.packName, - offset: plan.baseLoc.offset, - } - - store.cacheMu.RLock() - if ty, content, ok := store.deltaCache.get(cacheKey); ok { - store.cacheMu.RUnlock() - return ty, content, nil - } - store.cacheMu.RUnlock() - - pack, meta, err := store.entryMetaAt(plan.baseLoc) - if err != nil { - return objecttype.TypeInvalid, nil, err - } - if !packfmt.IsBaseObjectType(meta.ty) { - return objecttype.TypeInvalid, nil, fmt.Errorf("objectstore/packed: delta plan base is not a base object") - } - base, err := inflateAt(pack, meta.dataOffset, meta.size) - if err != nil { - return objecttype.TypeInvalid, nil, err - } - - store.cacheMu.Lock() - store.deltaCache.add(cacheKey, meta.ty, base) - store.cacheMu.Unlock() - return meta.ty, base, nil -} diff --git a/objectstore/packed/delta_plan.go b/objectstore/packed/delta_plan.go index 07dd5c77..f1a3b9b0 100644 --- a/objectstore/packed/delta_plan.go +++ b/objectstore/packed/delta_plan.go @@ -8,76 +8,76 @@ import ( "codeberg.org/lindenii/furgit/objecttype" ) -// deltaFrame describes one delta payload to apply during reconstruction. -type deltaFrame struct { - // packName identifies where the delta payload lives. - packName string +// deltaNode describes one delta object in a reconstruction chain. +type deltaNode struct { + // loc identifies the delta object's pack location. + loc location // dataOffset points to the start of the delta zlib payload in pack. dataOffset int } -// deltaPlan describes how to reconstruct one requested object. -type deltaPlan struct { +// deltaChain describes how to reconstruct one requested object. +type deltaChain struct { // declaredSize is the target object's declared content size. declaredSize int64 // baseLoc points to the innermost base object. baseLoc location // baseType is the canonical object type resolved from baseLoc. baseType objecttype.Type - // frames contains deltas from target down toward base. - frames []deltaFrame + // deltas contains delta objects from target down toward base. + deltas []deltaNode } -// deltaPlanFor walks one object's chain and builds a delta reconstruction plan. -func (store *Store) deltaPlanFor(start location) (deltaPlan, error) { +// deltaBuildChain walks one object's chain and builds a reconstruction chain. +func (store *Store) deltaBuildChain(start location) (deltaChain, error) { visited := make(map[location]struct{}) current := start - var plan deltaPlan - plan.declaredSize = -1 + var chain deltaChain + chain.declaredSize = -1 for { if _, ok := visited[current]; ok { - return deltaPlan{}, fmt.Errorf("objectstore/packed: delta cycle while resolving object") + return deltaChain{}, fmt.Errorf("objectstore/packed: delta cycle while resolving object") } visited[current] = struct{}{} pack, meta, err := store.entryMetaAt(current) if err != nil { - return deltaPlan{}, err + return deltaChain{}, err } - if plan.declaredSize < 0 { + if chain.declaredSize < 0 { if packfmt.IsBaseObjectType(meta.ty) { - plan.declaredSize = meta.size + chain.declaredSize = meta.size } else { declaredSize, err := deltaDeclaredSizeAt(pack, meta.dataOffset) if err != nil { - return deltaPlan{}, err + return deltaChain{}, err } - plan.declaredSize = declaredSize + chain.declaredSize = declaredSize } } if packfmt.IsBaseObjectType(meta.ty) { - plan.baseLoc = current - plan.baseType = meta.ty - return plan, nil + chain.baseLoc = current + chain.baseType = meta.ty + return chain, nil } switch meta.ty { case objecttype.TypeRefDelta: - plan.frames = append(plan.frames, deltaFrame{ - packName: current.packName, + chain.deltas = append(chain.deltas, deltaNode{ + loc: current, dataOffset: meta.dataOffset, }) next, err := store.lookup(meta.baseRefID) if err != nil { - return deltaPlan{}, err + return deltaChain{}, err } current = next case objecttype.TypeOfsDelta: - plan.frames = append(plan.frames, deltaFrame{ - packName: current.packName, + chain.deltas = append(chain.deltas, deltaNode{ + loc: current, dataOffset: meta.dataOffset, }) current = location{ @@ -85,11 +85,11 @@ func (store *Store) deltaPlanFor(start location) (deltaPlan, error) { offset: meta.baseOfs, } case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag: - return deltaPlan{}, fmt.Errorf("objectstore/packed: internal invariant violation for base type %d", meta.ty) + return deltaChain{}, fmt.Errorf("objectstore/packed: internal invariant violation for base type %d", meta.ty) case objecttype.TypeInvalid, objecttype.TypeFuture: - return deltaPlan{}, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty) + return deltaChain{}, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty) default: - return deltaPlan{}, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty) + return deltaChain{}, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty) } } } diff --git a/objectstore/packed/read_header.go b/objectstore/packed/read_header.go index c72188b9..63fc6d66 100644 --- a/objectstore/packed/read_header.go +++ b/objectstore/packed/read_header.go @@ -11,9 +11,9 @@ func (store *Store) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, er if err != nil { return objecttype.TypeInvalid, 0, err } - plan, err := store.deltaPlanFor(loc) + chain, err := store.deltaBuildChain(loc) if err != nil { return objecttype.TypeInvalid, 0, err } - return plan.baseType, plan.declaredSize, nil + return chain.baseType, chain.declaredSize, nil } |
