// Delta-aware refinalize: a Builder thawed from a Snapshot tracks which// components diverge from that source, and Finalize shares -- aliases -- the// clean ones into the successor instead of rebuilding them. A one-property// edit no longer costs O(n + m).//// The safety argument is the snapshot contract itself: snapshots are strictly// immutable and the Manager swap is the commit point, so a successor holding// its source's arrays can never observe a mutation, and Go's GC owns the// shared backings. Nothing here touches the read path -- traversal still// scans flat CSR arrays, with no delta overlay or merge-on-read.//// Sharing granularity is the component: each CSR direction, each property// column, each label bitmap, the atom table, and the lazily built indexes// keyed on them.package chickpeasimport// markNodeColDirty records that key's node column diverges from the source// snapshot, so Finalize rebuilds it from staged pairs rather than aliasing.// A no-op on a builder with no source, where nothing can be shared anyway.func ( *Builder) ( PropertyKey) {if .src == nil {return }if .dirtyNodeCols == nil { .dirtyNodeCols = map[PropertyKey]struct{}{} } .dirtyNodeCols[] = struct{}{}}// markRelColDirty records that key's rel column diverges from the source.func ( *Builder) ( PropertyKey) {if .src == nil {return }if .dirtyRelCols == nil { .dirtyRelCols = map[PropertyKey]struct{}{} } .dirtyRelCols[] = struct{}{}}// markLabelDirty records that label's node membership diverges from the// source, so its bitmap rebuilds.func ( *Builder) ( Label) {if .src == nil {return }if .dirtyLabels == nil { .dirtyLabels = map[Label]struct{}{} } .dirtyLabels[] = struct{}{}}// markRelsDirty records a rel add or removal. Every rel-derived component --// both CSR directions, inToOut, the type index, every rel column (which is// keyed by CSR position), and the forest-root caches -- rebuilds, and the// source's staging-index -> CSR-position map is dropped with them.func ( *Builder) () {if .src == nil {return } .relsDirty = true .srcRelToOutCSR = nil}// aliasPlan is Finalize's one-shot decision of which source components the// successor may share, plus a record of what actually aliased so the lazy// caches keyed on those components can carry forward. A builder with no// source gets a plan that aliases nothing.type aliasPlan struct { b *Builder src *Snapshot// csr: both CSR directions, inToOut, and the type index alias. csr bool// idSpace: the CSR id space is unchanged, so node-column spans -- and // with them the dense/rank/sparse layout selection -- match the source. idSpace bool// labels: no label bitmap diverged, so the whole label index aliases and // the O(n) label scan is skipped outright. labels bool aliasedNodeCols map[PropertyKey]struct{} aliasedRelCols map[PropertyKey]struct{} aliasedLabels map[Label]struct{}// allLabels records that finalizeLabels aliased the label index wholesale. allLabels bool}// newAliasPlan resolves the builder's dirty state against the shape Finalize// is about to produce: n id slots and m rels.func ( *Builder) (, int) *aliasPlan { := &aliasPlan{b: ,src: .src,aliasedNodeCols: map[PropertyKey]struct{}{},aliasedRelCols: map[PropertyKey]struct{}{},aliasedLabels: map[Label]struct{}{}, }if .src == nil {return } .idSpace = == int(.src.CSRIDSpace())// The offset arrays span the id space and the neighbor arrays span the // rel count, so a clean rel set is not enough -- a node added past the // old maximum (or a retired maximum) rewrites both offset arrays. .csr = !.relsDirty && .idSpace && == len(.src.outNbrs) && len(.srcRelToOutCSR) == .labels = len(.dirtyLabels) == 0return}// nodeColumn returns the source column to share for key, when the column is// untouched and its span is unchanged. Sharing also carries the column// bit-identically, which sidesteps the dense-column "never set vs zero"// conflation that a rebuild through thaw-staged pairs re-applies.func ( *aliasPlan) ( PropertyKey) (Column, bool) {if .src == nil || !.idSpace {returnnil, false }if , := .b.dirtyNodeCols[]; {returnnil, false } , := .src.columns[]if ! {returnnil, false } .aliasedNodeCols[] = struct{}{}return , true}// relColumn returns the source rel column to share for key. Rel columns are// stored by outgoing-CSR position, so they may only alias alongside the CSR.func ( *aliasPlan) ( PropertyKey) (Column, bool) {if .src == nil || !.csr {returnnil, false }if , := .b.dirtyRelCols[]; {returnnil, false } , := .src.relColumns[]if ! {returnnil, false } .aliasedRelCols[] = struct{}{}return , true}// labelClean reports whether label's membership is untouched and the source// holds a bitmap for it.func ( *aliasPlan) ( Label) bool {if .src == nil {returnfalse }if , := .b.dirtyLabels[]; {returnfalse } , := .src.labelIndex[]return}// aliasCSR shares both CSR directions and the type index (a pure function of// the outgoing types) with the source. inToOut follows when the successor has// rel properties to read through it; when the source had none it was never// built, and the successor's first rel column pays for it once.func ( *aliasPlan) ( *Snapshot, bool) { := .src .outOffsets, .outNbrs, .outTypes = .outOffsets, .outNbrs, .outTypes .inOffsets, .inNbrs, .inTypes = .inOffsets, .inNbrs, .inTypesmaps.Copy(.typeIndex, .typeIndex) .hasRelProps = if ! {return }// The successor reads through the SAME CSR positions, so it shares the // source's position map rather than deriving an identical copy -- forcing // the source's lazy build once and completing g's Once with the shared // slice, so getInToOut returns it without re-deriving. (A rebuilt CSR // takes a different path and never reaches aliasCSR.) := .getInToOut() .inToOutOnce.Do(func() { .inToOut = })}// aliasAtoms shares the source atom table when nothing new was interned. The// interner is seeded from the source and only ever appends, so equal lengths// mean equal tables -- and skipping the copy matters at LDBC scale, where the// table holds millions of strings.func ( *aliasPlan) ( *Builder) *Atoms {if .src != nil && .interner.Len() == .src.atoms.Len() {return .src.atoms }return .interner.Atoms()}// carryLazyCaches hands the source's built lazy indexes to the successor for// every entry whose underlying components aliased. propIndex, full-text, geo,// forest-root, and sparse-column position indexes are expensive builds that// would otherwise die with the source snapshot at the Manager swap. Entries// touching a rebuilt component are dropped and rebuild on demand.//// The cached values are immutable once built and already shared across// concurrent queries, so handing out the same pointers is safe; only the// source's cache maps need their locks while they are read.func ( *Snapshot) ( *aliasPlan) { := .srcif == nil {return } := func( Label) bool {if .allLabels { , := .labelIndex[]return } , := .aliasedLabels[]return } := func( PropertyKey) bool { , := .aliasedNodeCols[]return } .propIndexMu.Lock()for , := range .propIndex {// The label-free index (anyLabel) is a pure function of the column.if (.key) && (.label == anyLabel || (.label)) { .propIndex[] = } } .propIndexMu.Unlock() .fulltextMu.Lock()for , := range .fulltextIndex {if (.key) && (.label) { .fulltextIndex[] = } } .fulltextMu.Unlock() .geoMu.Lock()for , := range .geoIndex {if (.latKey) && (.lonKey) && (.label) { .geoIndex[] = } } .geoMu.Unlock()if .csr { .rootsMu.Lock()maps.Copy(.rootsViaIndex, .rootsViaIndex) .rootsMu.Unlock() } .colPosMu.Lock()for , := range .colPosIndex {if () { .colPosIndex[] = } } .colPosMu.Unlock() .relColPosMu.Lock()for , := range .relColPosIndex {if , := .aliasedRelCols[]; { .relColPosIndex[] = } } .relColPosMu.Unlock()}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.