Repository navigation
DDIR: child scopes read their parent's arrangements - #931
Merged
Merged
Conversation
A child scope used to receive every import as rows. An arrangement in the parent was flattened at the boundary, and each join, reduce, or arrange of it inside the child built a new one. Imports now arrive as the parent rendered them, and an arrangement enters the child's region with `Arranged::enter_region`, sharing the trace. Every scope has the same time type, so no `TraceEnter` is needed, and compaction requests need no translation. A reader inside a loop asks for compaction at iteration times, which is sound: each handle's frontier lower bounds the times its reader will ask about, and the trace compacts to the meet of its handles. Sharing happens when the parent item is an arrangement: an explicit `arrange`, or a reduce. `lower` places the arrange for a join in the joining scope, so a child joining a parent's collection still arranges it unless the parent does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frankmcsherry
added a commit
to frankmcsherry/differential-dataflow
that referenced
this pull request
Oct 12, 2026
A program that imported a published trace received it as rows, and each of its joins, reduces, and arranges of the import built a private copy of an arrangement the producer already maintains. Imports now arrive as that arrangement, entered into the program's scope (`TraceEnter`), on both backends. vec: `Arr` is a local or an imported arrangement. Join and reduce take either through the cursor tactics, as an entered batch's cursor lifts its times. corgi: times are integer lanes, and a host epoch `e` and the program time `(e, [])` have the same lanes, so an imported chunk is read at the program's time by sharing it: `CorgiChunk::retime` casts the `Rc`, which `repr(C)` on the chunk's contents and `ColTimes` makes sound. `RetimeJoin` and `RetimeReduce` wrap the proxy tactics and retime imported batches as they cross in, one `Rc` per chunk. Columnar exports reach columnar programs this way. An imported arrangement also enters child scopes, iterative ones included, as a program's own arrangements do (TimelyDataflow#931). An import that a scope also reads as rows is flattened once per scope, and the rows enter its children with the arrangement. `Server::set_arranged_imports(false)`, or DDIR_ARRANGED_IMPORTS=0 for the server binary, restores rows. On vec a shape ascription is checked by a side operator over the trace's batches; on corgi an arranged import's ascription is not checked (its chunks carry the producer's shapes). examples/import_bench: one producer of 1M edges, then 8 consumers each joining the import twice, 1000 edges replaced per tick. vec corgi rows arranged rows arranged install-to-answer, ms ~420 ~3 ~100 ~1 memory per consumer, MiB ~120 ~1 ~32 ~0.4 tick, 8 consumers, ms 13-19 1.6 3.9-4.7 0.8 Consumers that join the import inside a loop (reachability) are alike. After 500 ticks of churn the published trace holds the same updates with 0 or 8 importers sharing it. Tests: answers with and without arranged imports agree on each backend, and the backends agree, for a join and a reduce of an import at the root, a join inside a loop, and the import read as rows, through changes and an importer installed late. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
frankmcsherry
added a commit
to frankmcsherry/differential-dataflow
that referenced
this pull request
Oct 12, 2026
A program that imported a published trace received it as rows, and each of its joins, reduces, and arranges of the import built a private copy of an arrangement the producer already maintains. Imports now arrive as that arrangement, entered into the program's scope (`TraceEnter`), on both backends. vec: `Arr` is a local or an imported arrangement. Join and reduce take either through the cursor tactics, as an entered batch's cursor lifts its times. corgi: times are integer lanes, and a host epoch `e` and the program time `(e, [])` have the same lanes, so an imported chunk is read at the program's time by sharing it: `CorgiChunk::retime` casts the `Rc`, which `repr(C)` on the chunk's contents and `ColTimes` makes sound. `RetimeJoin` and `RetimeReduce` wrap the proxy tactics and retime imported batches as they cross in, one `Rc` per chunk. Columnar exports reach columnar programs this way. An imported arrangement also enters child scopes, iterative ones included, as a program's own arrangements do (TimelyDataflow#931). An import that a scope also reads as rows is flattened once per scope, and the rows enter its children with the arrangement. `Server::set_arranged_imports(false)`, or DDIR_ARRANGED_IMPORTS=0 for the server binary, restores rows. On vec a shape ascription is checked by a side operator over the trace's batches; on corgi an arranged import's ascription is not checked (its chunks carry the producer's shapes). examples/import_bench: one producer of 1M edges, then 8 consumers each joining the import twice, 1000 edges replaced per tick. vec corgi rows arranged rows arranged install-to-answer, ms ~420 ~3 ~100 ~1 memory per consumer, MiB ~120 ~1 ~32 ~0.4 tick, 8 consumers, ms 13-19 1.6 3.9-4.7 0.8 Consumers that join the import inside a loop (reachability) are alike. After 500 ticks of churn the published trace holds the same updates with 0 or 8 importers sharing it. Tests: answers with and without arranged imports agree on each backend, and the backends agree, for a join and a reduce of an import at the root, a join inside a loop, and the import read as rows, through changes and an importer installed late. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
frankmcsherry
added a commit
to frankmcsherry/differential-dataflow
that referenced
this pull request
Oct 12, 2026
A program that imported a published trace received it as rows (or, in columnar mode, as containers), and each of its joins, reduces, and arranges of the import built a private copy of an arrangement the producer already maintains. Imports now arrive as that arrangement, entered into the program's scope (`TraceEnter`), on both backends. vec: `Arr` is a local or an imported arrangement. Join and reduce take either through the cursor tactics, as an entered batch's cursor lifts its times. corgi: times are integer lanes, and a host epoch `e` and the program time `(e, [])` have the same lanes, so an imported chunk is read at the program's time by sharing it: `CorgiChunk::retime` casts the `Rc`, which `repr(C)` on the chunk's contents and `ColTimes` makes sound. `RetimeJoin` and `RetimeReduce` wrap the proxy tactics and retime imported batches as they cross in, one `Rc` per chunk. Columnar exports, generated sources included, reach columnar programs this way. An imported arrangement also enters child scopes, iterative ones included, as a program's own arrangements do (TimelyDataflow#931). An import that a scope also reads as rows is flattened once per scope, and the rows enter its children with the arrangement. `Server::set_arranged_imports(false)`, or DDIR_ARRANGED_IMPORTS=0 for the server binary, restores rows (containers in columnar mode). A shape ascription on an arranged import is checked by a side operator over the trace's batches, which produces nothing: on vec always, on corgi when the export's shape was not known at install (as TimelyDataflow#927 checks containers). examples/import_bench: one producer of 1M edges, then 8 consumers each joining the import twice, 1000 edges replaced per tick. Corgi without arranged imports reads containers (TimelyDataflow#927). vec corgi rows arranged containers arranged install-to-answer, ms ~400 ~3 27-42 ~1 memory per consumer, MiB ~95 ~0.5 ~54 ~0.3 tick, 8 consumers, ms 13.3 2.0 3.1 0.9 Consumers that join the import inside a loop (reachability) are alike. After 500 ticks of churn the published trace holds the same updates with 0 or 8 importers sharing it. Tests: answers with and without arranged imports agree on each backend, and the backends agree, for a join and a reduce of an import at the root, a join inside a loop, and the import read as rows, through changes and an importer installed late. A declared import of an export of unknown shape fails on mismatched data when read in place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frankmcsherry
added a commit
that referenced
this pull request
Oct 12, 2026
A program that imported a published trace received it as rows (or, in columnar mode, as containers), and each of its joins, reduces, and arranges of the import built a private copy of an arrangement the producer already maintains. Imports now arrive as that arrangement, entered into the program's scope (`TraceEnter`), on both backends. vec: `Arr` is a local or an imported arrangement. Join and reduce take either through the cursor tactics, as an entered batch's cursor lifts its times. corgi: times are integer lanes, and a host epoch `e` and the program time `(e, [])` have the same lanes, so an imported chunk is read at the program's time by sharing it: `CorgiChunk::retime` casts the `Rc`, which `repr(C)` on the chunk's contents and `ColTimes` makes sound. `RetimeJoin` and `RetimeReduce` wrap the proxy tactics and retime imported batches as they cross in, one `Rc` per chunk. Columnar exports, generated sources included, reach columnar programs this way. An imported arrangement also enters child scopes, iterative ones included, as a program's own arrangements do (#931). An import that a scope also reads as rows is flattened once per scope, and the rows enter its children with the arrangement. `Server::set_arranged_imports(false)`, or DDIR_ARRANGED_IMPORTS=0 for the server binary, restores rows (containers in columnar mode). A shape ascription on an arranged import is checked by a side operator over the trace's batches, which produces nothing: on vec always, on corgi when the export's shape was not known at install (as #927 checks containers). examples/import_bench: one producer of 1M edges, then 8 consumers each joining the import twice, 1000 edges replaced per tick. Corgi without arranged imports reads containers (#927). vec corgi rows arranged containers arranged install-to-answer, ms ~400 ~3 27-42 ~1 memory per consumer, MiB ~95 ~0.5 ~54 ~0.3 tick, 8 consumers, ms 13.3 2.0 3.1 0.9 Consumers that join the import inside a loop (reachability) are alike. After 500 ticks of churn the published trace holds the same updates with 0 or 8 importers sharing it. Tests: answers with and without arranged imports agree on each backend, and the backends agree, for a join and a reduce of an import at the root, a join inside a loop, and the import read as rows, through changes and an importer installed late. A declared import of an export of unknown shape fails on mismatched data when read in place. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A child scope used to receive every import as rows.
An arrangement in the parent was flattened at the scope boundary, and each join, reduce, or arrange of it inside the child built a new one.
Imports now arrive as the parent rendered them, and an arrangement enters the child's region with
Arranged::enter_region, sharing the trace.Why no
TraceEnterDD's
TraceEntermaps a reader's compaction request back to the outer time because the trace cannot hold inner times.DDIR uses one time type,
Product<u64, PointStamp<u64>>, in every scope, so an inner request is already a valid frontier for the outer trace.It is also sound: each handle's frontier lower bounds the times its reader will ask about, and the trace compacts to the meet of its handles.
While epoch
eiterates at roundiand epoche+1may still arrive, a loop reader's frontier is{(e,[i]), (e+1,[])}, and advancing an outer time(e',[])withe' < eby it gives(e,[]).Changes
Backend::enter_region, one line in each backend.render_treerenders a child's imports as the parent's items (collection or arrangement) rather than rows; its public signature is unchanged.Sharing happens when the parent item is an arrangement: an explicit
arrange, or a reduce.lowerplaces the arrange for a join in the joining scope, so a child joining a parent's collection still arranges it unless the parent does.Among the checked-in programs, AoC 2023 day 4 part 2 (
matches) and day 22 part 2 (supcnt) each now share a reduce's output with a child instead of rebuilding it.This is the in-program half of #918, which can rebase onto it and drop its
shares_into_regionsgate.Tests
tests/shared_arrangements.rs: reach and SCC, as written and with the parent arranging the collections its children join, through 8 epochs of edge insertions and deletions; the shared variants agree with the written one at every epoch on vec and corgi, at 1 and 3 workers.cargo test -p interactiveandcargo test -p ddir-serverpass; AoC 2023run.shpasses on both backends.🤖 Generated with Claude Code