Skip to content

CU25 — What only fed an answer that was kept is not run

Opened by a question about the notebook above: if widest is cached, should sms and clean not be skipped? They should, and they were not.

The cache skipped the node whose own output was there and ran everything else anyway. On a graph fed by a dataset that is the expensive half — the file is read, the rows are tokenized, and none of it is looked at.

key_for had already said so: the name this node’s output will have, before it has one. Only the graph’s input is hashed by content; from there down a key is made of keys. So the engine can name the whole plan with nothing executed, ask which of those answers it already has, and then work backwards from the leaves: a node whose answer is kept does not need its inputs.

Keeper::present is the question and it is new in the hole. The default is honest and expensive — it reads them — and a store overrides it with resolve_many: one scan, no fetches. Asking early has to be free or it is not worth asking, which is the same price list CU20 wrote down.

Two places it gives up, both towards keeping a node rather than skipping one: a .mapped() node is named out of the content of its items, so its names are not knowable until it has them; and a node with no key at all is a miss for the same reason it was never cached.

A slice nobody needs is not sent. The saving there is the round trip and not the work at the far end.

Fact::Spared is a fact rather than an absence, because a node missing from a record cannot be told from one that was never in the graph, and why is there no time for clean is a question whoever reads a run will have. In the notebook it reads:

{'fact': 'spared', 'node': 'sms'}
{'fact': 'spared', 'node': 'clean'}
{'fact': 'recalled', 'node': 'widest', 'key': 'sha256:c521a0…'}
{'fact': 'finished', 'took_us': '155'}

RunError::Vanished is the rare other end: something that was there when the store was asked and gone when it was read, after what feeds it had already been skipped because of that answer. It says what happened instead of leaving a puzzle.

Re-running examples/10-a-dataset.ipynb after writing this, the 19 MB toll had gone from 121 ms to 245 ms. The pre-pass named the root and the walk named it again, so the batch was hashed twice: asking early cost exactly what it saves. The walk reuses the name that was already worked out, and it is back at 121 ms.

No test would have caught it — they all pass either way. It is the third real thing a notebook has found in this project, and the reason they are executed rather than written.

The pruning (soma-core/tests/unit/execution.rs)

  • what only fed an answer that was kept is not run
  • and the answer is still the answer
  • and it says so rather than leaving a hole in the record
  • but what somebody else still reads is run
  • a node that maps keeps everything above it
  • and a slice nobody needs is not sent at all

End to end (soma-data/tests/unit/parquet.rs)

  • the second run finds the answer under a name it could work out, and never opens the dataset