erigontech / erigontech/erigon
execution: SD-owned run-task worker abstraction + drop the parallel-exec scheduler queue
- Dominant language
- Go
- Stars
- 3.6k
- Forks
- 1.5k
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 463
Description
## Motivation
While making the block read-ahead prefetcher SD-aware (part of #21414), the prefetch workers were switched to read through the published `SharedDomains` via `sd.BeginCoordinatedRo(ctx, db)` + manual `defer tx.Rollback()`. That works, but exposing `BeginCoordinatedRo` and making every caller own the tx lifecycle is more surface than it needs to be, and it points at a broader rationalization of how we run concurrent read/exec work.
## Part 1 — SD-owned "run task" API
Replace the "caller does `BeginCoordinatedRo` then manages begin/rollback" pattern with an SD-owned runner that owns the tx lifecycle:
```go
sd.WithCoordinatedRo(ctx, func(tx kv.TemporalTx) error { ... }) // or a Task/Worker abstraction
```
The context is passed to the runner; the SD manages begin + rollback (and coordination with the commit worker). Callers just express the work. This streamlines the read-ahead, RPC consumers, and — see below — the parallel executor.
## Part 2 — Drop the parallel-exec application scheduler queue
The same run-task/worker model rationalizes parallel-exec scheduling. Today the parallel executor feeds a fixed pool of workers from an application-level "in" queue (tasks popped off the pending queue are pushed onto a start queue that the fixed workers drain). With a run-task worker we can likely drop that start queue entirely: workers just *run tasks*, and task scheduling is left to the Go runtime rather than a bespoke application queue.
Rationale: there is no ordering requirement here — all execution is now effectively **speculative**, and a task is not queued at the point its goroutine starts. So a start queue that is populated as items pop off the pending queue is pointless from a concurrency perspective; the Go scheduler can do this directly.
## Part 3 — Make the worker stateless
The parallel worker should be **stateless apart from its TX** — all state should live in the task + result. If that is not already true, we should complete the separation (move any residual per-worker state into the task/result) so a worker is purely `run(task) -> result` over a coordinated tx.
## Scope / why a follow-up
This changes the parallel-exec workers, so it is deliberately out of scope for #21414 (which keeps the read-ahead change minimal, using `BeginCoordinatedRo` for now). Landing the read-ahead first, then this abstraction, lets the read-ahead + RPC consumers + parallel workers all converge on one `run-task-over-coordinated-tx` shape.
### Sketch of the end state
- `SharedDomains` owns a coordinated run-task API (owns begin/rollback + commit coordination).
- Consumers (read-ahead prefetch, RPC readers, parallel-exec workers) submit tasks; no caller manages a tx directly.
- The parallel executor drops its start queue; stateless workers run tasks scheduled by the Go runtime.
- All exec state flows through task + result; the worker holds only its tx.
Related: #21414 (read-ahead SD-routing that motivated this), #21314 (SD-aware consumers), #22494 (bg-commit worker/coordination follow-ups).
Contributor guide
Research direction
Start with SharedDomains and the BeginCoordinatedRo usage described for read-ahead, then trace the RPC consumers and parallel executor. Determine where transaction lifecycle, the application start queue, and residual worker state are handled. Done means consumers use the SD-owned run-task shape, the parallel-exec start queue is removed, and worker state flows through task and result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend, databases, distributed-systems
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100