count

package
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 2 Imported by: 0

Documentation

Overview

Package count holds the derived, non-durable relationship count-store that backs exact cardinality estimates for the Cypher planner (design docs/count-store-design.md, task #2082). It maintains three relationship statistics keyed by the stable interned ids of the graph's single label/relationship-type registry:

E(relType)            — live edges of a relationship type
D(label, relType, dir)— degree-sum: edge endpoints of relType in a direction
                        whose this-end node carries label
T(labelA, relType, labelB) — live edges (:labelA)-[:relType]->(:labelB)

The node statistic N(label) is NOT stored here; it is read from the existing label index (see cypher/api.go ResolveLabelCount).

Structure

Each cell is an atomic.Int64 held in one of a fixed number of shards; a cell is created on first observation of a combination and DELETED when its counter returns to zero, so the store's footprint is bounded by the number of currently-observed schema combinations — a function of schema cardinality, never of |V| or |E| (design §2.3). Keys are the registry's uint32 ids, so no string touches the hot path.

Concurrency contract

The Store is safe for concurrent use with the following discipline, which the Cypher engine already provides: all MUTATIONS (Store.Apply, Store.MarkDirty, Store.RecomputeReset) are serialised by the engine's write barrier (visMu.Lock in commitUnderBarrier), and all READS (Store.CountE/Store.CountD/ Store.CountT and the dirty predicates) run under the query's read barrier (visMu.RLock in Graph.View). The per-shard sync.RWMutex and the dirty-set mutex are defence-in-depth that keep any non-barrier access path race-free; the atomic cells make an individual counter read lock-free regardless. The store spawns no goroutines.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Delta

type Delta struct {
	A     uint32    // KindD: label; KindT: labelA; KindE: unused.
	RT    uint32    // relationship-type id.
	B     uint32    // KindT: labelB; otherwise unused.
	Delta int64     // signed increment (+1 on create, -1 on remove).
	Kind  Kind      // which family this delta targets.
	Dir   Direction // KindD only.
}

Delta is one buffered increment to a single count cell. It is a small value carried by copy; a transaction accumulates a slice of them in the engine's CountBuffer and applies them at commit via Store.Apply.

func DDelta

func DDelta(label, rt uint32, dir Direction, sign int64) Delta

DDelta builds a D(label, relType, dir) increment.

func EDelta

func EDelta(rt uint32, sign int64) Delta

EDelta builds an E(relType) increment.

func TDelta

func TDelta(a, rt, b uint32, sign int64) Delta

TDelta builds a T(labelA, relType, labelB) increment.

type Direction

type Direction uint8

Direction selects which end of a relationship a [D] degree-sum counts.

const (
	// Out counts the source endpoint's label (n)-[rt]->().
	Out Direction = iota
	// In counts the destination endpoint's label ()-[rt]->(n).
	In
)

type DirtyMark

type DirtyMark struct {
	Label uint32
	Scope DirtyScope
}

DirtyMark records that a family becomes non-exact for one label id, buffered alongside deltas and applied at commit via Store.MarkDirty. See design §3.3.1: a relabel whose IN-side cannot be enumerated in O(delta) marks the minimal X-scoped IN cells dirty rather than writing a wrong exact.

type DirtyScope

type DirtyScope uint8

DirtyScope selects which X-scoped exactness set a DirtyMark toggles off.

const (
	// DirtyDOut marks D(label, *, OUT) untrustworthy for a label.
	DirtyDOut DirtyScope = iota
	// DirtyDIn marks D(label, *, IN) untrustworthy for a label.
	DirtyDIn
	// DirtyTA marks T(label, *, *) untrustworthy (the a-position).
	DirtyTA
	// DirtyTB marks T(*, *, label) untrustworthy (the b-position).
	DirtyTB
)

type Kind

type Kind uint8

Kind selects which count family a Delta targets.

const (
	// KindE targets E(relType); only RT and Delta are read.
	KindE Kind = iota
	// KindD targets D(label, relType, dir); A (the label), RT, Dir and Delta are read.
	KindD
	// KindT targets T(labelA, relType, labelB); A, RT, B and Delta are read.
	KindT
)

type Snapshot

type Snapshot struct {
	E         map[uint32]int64
	DOut      map[uint64]int64
	DIn       map[uint64]int64
	T         map[[3]uint32]int64
	DirtyDOut []uint32
	DirtyDIn  []uint32
	DirtyTA   []uint32
	DirtyTB   []uint32
}

Snapshot is a point-in-time copy of every live cell and dirty marking, for observability and differential testing. The D keys are dkey(label, relType) = label<<32|relType; the T keys are [3]uint32{labelA, relType, labelB}. The dirty slices list the label ids currently marked non-exact in each family.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store is the sharded relationship count-store. Its zero value is not usable; construct one with New.

func New

func New(maxRecountEdges int) *Store

New returns an empty, ready-to-use Store whose per-relabel OUT-side recount ceiling is maxRecountEdges (design §3.3.1). A maxRecountEdges of 0 or less disables the ceiling (the OUT side is always recounted exactly).

func (*Store) Apply

func (s *Store) Apply(d Delta)

Apply applies one buffered delta to its cell. A key is created on first observation and deleted when its counter returns to zero (bounded growth). A zero delta is a no-op. Apply is a mutation and must be serialised by the caller's write barrier (see the package concurrency contract).

func (*Store) Cells

func (s *Store) Cells() int

Cells reports the number of distinct live count cells currently held — the sum over every shard of the E, D(out), D(in) and T map sizes. Because a cell is deleted the moment its counter returns to zero ([Store.add]), every map entry is a live combination, so this is an exact, allocation-free size indicator for observability: it is bounded by the number of currently-observed schema combinations (design §2.3), never by |V| or |E|. It is a read taken under the shard read locks and is safe to call concurrently with the barrier-serialised writers. The metrics [Backend] exposes no gauge, so this is the accessor an observer reads to surface the store's footprint (task #2087).

func (*Store) CountD

func (s *Store) CountD(label, rt uint32, dir Direction) int64

CountD returns the degree-sum D(label, rt, dir) (0 when absent). It ignores the dirty flag; callers that need the exactness verdict consult Store.DDirty.

func (*Store) CountE

func (s *Store) CountE(rt uint32) int64

CountE returns the live edge count of relationship type rt (0 when absent).

func (*Store) CountT

func (s *Store) CountT(a, rt, b uint32) int64

CountT returns the triple count T(a, rt, b) (0 when absent). It ignores the dirty flag; callers that need the exactness verdict consult Store.TDirty.

func (*Store) DDirty

func (s *Store) DDirty(label uint32, dir Direction) bool

DDirty reports whether D(label, *, dir) is currently non-exact.

func (*Store) MarkDirty

func (s *Store) MarkDirty(m DirtyMark)

MarkDirty toggles off the exactness of one X-scoped family set. It is a mutation and must be serialised by the caller's write barrier.

func (*Store) MaxRecountEdges

func (s *Store) MaxRecountEdges() int

MaxRecountEdges reports the per-relabel OUT-side recount ceiling (0 or less means unbounded). The relabel maintenance consults it to decide between an exact OUT-side recount and an X-scoped OUT dirty marking (design §3.3.1).

func (*Store) RecomputeReset

func (s *Store) RecomputeReset()

RecomputeReset clears every cell and every dirty flag, returning the store to its empty state. It is the seam an O(V+E) recompute-from-graph (task #2084) resets before replaying the create-deltas of every live edge; clearing the dirty sets restores full exactness. It is a mutation and must be serialised by the caller's write barrier.

func (*Store) Snapshot

func (s *Store) Snapshot() Snapshot

Snapshot returns a copy of every live cell (value > 0) and every dirty marking. It is a read taken under the shard and dirty read locks, so it is safe to call concurrently with the barrier-serialised writers.

func (*Store) TDirty

func (s *Store) TDirty(a, b uint32) bool

TDirty reports whether T(a, *, b) is currently non-exact — true when either the a-position label or the b-position label has been marked dirty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL