Session::edge appends a record for every call, and compact::keep_edge decides an add record is live by asking whether the adjacency has the edge:
if !session.neighbours(Direction::Out, src, |n| n.binary_search(&dst).is_ok()) {
return Ok(false);
}
session.append_untracked(record::KIND_EDGE, payload)?;
Nothing asks whether the adjacency got the edge from this record or from the two hundred below it, so all of them are live and every pass copies all of them forward. The live set is then a record per edge operation rather than per edge, over a graph that is not growing at all.
That is not an exotic workload, it is the normal one. Every MERGE shaped write re-asserts an edge that is usually already there, and so does any loader that is run twice.
crates/zu2/tests/edgedup.rs measures it. One edge, then the same edge again a hundred thousand times, compacting throughout so the span is what a pass could not get rid of rather than what has not been looked at:
span after one edge 128, after a hundred thousand re-adds 4800152, migrated 0
48 bytes an operation that no pass will ever reclaim. It also has a second face, which is where this was actually noticed: tests/graphracy.rs with four threads writing edges into 1024 nodes ends in LogFull { span: 19, max: 16 } with under 5 MB of live padding, because the rest of the log is duplicate add records.
The fix on the write side is that a write which changes nothing does not go on the log. Under the node's edge order lock, an add of an edge that is there and a remove of one that is not are both no-ops in memory, so they can return without appending. With that, the same test reads 128 bytes after a hundred thousand re-adds.
What has to be true when this is closed:
Session::edgeappends a record for every call, andcompact::keep_edgedecides an add record is live by asking whether the adjacency has the edge:Nothing asks whether the adjacency got the edge from this record or from the two hundred below it, so all of them are live and every pass copies all of them forward. The live set is then a record per edge operation rather than per edge, over a graph that is not growing at all.
That is not an exotic workload, it is the normal one. Every
MERGEshaped write re-asserts an edge that is usually already there, and so does any loader that is run twice.crates/zu2/tests/edgedup.rsmeasures it. One edge, then the same edge again a hundred thousand times, compacting throughout so the span is what a pass could not get rid of rather than what has not been looked at:48 bytes an operation that no pass will ever reclaim. It also has a second face, which is where this was actually noticed:
tests/graphracy.rswith four threads writing edges into 1024 nodes ends inLogFull { span: 19, max: 16 }with under 5 MB of live padding, because the rest of the log is duplicate add records.The fix on the write side is that a write which changes nothing does not go on the log. Under the node's edge order lock, an add of an edge that is there and a remove of one that is not are both no-ops in memory, so they can return without appending. With that, the same test reads 128 bytes after a hundred thousand re-adds.
What has to be true when this is closed:
tests/edgedup.rs, which fails on the old behaviour with 4800152 against 128