GitHub user Xiao-zhen-Liu edited a comment on the discussion: Design and merge 
plan: operator output port result cache (MVP)

**Design update: planning cache reuse before scheduling**

The first three PRs of the plan above are merged: the cache key (#5966), the 
`operator_port_cache` table (#5967) and the reuse flag on operator metrics 
(#6729). The scheduler part (#5884) now goes up as four PRs:

1. #8731
2. #8732
3. #8733, which sits on the other two
4. #8734, which needs none of the others and only has to land before the lookup 
PR

Nothing sets a run's matched results until the lookup PR, so all four leave 
every run on main unchanged. Below is the design they introduce, and what 
changed from the post above.

> [!IMPORTANT]
> **Assumption: full reuse.** The design always reuses a usable saved result. 
> Every retained operator that reads a port with a usable saved result reads 
> that saved result, and an operator is skipped whenever usable saved results 
> cover every port the run needs from it. There is no cost-based choice between 
> reading a saved result and computing it again, even where computing would be 
> cheaper. That choice (cost-based reuse planning) stays future work, as in the 
> post above.

### Deciding what a run skips

A run's matched results are the saved results found for its output ports 
(`WorkflowContext.matchedResults`). Only the server sets them; they are not 
part of the run request. `SkeletonGenerator` turns them into the run's 
**skeleton**, which marks every operator and link of the workflow for this run:

- An output port is a required output if the run must store it (the user views 
its operator's results, or the operator has no outgoing link), or if it has no 
link.
- A port is needed if it is a required output or feeds an operator that runs.
- An operator is **retained**, meaning it runs in this execution, if it has no 
output ports or if one of its needed ports has no usable saved result. A 
retained operator computes all of its outputs.
- Every other operator is skipped, and every link into a skipped operator is 
skipped.
- Every link from a port with a usable saved result into a retained operator 
reads the saved result.

The example below applies the rules to a join. A, B and G are viewed; B and D 
have saved results. B and D are skipped, and C-Build and E read their saved 
results. A runs because its viewed result has no saved copy, and the link from 
A into the skipped B is skipped.

<img width="1714" height="1225" alt="Example: logical plan, physical plan with 
saved results, and the skeleton" 
src="https://github.com/user-attachments/assets/d24c77f8-cacd-4e49-bbc8-6e49fff0dcc9";
 />

Two changes from the post above:

- The skeleton (`RunSkeleton`) now marks the whole workflow, the skipped part 
included. The post above described it as only the part that still needs to run; 
that part is now the skeleton's **retained part**, which the scheduler builds 
from the marks.
- A saved port is read from storage by every retained operator it feeds, even 
when its own operator runs for another port. This follows from full reuse. When 
the run must store that port (the user views it), the run still stores and 
shows its own result; only the operators the port feeds read the saved copy.

<img width="1454" height="566" alt="Split runs for B, while A reads Split's 
saved port 0" 
src="https://github.com/user-attachments/assets/bbbbfdf4-1f94-49d6-a2ed-8994632cc6d6";
 />

A saved result is unusable, and the run just does not reuse it, when its port 
is not in the plan or its location is not that port's in this workflow and this 
run's warehouse. A plan with a loop reuses nothing. An unusable result never 
fails a run.

### Scheduling the retained part

<img width="1923" height="1400" alt="How the scheduler uses the skeleton" 
src="https://github.com/user-attachments/assets/4f3ec0a0-81b3-441e-935d-07e51adbba05";
 />

- The scheduler hands `CostBasedScheduleGenerator` the retained part: the 
plan's own operators that run and the links between them, without the links 
that read a saved result. The search, the regions and the storage of 
materialized links work as today.
- The generator also gets, for each input port on such a link, the saved 
location it reads (`CacheReadInputs`). Its second pass adds these next to the 
locations from materialized links. The resource allocator partitions an 
operator that reads a saved result as if it read a materialized link, so the 
retained part is partitioned as it would be in the whole plan.
- Operators keep their own link lists, which still name the links that read a 
saved result. Code that works on one region (`Region.getSourceOperators` in 
#8732, the reconfiguration scope in `FriesReconfigurationAlgorithm` in #8734) 
now counts only the region's own links. Without saved results their answers do 
not change.
- Skipped operators become skip regions (`Region.skipped`), one per connected 
group, all in a new first level ahead of the generated levels, with ids above 
the generated ones. Each skip region records the saved results its ports 
provide. A run that skips every operator has only that level. Running a skip 
region, which starts no workers, comes in a later PR.
- The scheduler keeps the full plan, so code that looks up an operator still 
finds a skipped one.

A schedule for the example (the generated regions are the ones the search would 
typically pick):

<img width="2387" height="1000" alt="Schedule for the example: skip regions in 
level 0, generated regions after" 
src="https://github.com/user-attachments/assets/4e0ac199-352b-4a13-82b2-75ebd360f7fe";
 />

With no matched results, the scheduler runs today's code: the whole plan goes 
to the generator.


GitHub link: 
https://github.com/apache/texera/discussions/5880#discussioncomment-18646157

----
This is an automatically sent email for [email protected].
To unsubscribe, please send an email to: [email protected]

Reply via email to