Describe the bug
After the compactor processes a delete request (filter-and-delete), /loki/api/v1/index/volume and /loki/api/v1/index/stats over-report the affected streams for hours, while log/metric queries (bytes_over_time etc.) remain exact throughout. A second delete against the same stream selector roughly doubles the namespace's index references, inflating even query windows that are disjoint from every deleted range. The inflation survives pod restarts and does not converge within hours.
This matters to anyone composing the Volume API with the delete API for per-tenant/per-namespace usage accounting: after the first delete, volume readings for that stream set can no longer be trusted.
To Reproduce
Loki 3.7.4, single binary, filesystem object store, TSDB v13 schema, auth_enabled: false. Compactor settings tightened only to shorten the wait: compaction_interval: 2m, delete_request_cancel_period: 1m, retention_delete_delay: 5m.
- Push ~30 MB of synthetic logs (3 streams, one
namespace label value, timestamps spread over the past 6 h), then POST /flush.
- Baseline agrees: instant volume query reports 29.95 MB vs 30.18 MB ground truth from
sum(bytes_over_time(...[7h])) (−0.8%).
POST /loki/api/v1/delete for the oldest 3 h of that namespace; wait until the request lists as processed.
bytes_over_time immediately reports the expected 14.84 MB (exact). The volume query for the same selector/window reports 68.1 MB, decays stepwise (39.9 → 6.6 MB over ~10 min), then sticks at ~1.9× truth. Restarting the pod does not clear it (it read 10.6 MB after a restart). index/stats at that point returns 17 streams / 16,201 entries where the actual remaining data is 3 streams / 6,946 lines.
- Issue a second delete for part of the remaining range. After it processes, index references for the whole namespace roughly double (2,359 chunks / 11.16 M entries vs a clean-baseline 1,176 / 5.99 M), and a volume query restricted to a window disjoint from both deleted ranges reports ~2.03× its
bytes_over_time truth, flat for 14+ minutes.
A larger second dataset (3.018 GB, 6 streams) reproduced the same shape: pre-delete volume/truth ratio 1.017; after one delete a window past the deleted range stayed byte-stable; after a second delete the whole namespace's refs doubled.
Expected behavior
Volume/stats either apply delete-request filtering the way log queries do, or the docs state that these endpoints are not deletion-aware.
Root cause (from reading v3.7.4 source; code paths unchanged on main as of 2026-08-05)
SelectLogs/SelectSamples inject pending (and recently completed) delete requests as query-time filters (pkg/querier/querier.go), which is why log queries stay exact through the index-cleanup transient. IndexStats and Volume in the same file take no delete parameters at all — the transient is exposed raw.
- The querier's volume also sums the ingester's in-memory contribution (
pkg/ingester/instance.go, unflushed chunks), which is delete-blind and WAL-durable — hence restart-proof inflation that decays as chunks flush.
- In single-binary, the index view merges the compactor-rewritten object-store index with ingester-uploaded head TSDBs (
indexshipper iterates the uploads manager too), which the compactor's delete processing never rewrites and which are retained for IndexCacheValidity + 3×ResyncInterval; the volume aggregation (ChunkStats summing ChunkMeta.KB, with pre-aggregated page markers) has no chunk-identity dedup across the merged sources, so rewritten chunks and their stale predecessors are counted together.
Environment
- Infrastructure: Kubernetes (k3d), grafana-community helm chart 18.5.2
- Deployment tool: helm
- Loki 3.7.4 (also inspected current
main), single binary, filesystem object store, TSDB v13
I'm not asking for a commitment to fix — filing so the behavior is documented and searchable, since we could find no existing issue covering it. Happy to provide the full reproduction scripts.
Describe the bug
After the compactor processes a delete request (filter-and-delete),
/loki/api/v1/index/volumeand/loki/api/v1/index/statsover-report the affected streams for hours, while log/metric queries (bytes_over_timeetc.) remain exact throughout. A second delete against the same stream selector roughly doubles the namespace's index references, inflating even query windows that are disjoint from every deleted range. The inflation survives pod restarts and does not converge within hours.This matters to anyone composing the Volume API with the delete API for per-tenant/per-namespace usage accounting: after the first delete, volume readings for that stream set can no longer be trusted.
To Reproduce
Loki 3.7.4, single binary, filesystem object store, TSDB v13 schema,
auth_enabled: false. Compactor settings tightened only to shorten the wait:compaction_interval: 2m,delete_request_cancel_period: 1m,retention_delete_delay: 5m.namespacelabel value, timestamps spread over the past 6 h), thenPOST /flush.sum(bytes_over_time(...[7h]))(−0.8%).POST /loki/api/v1/deletefor the oldest 3 h of that namespace; wait until the request lists asprocessed.bytes_over_timeimmediately reports the expected 14.84 MB (exact). The volume query for the same selector/window reports 68.1 MB, decays stepwise (39.9 → 6.6 MB over ~10 min), then sticks at ~1.9× truth. Restarting the pod does not clear it (it read 10.6 MB after a restart).index/statsat that point returns 17 streams / 16,201 entries where the actual remaining data is 3 streams / 6,946 lines.bytes_over_timetruth, flat for 14+ minutes.A larger second dataset (3.018 GB, 6 streams) reproduced the same shape: pre-delete volume/truth ratio 1.017; after one delete a window past the deleted range stayed byte-stable; after a second delete the whole namespace's refs doubled.
Expected behavior
Volume/stats either apply delete-request filtering the way log queries do, or the docs state that these endpoints are not deletion-aware.
Root cause (from reading v3.7.4 source; code paths unchanged on
mainas of 2026-08-05)SelectLogs/SelectSamplesinject pending (and recently completed) delete requests as query-time filters (pkg/querier/querier.go), which is why log queries stay exact through the index-cleanup transient.IndexStatsandVolumein the same file take no delete parameters at all — the transient is exposed raw.pkg/ingester/instance.go, unflushed chunks), which is delete-blind and WAL-durable — hence restart-proof inflation that decays as chunks flush.indexshipperiterates the uploads manager too), which the compactor's delete processing never rewrites and which are retained forIndexCacheValidity + 3×ResyncInterval; the volume aggregation (ChunkStatssummingChunkMeta.KB, with pre-aggregated page markers) has no chunk-identity dedup across the merged sources, so rewritten chunks and their stale predecessors are counted together.Environment
main), single binary, filesystem object store, TSDB v13I'm not asking for a commitment to fix — filing so the behavior is documented and searchable, since we could find no existing issue covering it. Happy to provide the full reproduction scripts.