Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
b751aa3
feat(mutations): add per-predicate mutation pipeline
darkcoderrises May 5, 2026
0a7c1f2
chore(posting): strip debug fmt.Println from hot paths
matthewmcneely May 1, 2026
db61e4b
chore(posting): remove dead commented-out code
matthewmcneely May 1, 2026
c23489e
feat(mutations): gate per-predicate pipeline behind a feature flag
matthewmcneely May 1, 2026
30adee4
test(worker): document why TestCount is skipped
matthewmcneely May 1, 2026
365140f
fix(pipeline): scalar Del-of-old-value must not wipe new Set in Proce…
matthewmcneely May 1, 2026
c9349c7
fix(pipeline): InsertTokenizerIndexes deadlocks on uids >= 2^63
matthewmcneely May 1, 2026
732512e
feat(mutations): replace pipeline on/off flag with edge-count threshold
matthewmcneely May 1, 2026
8b32677
feat(mutations): flip default mutations-pipeline-threshold from 0 to 1
matthewmcneely May 1, 2026
eb83b8c
fix(posting): re-enable IsEmpty check in IterateDisk
matthewmcneely May 1, 2026
ef3eb6a
fix(pipeline): nil-deref in ProcessSingle on multi-Del-per-uid + stri…
matthewmcneely May 2, 2026
6ec42f7
fix(posting): defensive-copy reused key buffers in ReadPostingList + …
matthewmcneely May 2, 2026
bed2ea5
test(worker): four [uid] @reverse @count regression harnesses
matthewmcneely May 2, 2026
6ce7bed
test(systest): add query-073 covering reverse counts for all 592 non-…
matthewmcneely May 2, 2026
9916d7b
fix(pipeline): bg-indexed star-delete + scalar Set data-list corruption
matthewmcneely May 4, 2026
f999230
fix(pipeline): serialize ProcessVectorIndex to prevent HNSW corruption
matthewmcneely May 4, 2026
97609b3
chore: add description of pipeline feature flag
matthewmcneely May 5, 2026
f2e6462
Merge branch 'main' into harshil-goel/mutation-pipeline
matthewmcneely May 18, 2026
cf4a087
fix(pipeline): six correctness gaps in MutationPipeline
shiva-istari May 21, 2026
918262c
fix(pipeline): two correctness gaps surfaced by alpha integration tests
shiva-istari May 26, 2026
37c834a
perf(pipeline): proportional + auto goroutine budget, parallel Proces…
rahst12 Jun 23, 2026
ab0a49e
test(pipeline): schema/index-type matrix — byte-identical across 12 s…
rahst12 Jun 23, 2026
4a02d8e
perf(pipeline): parallel reverse write, lock-free store at any worker…
rahst12 Jul 26, 2026
612ae8c
perf(pipeline): lock-free forward write at a one-worker grant
rahst12 Jul 26, 2026
5b69ac2
perf(pipeline): batch conflict-key emission into one lock acquisition
rahst12 Jul 26, 2026
016a03a
perf(query): snapshot the uid lease once per mutation instead of per …
rahst12 Jul 26, 2026
363ca37
docs: record EC2 validation of the apply-path optimisation series
rahst12 Jul 26, 2026
6b7b22a
docs: conflict-key batching design and decision log
rahst12 Jul 26, 2026
3a43e3d
docs: pipeline backlog — remaining hotspots, flag debt, harness lessons
rahst12 Jul 26, 2026
04944f1
perf(types): size LockedShardedMap per machine, drop per-call hash alloc
rahst12 Jul 26, 2026
529c7c6
docs: record intra-transaction completion status and the measurement …
rahst12 Jul 26, 2026
b09e99d
docs: regression evidence — 4-way differential and pre-existing failures
rahst12 Jul 26, 2026
c99e7bb
docs: integration suite results — 39 packages, zero failures
rahst12 Jul 26, 2026
ae30b44
docs: production-replica benchmark — +17.6% on a realistic predicate mix
rahst12 Jul 27, 2026
308ea6b
docs: client-thread sweep — abort storm dominates this workload
rahst12 Jul 27, 2026
f76a0ae
refactor(flags): collapse the mutation-pipeline knobs into intra-muta…
rahst12 Jul 27, 2026
1155fc3
docs: record the flag simplification and what measuring it turned up
rahst12 Jul 27, 2026
e6ad704
docs: correct the abort-storm attribution, add the controls that sett…
rahst12 Jul 27, 2026
6e9e336
docs: move internal planning docs to the local plans repo
rahst12 Aug 8, 2026
5ce99cd
Merge remote-tracking branch 'origin/main' into rahst12/hybrid-pipeli…
rahst12 Aug 8, 2026
a670159
feat(metrics): add txns_per_delta histogram — Zero's delta batching d…
rahst12 Aug 9, 2026
c830f28
fix(flags): remove cross-txn-apply-workers from the --feature-flags help
rahst12 Aug 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ programmatic control over local Dgraph clusters. Most newer integration2 and upg

## Module Structure

The main module is `github.com/hypermodeinc/dgraph`
The main module is `github.com/dgraph-io/dgraph`

The codebase is organized into several key packages:

Expand Down
38 changes: 38 additions & 0 deletions dgraph/cmd/alpha/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,35 @@ they form a Raft group and provide synchronous replication.
"with structured fields including trace ID for correlation with distributed traces. "+
"Disabled by default (0). Note: enabling this logs query text which may contain "+
"sensitive data; do not enable in deployments with strict data privacy requirements.").
Flag("intra-mutation-min-edges",
"Minimum edge count for a mutation to take the parallel apply path, "+
"which spreads the predicates in ONE mutation across goroutines. "+
"0 disables that path entirely (all mutations take the legacy path) "+
"and is the single kill switch for intra-mutation parallelism. "+
"1 (default) always takes it. A value N>1 takes it only when "+
"len(edges) >= N, leaving small interactive mutations on the legacy "+
"path — the parallel path pays per-predicate goroutine spin-up cost, "+
"so tiny mutations are slightly slower on it; bulk multi-predicate "+
"mutations are faster (crossover ~100 edges in benchmarks here).").
Flag("intra-mutation-parallelism",
"Concurrent worker goroutines used INSIDE a single mutation, shared "+
"across the predicates it touches and apportioned by edge count so a "+
"hot predicate is granted most of them and tiny predicates get one "+
"each. 'off' = one worker per predicate, no fan-out. 'N' = exactly N "+
"workers. 'Fx' = F workers per CPU available to Go (GOMAXPROCS, which "+
"respects container CPU limits), e.g. '1.5x'. 'auto' (default) = '1x'. "+
"Always further capped by intra-mutation-edges-per-worker. NOTE this "+
"parallelizes ONE mutation — transactions still apply serially, so it "+
"will not relieve a many-concurrent-writers bottleneck.").
Flag("intra-mutation-edges-per-worker",
"Minimum edges a worker should have to justify existing: the pool set by "+
"intra-mutation-parallelism is capped at totalEdges divided by this, "+
"so small mutations do not over-spawn. Mirrors the 256-edge "+
"DivideAndRule rule. Applies to every sizing mode, not just 'Fx'. On a "+
"large box this is frequently the BINDING constraint — at the default "+
"a 20k-edge mutation caps at 78 workers however many cores exist — so "+
"lower it, not the multiplier, to get more parallelism there. "+
"Default 256.").
String())

RegisterFlags(flag)
Expand Down Expand Up @@ -744,6 +773,15 @@ func run() {
x.Config.NormalizeCompatibilityMode = featureFlagsConf.GetString("normalize-compatibility-mode")
enableDetailedMetrics := featureFlagsConf.GetBool("enable-detailed-metrics")
x.WorkerConfig.SlowQueryLogThreshold = featureFlagsConf.GetDuration("log-slow-query-threshold")
x.WorkerConfig.IntraMutationMinEdges = int(featureFlagsConf.GetInt64("intra-mutation-min-edges"))
// The superflag layer cannot validate this one — it is off|auto|N|Fx, not a
// plain int — so a typo must fail here at startup rather than silently
// resolve to the zero value and disable fan-out for the life of the process.
parallelism, err := x.ParseIntraMutationParallelism(
featureFlagsConf.GetString("intra-mutation-parallelism"))
x.Checkf(err, "while parsing --feature-flags")
x.WorkerConfig.IntraMutationParallelism = parallelism
x.WorkerConfig.IntraMutationEdgesPerWorker = int(featureFlagsConf.GetInt64("intra-mutation-edges-per-worker"))

x.PrintVersion()
glog.Infof("x.Config: %+v", x.Config)
Expand Down
4 changes: 4 additions & 0 deletions graphql/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,10 @@ func newAdminResolver(
return
}

if len(pl.Postings) == 2 && string(pl.Postings[0].Value) == "_STAR_ALL" {
pl.Postings = pl.Postings[1:]
}

// There should be only one posting.
if len(pl.Postings) != 1 {
glog.Errorf("Only one posting is expected in the graphql schema posting list but got %d",
Expand Down
153 changes: 153 additions & 0 deletions posting/conflict_keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
* Golden test for the conflict keys that actually reach Zero.
*
* Every other conflict-key test in this package compares budget=0 against
* budget=N and reads txn.conflicts DIRECTLY. That catches a divergence between
* the serial and parallel paths, but it cannot catch a UNIFORM regression —
* one that changes both paths the same way (dropping the "skip zero" guard,
* mis-deriving a key, losing a call site). It also asserts nothing about
* FillContext, which is the function that actually converts the set into the
* api.TxnContext.Keys sent to Zero's conflict detector.
*
* This test pins that output against a golden captured before the conflict-key
* batching change, and asserts the keys do not depend on startTs (which is what
* makes a hardcoded golden legitimate).
*/

package posting

import (
"context"
"testing"

"github.com/stretchr/testify/require"

"github.com/dgraph-io/dgo/v250/protos/api"
"github.com/dgraph-io/dgraph/v25/protos/pb"
"github.com/dgraph-io/dgraph/v25/schema"
"github.com/dgraph-io/dgraph/v25/x"
)

// goldenFillContextKeys is the exact ctx.Keys emitted by fillContextFixture at
// commit 612ae8cd5 (before conflict-key batching). Sorted and deduped by
// x.Unique inside FillContext, so it is stable.
var goldenFillContextKeys = []string{
"10cqjlaf4j7hn", "11lxsz9zty2i4", "11rszn4j2ifrp", "15a7susihkrs5",
"15hr881x3365h", "15ssyzlmd8o6", "160144qa0z7c9", "16g45dj9u9wtl",
"16gddhvir9r5o", "17wz11s2rsjb0", "18r29da831jyv", "19v83k15qewy",
"19v83k15qf35", "19v83k15qf3g", "19v83k15qf3r", "1bn2uz26zl65",
"1csiiy24nvqav", "1csiiy24nvqb6", "1csiiy24nvqbh", "1cths9gcm15ya",
"1cths9gcm15yj", "1cths9gcm15yo", "1cths9gcm15yx", "1cths9gcm15z2",
"1cths9gcm15zx", "1cuw68vo5k0sx", "1d6h1mmm85zkh", "1dejkfi3t87uo",
"1dejkfi3t87uz", "1dejkfi3t87va", "1djh0on4tr7e7", "1djh0on4tr7im",
"1djh0on4tr7is", "1djh0on4tr7ix", "1exa3ehusmxld", "1ft3jzj1tai8s",
"1h0g0919r4ocj", "1h0g0919r4ocs", "1h0g0919r4od5", "1h0g0919r4of6",
"1j83u2a12v1ed", "1jdh8bcmnzjp9", "1jqs81nsjjhcd", "1mfllyn8p9mqy",
"1ni683zm14p37", "1nzb90h02v1ji", "1o1h924e2w8mw", "1o8pxqowvsz38",
"1ov8p3us03icd", "1q0yfx1ywiji", "1qihbzhyr587k", "1sjzq7432wmoj",
"1u3j524i9s5ky", "1vfnls53w4667", "1whetkryqjuqv", "1whetkryqjuro",
"1whetkryqjus1", "1whetkryqjusa", "1wxxpl268f9ai", "1xzpryyns5gd1",
"20g6onqogazec", "21m5fh88fejnf", "25j2xbwglqjei", "26sqlzuotoul3",
"278p0khy2r4bq", "27kronoo44soq", "27p1kzees9ayu", "281qho9c7jv02",
"28ht75d2lsrcz", "2b3pcywmvntws", "2b8pbxzp9xdg5", "2c6y486xygbnj",
"2cdvaxxkbeviz", "2d9ke6yxfknpj", "2ewhr6a3zocql", "2g6bgltmlq3f",
"2g92qcof8ed3k", "2ggs3cvcwaiz7", "2gnlxpa7apx06", "2hboxqqdmpyoa",
"2hptl0ky70li1", "2j0x0rx05xwwn", "2j7nzcgt3tg2p", "2jbbcctsnf9ro",
"2labkkz61gs9q", "2m96ie69oxeej", "2mynehoop9r44", "2mzimus36p2hz",
"2nwk2prfmoh9e", "2nwk2prfmohad", "2nwk2prfmoham", "2nwk2prfmohao",
"2nwk2prfmohav", "2nwk2prfmohax", "2pug92p0njx6w", "2pug92p0njx77",
"2pug92p0njx7i", "2qeyy1qrvq1ld", "2rauvzl3oxawo", "2wbm5aydy5tgg",
"2z4k5bs14nthf", "304we5tysszao", "30cmwr1gt1wlh", "314vcu3mju0jp",
"31iqzow4zlj45", "32ds5k706j92m", "32zfm4ay6zcaj", "33cfumtxb0glw",
"33drou0jikak3", "33jj1jcfb0z98", "33jj1jcfb0z9d", "33jj1jcfb0z9y",
"33jj1jcfb0za3", "34cn0esjmfu3o", "35jmgbjjkh8m6", "36u6n2bsbgngj",
"383okwwymw1by", "38a2ly4gdf2vk", "3a77iqqzkqg5p", "3chush5nnfw3j",
"3dv5y81l44ztv", "3dv5y81l44ztw", "3dv5y81l44zu5", "3dv5y81l44zu9",
"3dv5y81l44zui", "3dv5y81l44zuw", "3ekw731h56sjb", "3fyvp2gb6k3eu",
"3fyvp2gb6k3f1", "3fyvp2gb6k3f4", "3fyvp2gb6k3fj", "3fyvp2gb6k3hv",
"3fyvp2gb6k3hw", "3h8iwbbbmy9gz", "3i03t92jon73g", "3iwmbwzy03xbw",
"3jcin7c3g1zpf", "3jcin7c3g1zvr", "3jcin7c3g1zvw", "3jcin7c3g1zw5",
"3jcin7c3g1zwa", "3kvjt2iy5h2hd", "3lv7emyt3j9x5", "3mr4yt3pdct4r",
"3n1300qedgip9", "3p00hgoufmvxg", "3ri95zn5gkbc6", "3t8ugshh500c",
"3tofm6wdxi5iv", "3vzymxuljypwu", "4p47rm1wmn4u", "5zvwwexrvcxu",
"6lhiubvn7tg", "7svgt3i2uiw2", "8nyqq3jqcx8m", "8nyqq3jqcx95",
"8nyqq3jqcxbn", "90afjmkvqjh", "9tw59jomuq26", "adauhy6jj6sg",
"at8qvmgud4v9", "bgfl7a3jcbsc", "bzhvrptojpya", "bzhvrptojpys",
"bzhvrptojpz3", "bzhvrptojq39", "es4kwi7vusy0", "gghvi9ijw2zm",
"gghvi9ijw347", "gghvi9ijw34e", "gghvi9ijw34l", "gghvi9ijw34r",
"gghvi9ijw34s", "h9ew5io0bo44", "ibbghd28i0h4", "ietvcnshejbg",
"k131yhlfmlej", "khxohb579335", "lcc6rinevxt5", "muojk8zz9w0w",
"muojk8zz9w1t", "muojk8zz9w22", "muojk8zz9w2n", "ozd4b9biaeu1",
"pfx112nlxs4m", "pmr9d40ky1b4", "pmr9d40ky1bd", "pmr9d40ky1bq",
"pmr9d40ky1bz", "pmr9d40ky1g2", "r1xso673xr1m", "scl1q4kj5tev",
"sqyvtqgvi3ki", "ss117ufjqdru", "ttuep1j4ee49", "u5u8ncldw4k8",
"umk34xrekawv", "v001pn39tpox", "vdbosf0xcryy", "vvhfvkcuf3bk",
"w9k46kpeuwwr", "wmvftaeam6l3", "x36thxe2e6hn", "ytkefiufo8ur",
"zoywn6yafefw",
}

// fillContextFixture applies a fixed multi-predicate batch exercising several
// distinct conflict-key derivations — scalar index, [uid] @reverse (which emits
// per-target keys), @upsert (bare fingerprint, no uid XOR), and @noconflict
// (must emit nothing) — then returns FillContext's ctx.Keys.
func fillContextFixture(t *testing.T, startTs uint64, budget int) []string {
t.Helper()

require.NoError(t, pstore.DropAll())
MemLayerInstance.clear()
require.NoError(t, schema.ParseBytes([]byte(`
cname: string @index(exact) .
clink: [uid] @reverse .
cup: string @index(exact) @upsert .
cnc: string @index(exact) @noconflict .
`), 1))

ob := x.WorkerConfig.IntraMutationParallelism
x.WorkerConfig.IntraMutationParallelism = x.IntraMutationParallelism{Workers: budget}
defer func() { x.WorkerConfig.IntraMutationParallelism = ob }()

cname := x.AttrInRootNamespace("cname")
clink := x.AttrInRootNamespace("clink")
cup := x.AttrInRootNamespace("cup")
cnc := x.AttrInRootNamespace("cnc")

var edges []*pb.DirectedEdge
for i := 0; i < 40; i++ {
ent := uint64(700_000 + i)
edges = append(edges,
strEdge(cname, ent, "n"+string(rune('a'+i%7))),
strEdge(cup, ent, "u"+string(rune('a'+i%5))),
strEdge(cnc, ent, "c"+string(rune('a'+i%3))),
uidEdge(clink, ent, uint64(800_000+i%11)),
)
}

txn := NewTxn(startTs)
mp := NewMutationPipeline(txn)
require.NoError(t, mp.Process(context.Background(), edges))

ctx := &api.TxnContext{}
txn.FillContext(ctx, 1, true) // isErrored=true: skip txn.Update(), keep the test to Keys
return ctx.Keys
}

// TestFillContextKeysGolden pins the conflict keys that reach Zero.
func TestFillContextKeysGolden(t *testing.T) {
// A hardcoded golden is only valid if the keys are startTs-independent.
// Conflict keys derive from fingerprint(keyBytes) ^ posting.Uid, so they
// should be — assert it rather than assume it.
a := fillContextFixture(t, 4_100_000, 0)
b := fillContextFixture(t, 4_200_000, 0)
require.Equal(t, a, b, "conflict keys must not depend on startTs")
require.NotEmpty(t, a, "fixture emitted no conflict keys")

if len(goldenFillContextKeys) == 0 {
t.Fatalf("GOLDEN NOT SET — captured %d keys:\n%#v", len(a), a)
}
require.Equal(t, goldenFillContextKeys, a,
"ctx.Keys sent to Zero changed; a SUBSET risks lost updates")

// The parallel path must emit the same set as the serial path.
require.Equal(t, a, fillContextFixture(t, 4_300_000, 32),
"budget>1 must emit identical ctx.Keys")
}
Loading
Loading