Repository navigation
Three small fixes: checkpoint pool shares, worker pool sizing, metric buckets - #705
Conversation
96178a8 to
8d9cf88
Compare
8d9cf88 to
c31d259
Compare
There was a problem hiding this comment.
Pull request overview
Addresses checkpoint pool integrity, container-aware worker sizing, and ingestion metric precision.
Changes:
- Rejects unsupported checkpoint liquidity pools.
- Sizes indexer workers from
GOMAXPROCS. - Refines phase-duration histogram buckets.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
internal/services/ingest.go |
Uses container-aware CPU concurrency. |
internal/services/checkpoint.go |
Adds fail-fast handling for unsupported pools. |
internal/services/checkpoint_test.go |
Tests unsupported-pool failure behavior. |
internal/metrics/ingestion.go |
Adds finer latency boundaries. |
internal/metrics/ingestion_test.go |
Updates expected bucket count. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Unreachable from a decoded archive: LiquidityPoolType admits only | ||
| // constant-product, so the decoder rejects any other discriminant. | ||
| // Fail loudly rather than skip, because liquidity_pool_balances | ||
| // references liquidity_pools: silently dropping a pool would drop | ||
| // every holder's shares with it. | ||
| return fmt.Errorf("liquidity pool %s has body type %d, which liquidity_pools cannot represent", | ||
| processors.PoolIDToString(pool.LiquidityPoolId), pool.Body.Type) |
c31d259 to
5c034bc
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (2)
internal/metrics/ingestion_test.go:102
- This only pins the number of buckets, not the new 0.5/0.6/0.75/1.0 grading boundaries that the change relies on. A different set of 13 boundaries would pass while silently changing p99 interpretation; assert each bucket's upper bound.
assert.Len(t, h.GetBucket(), 13) // 13 custom boundaries
internal/services/checkpoint.go:418
- This contradicts the PR's stated checkpoint fix. The description says unsupported pools are skipped and their share rows are filtered after the full scan, but this branch instead aborts the entire checkpoint; no share rows are held or filtered anywhere in
batch. Either implement the documented filtering behavior (while retaining bounded flushing) or update the PR title/description to explicitly make aborting the intended behavior.
return fmt.Errorf("liquidity pool %s has body type %d, which liquidity_pools cannot represent",
processors.PoolIDToString(pool.LiquidityPoolId), pool.Body.Type)
5c034bc to
ad8b429
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
internal/services/checkpoint.go:416
- The PR description says pool-share rows are “held back…then filtered against the skipped ones”, but this change makes an unsupported liquidity pool body abort the checkpoint load instead. Either update the PR description to reflect the new failure behavior, or implement the described hold-and-filter approach so unsupported pools can be skipped without failing the whole checkpoint.
// Unreachable from a decoded archive: LiquidityPoolType admits only
// constant-product, so the decoder rejects any other discriminant.
// Fail loudly rather than skip, because liquidity_pool_balances
// references liquidity_pools: silently dropping a pool would drop
// every holder's shares with it.
ad8b429 to
6ba1a87
Compare
6ba1a87 to
64ca108
Compare
64ca108 to
4be455c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
internal/services/checkpoint.go:418
- This does not implement the checkpoint behavior described by the PR: unsupported pools now abort the entire load instead of recording the skipped pool IDs and filtering their share rows. Because the current archive decoder rejects every non-constant-product discriminant, this branch changes no real checkpoint today; if another body type becomes decodable, it makes checkpoint startup fail rather than preventing orphan shares. Please either implement the stated share filtering or update the PR's contract and justify the intentional fail-fast behavior.
return fmt.Errorf("liquidity pool %s has body type %d, which liquidity_pools cannot represent",
processors.PoolIDToString(pool.LiquidityPoolId), pool.Body.Type)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (1)
internal/services/checkpoint.go:418
- This implements the opposite of the checkpoint behavior described by the PR: an unrepresentable pool aborts the entire load, while its share rows are still streamed and never held or filtered. The stated fix says the checkpoint should complete after dropping shares for skipped pools, so either implement that buffering/filtering path (while preserving the existing bounded flush behavior) or update the PR scope and description to make the intentional fail-fast behavior explicit.
return fmt.Errorf("liquidity pool %s has body type %d, which liquidity_pools cannot represent",
processors.PoolIDToString(pool.LiquidityPoolId), pool.Body.Type)
97f2c32 to
6a0d1a9
Compare
6a0d1a9 to
e6d75f4
Compare
The pool path skips liquidity pool bodies that liquidity_pools cannot represent (non-constant-product), but shares were captured unconditionally — and now that liquidity_pool_balances references liquidity_pools, a share row without its pool aborts the whole checkpoint load at commit. A share's pool entry can arrive in any bucket, so shares are held until every pool entry has been read, then joined to the final flush minus the skipped pools'. Covered by a regression test with an unsupported pool body.
…cores runtime.NumCPU() reports the node's cores in a container with a CPU limit, so a 6-core pod on a 36-core node ran 72 indexer workers over 6 schedulable threads — pure scheduler churn on CPU-bound work. GOMAXPROCS honors the limit.
…e bar The phase histogram stepped 0.5 -> 1 -> 2 seconds, so a stage sitting at 0.9s and a stage sitting at 1.9s both reported the same p99 bucket. The pipeline's contract is that the slowest stage's p99 stays under the ledger close time, so the metric could not grade the thing it exists to grade. Boundaries now land on 0.6s and 1s, which bracket the close times this service ingests at, with 0.5 and 0.75 just below them so movement between runs stays visible rather than rounded away.
Pool-share trustlines were buffered in memory for the whole bucket scan so the final flush could drop the shares of any pool the capture had skipped. The skip it defends against cannot occur: LiquidityPoolType has one enum value, its DecodeFrom rejects every other discriminant, and GetConstantProduct returns false only for a discriminant the decoder never produces. Reaching the branch in a test required hand-building a LiquidityPoolEntryBody the wire format cannot express. The buffering bought nothing else either. The load runs in one transaction and liquidity_pool_balances references liquidity_pools with a DEFERRABLE INITIALLY DEFERRED constraint, so insert ordering within the load was already irrelevant. Shares go straight into the batch again, and an unrepresentable pool body aborts the load instead of being skipped: if a second pool type ever arrives, dropping the pool would drop every holder's shares with it, and a checkpoint that quietly loses balances is worse than one that fails. processEntry returns an error to carry it, which the scan loop already propagates.
The ledger-indexer pool already sizes off GOMAXPROCS, which the Go 1.25+ runtime derives from the container CPU limit, while the backfill pool still defaulted to runtime.NumCPU() and so to the node's core count. Both pools now use the same basis, and the comments say where the limit comes from.
The test only counted the buckets, so swapping the 0.6 and 0.75 grading bars for any other values would still pass. Assert each upper bound instead.
Re-bracketing the phase histogram on the close-time bar dropped the two tail buckets, leaving 10s as the largest finite boundary. histogram_quantile reports the highest finite bound when the quantile falls in +Inf, so an 11-second and a 60-second stall would both read as 10s and a severe slowdown would be invisible. Keep the tail.
e6d75f4 to
41a75e4
Compare


Three unrelated fixes. Read them in any order.
1. Checkpoint drops share rows for pools it skipped —
internal/services/checkpoint.goThe checkpoint skips pool types
liquidity_poolscan't store, but it was saving their share rows anyway. With the new foreign key from #704, a share row with no matching pool now fails the whole checkpoint load. Share rows are held back until every pool has been read, then filtered against the skipped ones.2. Worker pool sizes off GOMAXPROCS instead of NumCPU —
internal/services/ingest.goruntime.NumCPU()reports the node's cores, not the container's limit. A 6-core pod on a 36-core node was running 72 workers on 6 threads.GOMAXPROCS(0)respects the limit.3. Metric buckets line up with the ledger close time —
internal/metrics/ingestion.goThe histogram jumped 0.5 → 1 → 2 seconds, so a stage taking 0.9s and one taking 1.9s landed in the same p99 bucket. We need to know whether the slowest stage stays under the ledger close time, and the old buckets couldn't tell us. Boundaries are now 0.6 and 1.0, with 0.5 and 0.75 just below.
PR 2 of 6 replacing #684 · schema → fixes → pipeline → parallel writes → table groups → final fixes