Artifact ff64a8b0527e300273c7bc25d75cc7059980193620b9692f601e7ca467bdf901:
- File
internal/sync/horizon_memo.go
— part of check-in
[62ffc8ff92]
at
2026-10-22 22:31:27
on branch feat/horizon-cones
— perf(sync): extend the memo by the arrivals' metadata alone
Measured on a dense 68k-artifact, 21k check-in repository, a horizon pull after a one-artifact push still spent most of its time scanning every blob's metadata to extend the memo, and a structural request extended it twice: once to place the horizon ahead of the request's transaction, unpublished, and again inside it.
An extension now carries the artifact names over and reads metadata only for the arrivals and the delta ancestors their reads expand. A structural request places its horizon in a read-only transaction of its own, a committed snapshot, and publishes that view, so the inventory finds the memo current.
TestCorpusHorizonPullAfterPush (LIBFOSSIL_CORPUS) times it: a pull after a one-artifact push goes from about 2.7s to 0.2s for a content horizon, and from about 4.4s to 0.3s for a structural one. (user: [email protected], size: 3979) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "runtime" gosync "sync" "weak" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/cone" "github.com/danmestas/go-libfossil/internal/content" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" ) // originMemos holds the classification memo of each store this process // serves (draft-fossil-horizon-00 ยง6.2), keyed by the store's pool DB and // dropped when that DB is collected. var originMemos = struct { gosync.Mutex m map[weak.Pointer[db.DB]]*content.Memo }{m: map[weak.Pointer[db.DB]]*content.Memo{}} // originMemo returns d's memo, nil when d is bound to a transaction it does // not own: a memo is published only after the request that read the store // commits, which such a caller decides. func originMemo(d *db.DB) *content.Memo { if d == nil { panic("sync.originMemo: nil DB") } if d.Bound() { return nil } key := weak.Make(d) originMemos.Lock() defer originMemos.Unlock() if m := originMemos.m[key]; m != nil { return m } m := content.NewMemo() originMemos.m[key] = m runtime.AddCleanup(d, func(key weak.Pointer[db.DB]) { originMemos.Lock() defer originMemos.Unlock() delete(originMemos.m, key) }, key) return m } // classifier is how an origin classifies a request's cone: over the store // read afresh, or over its memo. type classifier interface { references(ctx context.Context, q db.Querier, c *cone.Cone) (*content.ConeReferences, error) below(ctx context.Context, q db.Querier, refs *content.ConeReferences, c *cone.Cone) (func(libfossil.FslID) bool, error) reach(ctx context.Context, q db.Querier, h cone.Horizon) ([]libfossil.FslID, map[string]bool, bool, error) // read is how many artifacts classifying read from the store. read(refs *content.ConeReferences) int } type freshClassifier struct{} func (freshClassifier) references(ctx context.Context, q db.Querier, c *cone.Cone) (*content.ConeReferences, error) { return content.LoadConeReferences(ctx, q, c) } func (freshClassifier) below( ctx context.Context, q db.Querier, refs *content.ConeReferences, c *cone.Cone, ) (func(libfossil.FslID) bool, error) { return refs.HorizonBelow(ctx, q, c) } func (freshClassifier) reach(ctx context.Context, q db.Querier, h cone.Horizon) ([]libfossil.FslID, map[string]bool, bool, error) { return content.HorizonReach(ctx, q, h) } func (freshClassifier) read(refs *content.ConeReferences) int { return refs.Artifacts() } type memoClassifier struct{ v *content.MemoView } func (m memoClassifier) references(ctx context.Context, _ db.Querier, c *cone.Cone) (*content.ConeReferences, error) { return m.v.References(ctx, c) } func (m memoClassifier) below( ctx context.Context, q db.Querier, refs *content.ConeReferences, c *cone.Cone, ) (func(libfossil.FslID) bool, error) { return m.v.HorizonBelow(ctx, q, refs, c) } func (m memoClassifier) reach(ctx context.Context, q db.Querier, h cone.Horizon) ([]libfossil.FslID, map[string]bool, bool, error) { return m.v.HorizonReach(ctx, q, h) } func (m memoClassifier) read(*content.ConeReferences) int { return m.v.Read() } // classifier returns how this request classifies over q: through the // store's memo when it has one, else afresh. The memo's view is the // handler's to publish. func (h *handler) classifier(ctx context.Context, q db.Querier) (classifier, error) { if h.memo == nil { return freshClassifier{}, nil } v, err := h.memo.View(ctx, q) if err != nil { return nil, err } h.view = v return memoClassifier{v}, nil } // publishView hands the memo what this request's classification computed, // once the request is known to have committed and to have stored no // artifact: the inventory classifies before the request's received files // are crosslinked, so a view from such a request describes a store that // never committed. func (h *handler) publishView() { if h.view == nil || h.fatal || h.aborted || h.filesRecvd > 0 { return } h.view.Publish() }