pkg/ddl, planner: add TiKV space precheck for DXF add-index#68955
pkg/ddl, planner: add TiKV space precheck for DXF add-index#68955expxiaoli wants to merge 53 commits into
Conversation
|
Hi @expxiaoli. Thanks for your PR. PRs from untrusted users cannot be marked as trusted with I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds TiKV capacity prediction and optional disk-space precheck for distributed add-index; refactors index KV generation and handle-restoration APIs; records ingested SST identities/sizes; extracts Lightning SST encoders; expands mocks and tests; updates Bazel deps and wiring. ChangesAdd-Index TiKV Capacity Prediction with Refactoring
Sequence DiagramsequenceDiagram
participant Worker as AddIndex Worker
participant PD as PD (gRPC/HTTP)
participant TiKV as TiKV Stores
participant Storage as Lightning / KV Storage
participant Collector as SubtaskSummary Collector
Worker->>PD: collect store stats and placement info
Worker->>TiKV: block-sample region snapshot reads
Worker->>Worker: predict index bytes, scale by replica count
Worker->>PD: optionally enforce disk-space precheck decision
Worker->>Storage: submit dist add-index task (SST ingest)
Storage->>Worker: return ingest metadata (SST id/size)
Worker->>Collector: record ingested SST identities/sizes into SubtaskSummary
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/ddl/index.go (1)
3195-3203:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDon't skip observed-capacity logging when resuming an already-succeeded task.
This early return bypasses
scheduleDistTaskObservedTiKVUsage(...), so a paused/resumed add-index job never emits the final predicted-vs-observed TiKV usage log even though the task history and metadata are still available.Suggested fix
if task.State == proto.TaskStateSucceed { w.updateDistTaskRowCount(taskKey, reorgInfo.Job.ID) + w.scheduleDistTaskObservedTiKVUsage(taskManager, taskKey, reorgInfo.Job.ID) logutil.DDLLogger().Info( "task succeed, start to resume the ddl job", zap.String("task-key", taskKey)) return nil }Also applies to: 3421-3424
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/ddl/index.go` around lines 3195 - 3203, The early return when task.State == proto.TaskStateSucceed skips scheduleDistTaskObservedTiKVUsage, so ensure the observed-capacity logging still runs before resuming an already-succeeded task: call scheduleDistTaskObservedTiKVUsage with the same identifiers used elsewhere (use taskKey and reorgInfo.Job.ID) and any required context/params, then call updateDistTaskRowCount and the resume log, and only then return; apply the same change to the analogous block around lines 3421-3424 so both paths emit the final predicted-vs-observed TiKV usage log.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@pkg/ddl/index.go`:
- Around line 3250-3331: Wrap the PD-backed precheck calls in a short timeout
derived from ctx (e.g. ctxWithTimeout, cancel := context.WithTimeout(ctx,
5*time.Second); defer cancel()) and pass ctxWithTimeout to the functions that
perform PD/TiKV RPCs so submission cannot hang: use the timed context when
calling w.predictTiKVIndexBytesBlockSample, w.resolveAddIndexTiKVReplicaCount,
canRunTiKVSpacePrecheck, and collectTiKVStoreCapacity (and any other
PD/region/capacity helpers invoked in this block); ensure you call cancel()
after creating the context.
- Around line 4144-4160: The check underestimates space when indexes are
placement-constrained because it divides predictedTiKVIndexBytes by
capacity.StoreCount and sums capacity.Stores indiscriminately; change the logic
in the cluster and per-store checks (where clusterRemaining,
perStorePredictBytes, remainingTiKVBytes, capacity.StoreCount and
capacity.Stores are used) to first compute the actual set of eligible stores
based on the index's placement/replica bundles (respecting pinned/targeted
stores), sum AvailableBytes only over those eligible stores for
clusterRemaining/clusterThreshold, and divide predictedTiKVIndexBytes across
only those eligible stores when computing perStorePredictBytes and
storeRemaining/storeThreshold; apply the same fix in the related block around
lines 4180-4250 that performs the same admission checks.
- Around line 3346-3358: The code stores peak MVCC bytes in the steady field:
blockSamplePrediction.PredictedBytes (set from mvccPhysicalBytes) is being
persisted into BlockSampleSteadyPredictedTiKVIndexBytes; change the assignment
so the MVCC peak value (mvccPhysicalBytes or the variable used when computing
peak) is saved to the appropriate "peak"/peak-named field and ensure
blockSamplePrediction.PredictedBytes is used for the true steady estimate;
update the three occurrences that mirror this pattern (around the symbols
BlockSampleSteadyPredictedTiKVIndexBytes, blockSamplePrediction.PredictedBytes,
and mvccPhysicalBytes) so steady vs peak are written to the correct fields and
logs.
In `@pkg/ddl/reorg_util_test.go`:
- Around line 355-360: Replace usage of context.Background() passed to
grpc.DialContext and RPC calls with a short timeout/deadline context to prevent
CI hangs: create a context with a timeout (e.g., context.WithTimeout) before
calling grpc.DialContext (the block that constructs conn via grpc.DialContext
and the RPC call sites around the listener.Dial() usage), use that context for
the dial and any subsequent RPCs, defer cancel() and handle the context deadline
error in the test (still calling require.NoError on the dial/RPC error) so tests
fail fast if the mock server is not responsive.
---
Outside diff comments:
In `@pkg/ddl/index.go`:
- Around line 3195-3203: The early return when task.State ==
proto.TaskStateSucceed skips scheduleDistTaskObservedTiKVUsage, so ensure the
observed-capacity logging still runs before resuming an already-succeeded task:
call scheduleDistTaskObservedTiKVUsage with the same identifiers used elsewhere
(use taskKey and reorgInfo.Job.ID) and any required context/params, then call
updateDistTaskRowCount and the resume log, and only then return; apply the same
change to the analogous block around lines 3421-3424 so both paths emit the
final predicted-vs-observed TiKV usage log.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 159a21d1-a5b2-42af-a059-5845d424fb3f
📒 Files selected for processing (21)
pkg/ddl/BUILD.bazelpkg/ddl/backfilling_clean_s3.gopkg/ddl/backfilling_dist_executor.gopkg/ddl/backfilling_operators.gopkg/ddl/copr/copr_ctx.gopkg/ddl/copr/copr_ctx_test.gopkg/ddl/index.gopkg/ddl/index_cop.gopkg/ddl/reorg_util.gopkg/ddl/reorg_util_test.gopkg/executor/admin.gopkg/lightning/tikv/local_sst_writer.gopkg/sessionctx/vardef/tidb_vars.gopkg/sessionctx/variable/sysvar.gopkg/sessionctx/variable/sysvar_test.gopkg/table/tables/BUILD.bazelpkg/table/tables/index.gopkg/table/tables/index_test.gopkg/table/tables/mutation_checker_test.gopkg/table/tables/tables.gotests/realtikvtest/addindextest2/global_sort_test.go
💤 Files with no reviewable changes (1)
- pkg/ddl/index_cop.go
| clusterRemaining := remainingTiKVBytes(capacity.AvailableBytes, predictedTiKVIndexBytes) | ||
| clusterThreshold := uint64(float64(capacity.TotalBytes) * 0.20) | ||
| if clusterRemaining < clusterThreshold { | ||
| return dbterror.ErrIngestCheckEnvFailed.FastGenByArgs( | ||
| fmt.Sprintf("insufficient TiKV cluster capacity predicted for add index: cluster_available_bytes=%d predict_tikv_index_bytes=%d cluster_total_bytes=%d cluster_remaining_bytes=%d cluster_threshold_bytes=%d", | ||
| capacity.AvailableBytes, predictedTiKVIndexBytes, capacity.TotalBytes, clusterRemaining, clusterThreshold)) | ||
| } | ||
|
|
||
| perStorePredictBytes := predictedTiKVIndexBytes / uint64(capacity.StoreCount) | ||
| for _, store := range capacity.Stores { | ||
| storeRemaining := remainingTiKVBytes(store.AvailableBytes, perStorePredictBytes) | ||
| storeThreshold := uint64(float64(store.TotalBytes) * 0.15) | ||
| if storeRemaining < storeThreshold { | ||
| return dbterror.ErrIngestCheckEnvFailed.FastGenByArgs( | ||
| fmt.Sprintf("insufficient TiKV store capacity predicted for add index: store_id=%d store_available_bytes=%d predict_tikv_index_bytes=%d tikv_store_count=%d per_store_predict_bytes=%d store_total_bytes=%d store_remaining_bytes=%d store_threshold_bytes=%d", | ||
| store.StoreID, store.AvailableBytes, predictedTiKVIndexBytes, capacity.StoreCount, perStorePredictBytes, store.TotalBytes, storeRemaining, storeThreshold)) | ||
| } |
There was a problem hiding this comment.
This precheck underestimates space for placement-constrained indexes.
The prediction is scaled by replica count, but the admission check still spreads those bytes across capacity.StoreCount and sums availability across every TiKV store. If a placement bundle pins replicas to only a subset of stores, untargeted stores are counted as usable capacity and each target store is undercharged, so the job can pass even when the eligible stores will breach the 15% floor.
Also applies to: 4180-4250
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@pkg/ddl/index.go` around lines 4144 - 4160, The check underestimates space
when indexes are placement-constrained because it divides
predictedTiKVIndexBytes by capacity.StoreCount and sums capacity.Stores
indiscriminately; change the logic in the cluster and per-store checks (where
clusterRemaining, perStorePredictBytes, remainingTiKVBytes, capacity.StoreCount
and capacity.Stores are used) to first compute the actual set of eligible stores
based on the index's placement/replica bundles (respecting pinned/targeted
stores), sum AvailableBytes only over those eligible stores for
clusterRemaining/clusterThreshold, and divide predictedTiKVIndexBytes across
only those eligible stores when computing perStorePredictBytes and
storeRemaining/storeThreshold; apply the same fix in the related block around
lines 4180-4250 that performs the same admission checks.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pkg/ddl/index.go (2)
3665-3674:⚠️ Potential issue | 🟠 Major | ⚡ Quick winCreate a fresh timeout context for the retry.
Line 3665's timeout context is reused for both
GetStoreattempts. If the first RPC consumed that deadline or canceled the context, the retry fails immediately and the TiKV capacity snapshot is skipped under exactly the transient PD conditions this branch is meant to tolerate.Suggested fix
- reqCtx, cancel := context.WithTimeout(ctx, pdStoreStatsRequestTimeout) - defer cancel() - reqCtx = serviceClient.BuildGRPCTargetContext(reqCtx, true) - resp, err := pdpb.NewPDClient(serviceClient.GetClientConn()).GetStore(reqCtx, req) + call := func(sc sd.ServiceClient) (*pdpb.GetStoreResponse, error) { + reqCtx, cancel := context.WithTimeout(ctx, pdStoreStatsRequestTimeout) + defer cancel() + reqCtx = sc.BuildGRPCTargetContext(reqCtx, true) + return pdpb.NewPDClient(sc.GetClientConn()).GetStore(reqCtx, req) + } + resp, err := call(serviceClient) if needRetryPDStoreStats(serviceClient, resp, err) { serviceClient = serviceDiscovery.GetServiceClient() if serviceClient != nil && serviceClient.GetClientConn() != nil { - reqCtx = serviceClient.BuildGRPCTargetContext(reqCtx, true) - resp, err = pdpb.NewPDClient(serviceClient.GetClientConn()).GetStore(reqCtx, req) + resp, err = call(serviceClient) } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/ddl/index.go` around lines 3665 - 3674, The retry is reusing the original reqCtx which may have exhausted its deadline; when needRetryPDStoreStats(...) is true, create a fresh timeout context (e.g., newReqCtx, newCancel := context.WithTimeout(ctx, pdStoreStatsRequestTimeout)) and call serviceClient.BuildGRPCTargetContext(newReqCtx, true) before invoking pdpb.NewPDClient(...).GetStore for the retry, and ensure newCancel() is called to release resources instead of reusing the original reqCtx/cancel.
4977-4979:⚠️ Potential issue | 🟠 MajorBound region sampling cost for precheck
Passing
0asmaxRegionstolistTableRegionsWithClientmakesiterateTableRegionsWithClient(page sizetableRegionsPageSize = 128) walk all regions in the table key range until it hits the end/empty page, since themaxRegionslimit/break logic only applies whenmaxRegions > 0.listSamplePredictionRegionsthen builds a fullregionsslice for every returned regionInfo, and only afterwardpickSamplePredictionRegionsWithLimittruncates to the small budget (selection.regionCount, max 5). This lets PD region listing scale with total region count rather than the sampled-region budget.Thread
selection.regionCount(or another positive cap) into the region-listing path so fetching is bounded before sampling.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@pkg/ddl/index.go` around lines 4977 - 4979, The current call to listTableRegionsWithClient passes 0 (no cap) causing iterateTableRegionsWithClient (page size tableRegionsPageSize = 128) to scan all regions before sampling; change the call site (where tableStart, tableEnd and listTableRegionsWithClient are used) to pass a positive cap based on the sampling budget (e.g., selection.regionCount or a small multiple thereof) so listing stops early; update any upstream calls like listSamplePredictionRegions/pickSamplePredictionRegionsWithLimit to thread selection.regionCount (or a defined positive limit) into listTableRegionsWithClient so region fetching is bounded before building the full regions slice.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@pkg/ddl/index.go`:
- Around line 3665-3674: The retry is reusing the original reqCtx which may have
exhausted its deadline; when needRetryPDStoreStats(...) is true, create a fresh
timeout context (e.g., newReqCtx, newCancel := context.WithTimeout(ctx,
pdStoreStatsRequestTimeout)) and call
serviceClient.BuildGRPCTargetContext(newReqCtx, true) before invoking
pdpb.NewPDClient(...).GetStore for the retry, and ensure newCancel() is called
to release resources instead of reusing the original reqCtx/cancel.
- Around line 4977-4979: The current call to listTableRegionsWithClient passes 0
(no cap) causing iterateTableRegionsWithClient (page size tableRegionsPageSize =
128) to scan all regions before sampling; change the call site (where
tableStart, tableEnd and listTableRegionsWithClient are used) to pass a positive
cap based on the sampling budget (e.g., selection.regionCount or a small
multiple thereof) so listing stops early; update any upstream calls like
listSamplePredictionRegions/pickSamplePredictionRegionsWithLimit to thread
selection.regionCount (or a defined positive limit) into
listTableRegionsWithClient so region fetching is bounded before building the
full regions slice.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4acd83fa-b442-4158-a96a-38b8a80446f8
📒 Files selected for processing (12)
pkg/ddl/backfilling_dist_executor.gopkg/ddl/backfilling_import_cloud.gopkg/ddl/backfilling_read_index.gopkg/ddl/index.gopkg/ddl/reorg_util_test.gopkg/dxf/framework/taskexecutor/execute/interface.gopkg/ingestor/ingestcli/client.gopkg/ingestor/ingestcli/client_test.gopkg/ingestor/ingestcli/interface.gopkg/lightning/backend/local/job_worker.gopkg/lightning/backend/local/job_worker_test.gopkg/lightning/backend/local/region_job.go
🚧 Files skipped from review as they are similar to previous changes (1)
- pkg/ddl/reorg_util_test.go
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## release-nextgen-202603 #68955 +/- ##
===========================================================
Coverage ? 76.1559%
===========================================================
Files ? 1936
Lines ? 541301
Branches ? 0
===========================================================
Hits ? 412233
Misses ? 129068
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
1 similar comment
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
(cherry picked from commit c2bdb41)
(cherry picked from commit cb5558d)
(cherry picked from commit 78f814b)
(cherry picked from commit 5408035)
(cherry picked from commit 5af3a42)
(cherry picked from commit 419c0d1)
(cherry picked from commit 2d1e9cf)
(cherry picked from commit 64bd5e5)
(cherry picked from commit bb76450)
(cherry picked from commit 84866c1)
(cherry picked from commit 6f33aa0)
(cherry picked from commit 6a09801)
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
| zap.Int("worker-cnt", workerCntLimit), zap.Int("required-slots", requiredSlots), | ||
| zap.String("task-key", taskKey)) | ||
| rowSize := estimateTableRowSize(w.workCtx, w.store, w.sess.GetRestrictedSQLExecutor(), t) | ||
| var ( |
There was a problem hiding this comment.
IMO it'd be better to have a standalone function / method to represent the logic of TiKV size pre-check.
Then, the nested else branch can be replaced by return directly in if branch. Also, we can re-org some of the condition of the code blocks (e.g. many if initialCapacity != nil { can be replaced by if initialCapacity == nil { return }
There was a problem hiding this comment.
I will move it to helper method
| if err != nil { | ||
| return result, err | ||
| } | ||
| result.UseStats = useStats |
There was a problem hiding this comment.
It seems that UseStats is only used for log, so a table will not be enforced if it's using pseudo stats. Is it expected?
There was a problem hiding this comment.
Reasonable. If stats are pseudo, we should skip enforcement and only log, since row-count scaling is unreliable.
| } | ||
| } | ||
|
|
||
| func scalePredictedBytesByReplicaCount(predictedBytes, replicaCount uint64) uint64 { |
There was a problem hiding this comment.
It seems that this function is useless. It's only used in test.
There was a problem hiding this comment.
Great catch. I will move it to test code.
| return writerCfg | ||
| } | ||
|
|
||
| func generateIndexKVsForRow( |
There was a problem hiding this comment.
This callback style function looks strange but for now I don't have better ideas. I understand that it's used to abstract some common logic between the prediction and the real writeChunk. Let me think whether we can have better ways later 🤔
There was a problem hiding this comment.
Agreed it’s not ideal, but I will keep it for now to avoid duplicating KV generation logic between prediction and writeChunk.
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
|
/retest-required |
|
@expxiaoli: PRs from untrusted users cannot be marked as trusted with DetailsIn response to this:
Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
YangKeao
left a comment
There was a problem hiding this comment.
For the common logic of prediction and writeChunk, I also didn't have better idea🤦🏻♂️.
|
@wjhuang2016 PTAL |
|
/hold Feel free to unhold once the master PR merged. |
[LGTM Timeline notifier]Timeline:
|
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: GMHDBJD, wjhuang2016, YangKeao, yudongusa The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
This PR cherry-picks #68490 to
release-nextgen-202603and adds follow-up changes for Next-Gen SST estimation and ingested SST observability.What problem does this PR solve?
Issue Number: ref #68354
Problem Summary:
DXF add-index can generate a large amount of SST data and exhaust TiKV disk space, but TiDB does not estimate the physical write footprint before submitting the distributed task.
TiDB also lacks reliable task-level observability for comparing predicted bytes with the SST bytes successfully ingested, especially on Next-Gen clusters where the CSE storage format differs from classic TiKV SSTs.
What changed and how does it work?
Submission-time prediction and precheck
enforce_disk_space_precheck_before_add_index:OFFby default: log insufficient capacity without rejecting the DDLON: reject the add-index job when the capacity check failsIngested SST observability
block_sample_predicted_tikv_index_single_replica_bytesblock_sample_predicted_tikv_index_all_replica_bytesblock_sample_mvcc_overhead_total_byteslogical_index_kv_bytesingested_sst_bytesShared helper and correctness changes
RECOVER INDEX.Check List
Tests
Validation:
make bazel_prepareTestDXFAddIndexRealtimeSummary, including verification of reported ingested SST bytes on Next-Genmake lintgit diff --checkSide effects
Documentation
Release note