Files
openobserve/deny.toml
T
Huaijin 37e5f55684 feat(metrics): .midx series index for TSID-major files and PromQL pruning through it (#13899)
The query half of the TSID-major layout: #13882 wrote `tsid-major-v3-*`
files ordered by `(__hash__, _timestamp)`; this PR adds the `.midx`
series index next to them and makes PromQL prune through it. Supersedes
the remaining scope of #13839.

## Write side

- Every `tsid-major-v3-*` file written by the hour-end merge gets a
`.midx` series index (Arrow IPC + zstd), stored like the Tantivy index
under a derived root distinct from the data tree:
`files/{org}/metrics/{stream}/{date}/{hour}/tsid-major-v3-{id}.parquet`
→ `files/{org}/midx/{stream}/{date}/{hour}/tsid-major-v3-{id}.midx`
(Tantivy: `files/{org}/index/{stream}_{type}/…/{id}.ttv`): **one row per
contiguous TSID run** — its `row_count: u32` plus the label columns of
the run's first row (source types preserved). Runs tile the data file,
so each run's starting row is the prefix sum of the preceding counts and
is not stored; `__hash__` is excluded (matchers never reference it). The
whole index is one ZSTD-compressed IPC batch. Runs crossing an input
RecordBatch boundary appear as two adjacent entries; query-time range
coalescing makes that representation equivalent without buffering
samples. File and row-group boundaries stay independent of TSID
boundaries.
- `TsidSeriesIndexWriter` lives inside
`search/datafusion/merge/tsid_major.rs` and collects run entries
alongside the Parquet writer (one buffered row per run), encoding and
compressing once at finish. `MergedFile` carries `series_index:
Option<Vec<u8>>`; the compactor uploads it to the `midx/` tree
(compliance mode included).
- `.midx` is intentionally **not** tracked in `file_list`; its path is
derived from the data file's key
(`MetricsFileLayout::series_index_path`) and it is deleted together with
the parent in the delayed-deletion pass (`deleted.rs`); like the
`index/` tree, the `midx/` day directories are covered by retention's
local-disk deletion and the gc tool.

## Query side (PromQL)

- `promql_service::…::tsid_series_index::search` runs where Tantivy used
to, before the metrics table is registered: for each TSID-major file it
loads the `.midx` (concurrent, `2×target_partitions ≤ 64`), projects the
row-range columns plus the matched labels **by name against each series
index's own schema** (label sets / column orders differ between files),
builds the matcher filter **per series index** with
`physical_expr::create_physical_expr` — the exact same predicates as
`apply_matchers` (`matcher_predicates`) — evaluates it over the run
rows, coalesces adjacent/overlapping ranges and attaches them as
`FileSelection::RowRanges` (converted into a Parquet access plan by the
generic helper from #13861, no per-row bitmap).
- **Applied per file**: files of any other layout in the same query
(still-open hours, legacy data, late arrivals) are left in place and
scanned in full. A matched label missing from a series index
over-selects that file; a query whose matchers cannot be evaluated at
all selects the whole file; any series index failure logs and falls back
to a full scan. The final PromQL matchers are always applied, so results
are exact in every case.
- Evaluated selections are cached (LRU, 256 MiB) keyed by account +
series index path + the full matcher set.
- The Tantivy path in the PromQL storage scan is removed
(`convert_matchers_to_index_condition`, index-field plumbing,
`is_add_filter_back` bookkeeping). SQL search is untouched.

## Benchmark (from the #13839 POC, same layout + series index)

478,144,896 rows, 7 CPUs, one-hour PromQL range, step 15s, result cache
off, release + mimalloc:

| Layout | Filter steady median | No-filter steady median | Data files |
Total storage |
|---|---:|---:|---:|---:|
| main, 2 GiB | 695.709 ms | 4.638 s | 88 | 4,925.81 MiB |
| TSID-major + `.midx` | 269.496 ms | 2.793 s | 80 | 1,294.61 MiB |

## Validation

- `cargo fmt --check`; `cargo check` / `clippy` on config, search,
compaction, promql-service, jobs, search_service (`--tests`) clean
- `config tsid_layout` 3 passed (incl. `series_index_path`); `search
tsid_major mode:: merge::` 10 passed (rotation test now also asserts the
series index, exact-run and coalescing series index tests); `compaction
merge deleted` 33 passed; `promql-service` 20 passed + 1 pre-existing
wall-clock flake (`cache::test_promql_cache_set_and_get`, fails
intermittently on main too)
- End-to-end on the #13839 branch (same code paths): hour-end merge
writes N `tsid-major-v3-*` + `.midx`; PromQL takes the series index path
(`selected 39 ranges across 4/4 files`), counts/sums match the synthetic
data, cold-cache identical after restart; a mixed hour (3 major + 1 late
`tsid-sorted`) reports `1 files without a series index left to
tantivy/full scan` with unchanged results; delayed deletion removes
`.parquet` **and** `.midx`
2026-08-21 10:05:46 +00:00

109 lines
4.6 KiB
TOML
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Configuration documentation:
#  https://embarkstudios.github.io/cargo-deny/index.html
[advisories]
yanked = "deny"
git-fetch-with-cli = true
ignore = [
"RUSTSEC-2023-0071", # from rsa, transitive dep of sqlx
"RUSTSEC-2021-0139", # from ansi_term, transitive dep of vrl
"RUSTSEC-2024-0370", # we need it even it is no longer maintained
"RUSTSEC-2024-0384", # from instant, dep of parking_lot
"RUSTSEC-2024-0388", # from derivative, dep of vrl
"RUSTSEC-2024-0421", # from idna, transitive dep of vrl
"RUSTSEC-2024-0436", # from paste, transitive dep of datafusion
"RUSTSEC-2024-0437", # from protobuf, transitive dep of prometheus
]
[licenses]
private = { ignore = true }
exceptions = [
# We should probably NOT bundle CA certs but use the OS ones.
{ name = "webpki-roots", allow = ["MPL-2.0","CDLA-Permissive-2.0"] },
{ name = "webpki-root-certs", allow = ["CDLA-Permissive-2.0"] }, # transitive dep via rustls-platform-verifier
{ name = "openobserve", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-common", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-grpc", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-http", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-ingest", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-management", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-pipelines", allow = ["AGPL-3.0"] },
{ name = "openobserve-api-search", allow = ["AGPL-3.0"] },
{ name = "openobserve-builtins", allow = ["AGPL-3.0"] },
{ name = "common", allow = ["AGPL-3.0"] },
{ name = "openobserve-core", allow = ["AGPL-3.0"] },
{ name = "openobserve-node", allow = ["AGPL-3.0"] },
{ name = "openobserve-jobs", allow = ["AGPL-3.0"] },
{ name = "openobserve-mcp", allow = ["AGPL-3.0"] },
{ name = "openobserve-org-storage", allow = ["AGPL-3.0"] },
{ name = "openobserve-sourcemaps", allow = ["AGPL-3.0"] },
{ name = "openobserve-tls", allow = ["AGPL-3.0"] },
{ name = "transform", allow = ["AGPL-3.0"] },
{ name = "super_cluster_queue", allow = ["AGPL-3.0"] },
{ name = "search", allow = ["AGPL-3.0"] },
{ name = "openobserve-cipher", allow = ["AGPL-3.0"] },
{ name = "db", allow = ["AGPL-3.0"] },
{ name = "promql", allow = ["AGPL-3.0"] },
{ name = "audit", allow = ["AGPL-3.0"] },
{ name = "search_service", allow = ["AGPL-3.0"] },
{ name = "usage_reporting", allow = ["AGPL-3.0"] },
{ name = "vortex", allow = ["AGPL-3.0"] },
{ name = "vortex-datafusion", allow = ["AGPL-3.0"] },
{ name = "metrics_index", allow = ["AGPL-3.0"] },
{ name = "config", allow = ["AGPL-3.0"] },
{ name = "infra", allow = ["AGPL-3.0"] },
{ name = "ingester", allow = ["AGPL-3.0"] },
{ name = "wal", allow = ["AGPL-3.0"] },
{ name = "proto", allow = ["AGPL-3.0"] },
{ name = "flight", allow = ["AGPL-3.0"] },
{ name = "tantivy_utils", allow = ["AGPL-3.0"] },
{ name = "report_server", allow = ["AGPL-3.0"] },
{ name = "web", allow = ["AGPL-3.0"] },
{ name = "arrayref", allow = ["BSD-2-Clause"] },
{ name = "mach", allow = ["BSD-2-Clause"] },
{ name = "fastdivide", allow = ["zlib-acknowledgement"] },
{ name = "foldhash", allow = ["Zlib"] },
{ name = "adler32", allow = ["Zlib"] },
{ name = "libz-rs-sys", allow = ["Zlib"] },
{ name = "libz-rs", allow = ["Zlib"] },
{ name = "zlib-rs", allow = ["Zlib"] },
# These two are transitive deps of vrl via dep rustyline.
# However, rustyline is needed only when we enable feature cli
# which we haven't. Probably in Cargo.lock due to old code, need
# to take a look and remove.
{ name = "clipboard-win", allow = ["BSL-1.0"] },
{ name = "error-code", allow = ["BSL-1.0"] },
{ name = "base16", allow = ["CC0-1.0"] },
{ name = "tiny-keccak", allow = ["CC0-1.0"] },
{ name = "ring", allow = ["BSD-4-Clause", "OpenSSL"] },
{ name = "inferno", allow = ["CDDL-1.0"] },
{ name = "libbz2-rs-sys", allow = ["bzip2-1.0.6"] },
{ name = "aws-lc-sys", allow = ["OpenSSL"] },
{ name = "borrow-or-share", allow = ["MIT-0"] },
]
allow = [
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"MPL-2.0",
"MIT",
"BSD-3-Clause", # OSI + fsf free
"Unicode-3.0", # OSI
"ISC", # OSI + fsf free
"BSL-1.0",
"CC0-1.0",
"0BSD", # OSI approved - BSD Zero Clause License
]
[[licenses.clarify]]
name = "ring"
expression = "BSD-4-Clause AND ISC AND MIT AND OpenSSL"
license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }]
[sources.allow-org]
github = ["openobserve", "apache"]
[sources]
allow-git = ["https://github.com/mattsse/chromiumoxide"]