Skip to content

Commit 2f9a474

Browse files
Merge branch 'main' into sp/dedup-neighbor-updates
2 parents 38e50cd + a1499c6 commit 2f9a474

7 files changed

Lines changed: 228 additions & 48 deletions

File tree

.trunk/trunk.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ lint:
2828
enabled:
2929
3030
31-
- trivy@0.65.0
31+
- trivy@0.69.3
3232
3333
3434

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,51 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
66
to [Semantic Versioning](https://semver.org). When adding a new entry, please use the entries below
77
as a guide.
88

9+
## [v25.3.0] - 2026-03-05
10+
11+
[v25.3.0]: https://github.com/dgraph-io/dgraph/compare/v25.2.0...v25.3.0
12+
13+
- **Added**
14+
15+
- **Bulk Loader**
16+
- feat(bulk): add a "skip reduce" flag to the bulk loader (#9618)
17+
- feat(bulk): add HNSW vector index support to bulk loader (#9598)
18+
- feat(bulk): add error logging to bulk loader (#9601)
19+
20+
- **Test**
21+
- feat(test): simplify macOS testing with automatic cross-compilation (#9585)
22+
23+
- **Fixed**
24+
25+
- **Core**
26+
- fix(posting): prevent stale cache hits when maxTs < readTs (#9597) (#9614)
27+
- fix(core): fix opentelemetry upgrade issues (#9595)
28+
- fix: fetch schema over network to ensure @unique check across groups (#9596)
29+
30+
- **Test**
31+
- fix(test): resolve macOS binary selection and test data clobbering (#9606)
32+
- fix(test): macOS local image, plugin tests, and test suite restructuring (#9610)
33+
- fix(test): make TestDropNamespaceErr resilient to async namespace operations (#9589)
34+
35+
- **Changed**
36+
37+
- **Perf**
38+
- perf(concurrency): cancel remaining goroutines when error occurs (#9484)
39+
40+
- **Chore**
41+
- chore: update docs references (#9617)
42+
- chore(test): Clean up testing guide and Makefile testing dependency checks (#9611)
43+
- chore: address CVEs (Feb '26 edition) (#9608)
44+
> **WARNING** In #9608, badger will no longer be copied into the Dgraph image. You can download
45+
> it from [the releases page](https://github.com/dgraph-io/dgraph/releases).
46+
- chore: suppress receipt of graphql request to logging level 2 (#9612)
47+
- chore(core): use provider.RetrieveWithCredContext instead of deprecated provider.Retrieve
48+
(#9551)
49+
- chore: remove hardcoded GOMAXPROCS; print cpu info in startup-banner (#9600)
50+
51+
- **Dependency Updates**
52+
- chore(deps): bump go.opentelemetry.io/otel/sdk from 1.39.0 to 1.40.0 (#9616)
53+
954
## [v25.2.0] - 2026-01-28
1055

1156
[v25.2.0]: https://github.com/dgraph-io/dgraph/compare/v25.1.0...v25.2.0

contrib/RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This document outlines the steps needed to build and push a new release of Dgrap
99
[ci-dgraph-weekly-upgrade-tests](https://github.com/dgraph-io/dgraph/actions/workflows/ci-dgraph-weekly-upgrade-tests.yml),
1010
it takes about 60-70 minutes… Note that you only need to do this if there are changes on the main
1111
branch after the last run (it runs weekly on Sunday nights).
12-
1. Update the CHANGELOG.md. Sonnet 4.5 does a great job of doing this. Example prompt:
12+
1. Update the CHANGELOG.md. GenAI does a great job of doing this. Example prompt:
1313
`I'm releasing vXX.X.X off the main branch, add a new entry for this release. Conform to the "Keep a Changelog" format, use past entries as a formatting guide. Run the trunk linter on your changes.`.
1414
1. Validate the version does not have storage incompatibilities with the previous version. If so, add a warning to the CHANGELOG.md
1515
that export/import of data will need to be run as part of the upgrade process.

dgraph/cmd/bulk/loader.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type BulkOptions struct {
5151
MapBufSize uint64
5252
PartitionBufSize int64
5353
SkipMapPhase bool
54+
SkipReducePhase bool
5455
CleanupTmp bool
5556
NumReducers int
5657
Version bool
@@ -144,7 +145,7 @@ type loader struct {
144145
dg *dgo.Dgraph
145146
}
146147

147-
func newLoader(opt *BulkOptions) *loader {
148+
func newLoader(opt *BulkOptions, precomputedWriteTs uint64) *loader {
148149
if opt == nil {
149150
log.Fatalf("Cannot create loader with nil options.")
150151
}
@@ -185,17 +186,27 @@ func newLoader(opt *BulkOptions) *loader {
185186
fmt.Printf("Error logging enabled, writing to: %s\n", opt.ErrorLogPath)
186187
}
187188

189+
writeTs := precomputedWriteTs
190+
if writeTs == 0 {
191+
writeTs = getWriteTimestamp(zero, dg)
192+
}
188193
st := &state{
189194
opt: opt,
190195
prog: newProgress(),
191196
shards: newShardMap(opt.MapShards),
192197
// Lots of gz readers, so not much channel buffer needed.
193198
readerChunkCh: make(chan *chunkWithMeta, opt.NumGoroutines),
194-
writeTs: getWriteTimestamp(zero, dg),
199+
writeTs: writeTs,
195200
namespaces: &sync.Map{},
196201
errorLog: errLog,
197202
}
198-
st.schema = newSchemaStore(readSchema(opt), opt, st)
203+
var parsedSchema *schema.ParsedSchema
204+
if !opt.SkipMapPhase {
205+
parsedSchema = readSchema(opt)
206+
} else {
207+
parsedSchema = &schema.ParsedSchema{}
208+
}
209+
st.schema = newSchemaStore(parsedSchema, opt, st)
199210
ld := &loader{
200211
state: st,
201212
mappers: make([]*mapper, opt.NumGoroutines),

dgraph/cmd/bulk/run.go

Lines changed: 88 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ func init() {
7878
flag.Int64("partition_mb", 4, "Pick a partition key every N megabytes of data.")
7979
flag.Bool("skip_map_phase", false,
8080
"Skip the map phase (assumes that map output files already exist).")
81+
flag.Bool("skip_reduce_phase", false,
82+
"Skip the reduce phase (stops after map phase completion).")
8183
flag.Bool("cleanup_tmp", true,
8284
"Clean up the tmp directory after the loader finishes. Setting this to false allows the"+
8385
" bulk loader can be re-run while skipping the map phase.")
@@ -150,6 +152,7 @@ func run() {
150152
MapBufSize: uint64(Bulk.Conf.GetInt("mapoutput_mb")),
151153
PartitionBufSize: int64(Bulk.Conf.GetInt("partition_mb")),
152154
SkipMapPhase: Bulk.Conf.GetBool("skip_map_phase"),
155+
SkipReducePhase: Bulk.Conf.GetBool("skip_reduce_phase"),
153156
CleanupTmp: Bulk.Conf.GetBool("cleanup_tmp"),
154157
NumReducers: Bulk.Conf.GetInt("reducers"),
155158
Version: Bulk.Conf.GetBool("version"),
@@ -205,27 +208,29 @@ func RunBulkLoader(opt BulkOptions) {
205208
}
206209
fmt.Printf("Encrypted input: %v; Encrypted output: %v\n", opt.Encrypted, opt.EncryptedOut)
207210

208-
if opt.SchemaFile == "" {
209-
// if only graphql schema is provided, we can generate DQL schema from it.
210-
if opt.GqlSchemaFile == "" {
211-
fmt.Fprint(os.Stderr, "Schema file must be specified.\n")
212-
os.Exit(1)
211+
if !opt.SkipMapPhase {
212+
if opt.SchemaFile == "" {
213+
// if only graphql schema is provided, we can generate DQL schema from it.
214+
if opt.GqlSchemaFile == "" {
215+
fmt.Fprint(os.Stderr, "Schema file must be specified.\n")
216+
os.Exit(1)
217+
}
218+
} else {
219+
if !filestore.Exists(opt.SchemaFile) {
220+
fmt.Fprintf(os.Stderr, "Schema path(%v) does not exist.\n", opt.SchemaFile)
221+
os.Exit(1)
222+
}
213223
}
214-
} else {
215-
if !filestore.Exists(opt.SchemaFile) {
216-
fmt.Fprintf(os.Stderr, "Schema path(%v) does not exist.\n", opt.SchemaFile)
224+
if opt.DataFiles == "" {
225+
fmt.Fprint(os.Stderr, "RDF or JSON file(s) location must be specified.\n")
217226
os.Exit(1)
218-
}
219-
}
220-
if opt.DataFiles == "" {
221-
fmt.Fprint(os.Stderr, "RDF or JSON file(s) location must be specified.\n")
222-
os.Exit(1)
223-
} else {
224-
fileList := strings.SplitSeq(opt.DataFiles, ",")
225-
for file := range fileList {
226-
if !filestore.Exists(file) {
227-
fmt.Fprintf(os.Stderr, "Data path(%v) does not exist.\n", file)
228-
os.Exit(1)
227+
} else {
228+
fileList := strings.SplitSeq(opt.DataFiles, ",")
229+
for file := range fileList {
230+
if !filestore.Exists(file) {
231+
fmt.Fprintf(os.Stderr, "Data path(%v) does not exist.\n", file)
232+
os.Exit(1)
233+
}
229234
}
230235
}
231236
}
@@ -240,6 +245,20 @@ func RunBulkLoader(opt BulkOptions) {
240245
opt.NumReducers, opt.ReduceShards)
241246
os.Exit(1)
242247
}
248+
249+
// Validate skip phase flags
250+
if opt.SkipMapPhase && opt.SkipReducePhase {
251+
fmt.Fprint(os.Stderr, "Cannot skip both map and reduce phases.\n")
252+
os.Exit(1)
253+
}
254+
if opt.SkipReducePhase {
255+
if Bulk.Cmd.Flags().Changed("cleanup_tmp") && opt.CleanupTmp {
256+
fmt.Fprint(os.Stderr, "Cannot use --skip_reduce_phase with --cleanup_tmp=true. "+
257+
"Temp files must be preserved for the later reduce phase.\n")
258+
os.Exit(1)
259+
}
260+
opt.CleanupTmp = false
261+
}
243262
if opt.CustomTokenizers != "" {
244263
for _, soFile := range strings.Split(opt.CustomTokenizers, ",") {
245264
tok.LoadCustomTokenizer(soFile)
@@ -267,25 +286,28 @@ func RunBulkLoader(opt BulkOptions) {
267286

268287
// Make sure it's OK to create or replace the directory specified with the --out option.
269288
// It is always OK to create or replace the default output directory.
270-
if opt.OutDir != defaultOutDir && !opt.ReplaceOutDir {
271-
err := x.IsMissingOrEmptyDir(opt.OutDir)
272-
if err == nil {
273-
fmt.Fprintf(os.Stderr, "Output directory exists and is not empty."+
274-
" Use --replace_out to overwrite it.\n")
275-
os.Exit(1)
276-
} else if err != x.ErrMissingDir {
277-
x.CheckfNoTrace(err)
289+
// Skip output directory validation if we're only doing map phase
290+
if !opt.SkipReducePhase {
291+
if opt.OutDir != defaultOutDir && !opt.ReplaceOutDir {
292+
err := x.IsMissingOrEmptyDir(opt.OutDir)
293+
if err == nil {
294+
fmt.Fprintf(os.Stderr, "Output directory exists and is not empty."+
295+
" Use --replace_out to overwrite it.\n")
296+
os.Exit(1)
297+
} else if err != x.ErrMissingDir {
298+
x.CheckfNoTrace(err)
299+
}
278300
}
279-
}
280301

281-
// Delete and recreate the output dirs to ensure they are empty.
282-
x.Check(os.RemoveAll(opt.OutDir))
283-
for i := range opt.ReduceShards {
284-
dir := filepath.Join(opt.OutDir, strconv.Itoa(i), "p")
285-
x.Check(os.MkdirAll(dir, 0700))
286-
opt.shardOutputDirs = append(opt.shardOutputDirs, dir)
302+
// Delete and recreate the output dirs to ensure they are empty.
303+
x.Check(os.RemoveAll(opt.OutDir))
304+
for i := range opt.ReduceShards {
305+
dir := filepath.Join(opt.OutDir, strconv.Itoa(i), "p")
306+
x.Check(os.MkdirAll(dir, 0700))
307+
opt.shardOutputDirs = append(opt.shardOutputDirs, dir)
287308

288-
x.Check(x.WriteGroupIdFile(dir, uint32(i+1)))
309+
x.Check(x.WriteGroupIdFile(dir, uint32(i+1)))
310+
}
289311
}
290312

291313
// Create a directory just for bulk loader's usage.
@@ -303,10 +325,26 @@ func RunBulkLoader(opt BulkOptions) {
303325
x.Check(os.MkdirAll(bufDir, 0700))
304326
defer os.RemoveAll(bufDir)
305327

306-
loader := newLoader(&opt)
307-
308328
const bulkMetaFilename = "bulk.meta"
309329
bulkMetaPath := filepath.Join(opt.TmpDir, bulkMetaFilename)
330+
const writeTsFilename = "write.ts"
331+
writeTsPath := filepath.Join(opt.TmpDir, writeTsFilename)
332+
333+
var precomputedWriteTs uint64
334+
if opt.SkipMapPhase {
335+
writeTsData, err := os.ReadFile(writeTsPath)
336+
if err != nil {
337+
fmt.Fprintln(os.Stderr, "Error reading write timestamp file; was --skip_reduce_phase used in the map run?")
338+
os.Exit(1)
339+
}
340+
precomputedWriteTs, err = strconv.ParseUint(strings.TrimSpace(string(writeTsData)), 10, 64)
341+
if err != nil {
342+
fmt.Fprintln(os.Stderr, "Error parsing write timestamp file")
343+
os.Exit(1)
344+
}
345+
}
346+
347+
loader := newLoader(&opt, precomputedWriteTs)
310348

311349
if opt.SkipMapPhase {
312350
bulkMetaData, err := os.ReadFile(bulkMetaPath)
@@ -343,7 +381,20 @@ func RunBulkLoader(opt BulkOptions) {
343381
fmt.Fprintln(os.Stderr, "Error writing to bulk meta file")
344382
os.Exit(1)
345383
}
384+
if err = os.WriteFile(writeTsPath, []byte(strconv.FormatUint(loader.writeTs, 10)), 0600); err != nil {
385+
fmt.Fprintln(os.Stderr, "Error writing write timestamp file")
386+
os.Exit(1)
387+
}
346388
}
389+
390+
if opt.SkipReducePhase {
391+
fmt.Println("Skipping reduce phase. Map phase completed successfully.")
392+
fmt.Println("Temp files preserved for later reduce phase processing.")
393+
// Don't call cleanup() to preserve temp files
394+
loader.prog.endSummary()
395+
return
396+
}
397+
347398
loader.reduceStage()
348399
loader.writeSchema()
349400
loader.cleanup()

dgraphtest/load.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -475,12 +475,15 @@ func (c *LocalCluster) LiveLoadFromExport(exportDir string) error {
475475
}
476476

477477
type BulkOpts struct {
478-
DataFiles []string
479-
SchemaFiles []string
480-
GQLSchemaFiles []string
481-
OutDir string
482-
MapShards int // Number of map shards (0 = auto based on numAlphas/replicas)
483-
ReduceShards int // Number of reduce shards (0 = auto based on numAlphas/replicas)
478+
DataFiles []string
479+
SchemaFiles []string
480+
GQLSchemaFiles []string
481+
OutDir string
482+
MapShards int // Number of map shards (0 = auto based on numAlphas/replicas)
483+
ReduceShards int // Number of reduce shards (0 = auto based on numAlphas/replicas)
484+
SkipReducePhase bool // Stop after map phase; preserve tmp dir for later reduce
485+
SkipMapPhase bool // Skip map phase; assumes map output files already exist
486+
TmpDir string // Custom tmp directory (required when splitting map/reduce runs)
484487
}
485488

486489
func (c *LocalCluster) BulkLoad(opts BulkOpts) error {
@@ -518,6 +521,16 @@ func (c *LocalCluster) BulkLoad(opts BulkOpts) error {
518521
"--http", ":0",
519522
}
520523

524+
if opts.TmpDir != "" {
525+
args = append(args, "--tmp", opts.TmpDir)
526+
}
527+
if opts.SkipReducePhase {
528+
args = append(args, "--skip_reduce_phase")
529+
}
530+
if opts.SkipMapPhase {
531+
args = append(args, "--skip_map_phase")
532+
}
533+
521534
if len(opts.DataFiles) > 0 {
522535
args = append(args, "-f", strings.Join(opts.DataFiles, ","))
523536
}

0 commit comments

Comments
 (0)