Skip to content

perf(python): reuse prepared series for repeated single-device table queries - #1082

Open
ColinLeeo wants to merge 1 commit into
apache:developfrom
ColinLeeo:perf/python-query-table-prepared-cache
Open

ColinLeeo wants to merge 1 commit into
apache:developfrom
ColinLeeo:perf/python-query-table-prepared-cache

Conversation

@ColinLeeo

@ColinLeeo ColinLeeo commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Repeated batch queries whose tag filter selects exactly one device rebuilt the device and timeseries-index metadata on every call. query_table now resolves the requested field columns once through a bounded LRU and executes them as prepared queries: one column through query_prepared, several aligned columns through query_prepared_multi. Queries that select a TAG column, use row-by-row mode, or whose filter matches zero or several devices keep the previous path.

  • TsFileReader::prepare_table_series() resolves (table, column, tag filter) to a PreparedLocator through the existing device index and parses it once.
  • The prepared query paths accept a TsBlock row capacity (block_rows) instead of always allocating PREPARED_BATCH_ROWS, so cached queries keep the caller's batch_size block boundaries. The existing C entry points keep their signatures; tsfile_reader_query_prepared_ex and tsfile_reader_query_prepared_multi_ex carry the new parameter.
  • Python exposes prepare_table_series() and caches single-device field queries behind TSFILE_QUERY_CACHE_SIZE (default 1024, 0 disables). close() releases the cached handles.
  • Only "not cacheable" error codes fall back to the regular path. I/O, short-read, corruption and OOM errors still reach the caller, so the existing read-failure propagation tests pass unchanged.

Measurements

323 devices x 20,000 points, 2,000 point queries per run, warm page cache, alternating cache on/off:

query cache off (median) cache on (median)
1 field column 229 us 54 us
2 field columns 287 us 102 us

The means include one miss per device; steady state is roughly 50 us (one column) and 100 us (two columns) per query.

Scope

  • The cache applies only when the tag filter selects exactly one device and every requested column is a stored field. TAG columns, row mode and multi-device filters keep the previous code path; non-cacheable results are remembered so they are not retried on every call.
  • Results are unchanged: the new tests compare cached and uncached output across one and two columns, column order, time ranges containing nulls, compound filters, eviction, TAG-column fallback and row mode. tests/test_reader_sources.py still verifies that every injected read failure propagates.
  • block_rows also removes the hard-coded 65,536-row block that prepared queries used when no row limit was given ([Perf][C++] Point queries rebuild index metadata on every call; prepared series queries are ~7x faster but need fixes #1080).

Tests

  • python/tests/test_query_table_cache.py: 17 cases.
  • Full Python suite: 620 passed, 1 skipped.
  • C++ library and Cython extensions built locally; clang-format applied to the touched files.

Closes #1081. Relates to #1080.

…queries

Repeated batch queries whose tag filter selects exactly one device rebuilt
the device and timeseries-index metadata on every call. query_table now
resolves the requested field columns once through a bounded LRU and runs
them as prepared queries: one column through query_prepared, several aligned
columns through query_prepared_multi. Queries that select a TAG column, use
row-by-row mode, or whose filter matches zero or several devices keep the
previous path.

- Add TsFileReader::prepare_table_series(), which resolves
  (table, column, tag filter) to a PreparedLocator through the existing
  device index and parses it once.
- Let the prepared query paths take a TsBlock row capacity (block_rows)
  instead of always allocating PREPARED_BATCH_ROWS, so cached queries keep
  the caller's batch_size block boundaries. The existing C entry points keep
  their signatures; tsfile_reader_query_prepared_ex and
  tsfile_reader_query_prepared_multi_ex carry the new parameter.
- Expose prepare_table_series() in Python and cache single-device field
  queries behind TSFILE_QUERY_CACHE_SIZE (default 1024, 0 disables). Only
  "not cacheable" error codes fall back to the regular path; I/O, short-read,
  corruption and OOM errors still reach the caller. close() releases the
  cached handles.

Measured locally (323 devices x 20,000 points, 2048-point windows, 2,000
queries, warm cache): one column 229 us -> 54 us median; two columns
287 us -> 102 us median. Full Python suite: 620 passed.

Closes apache#1081. Relates to apache#1080.
@ColinLeeo
ColinLeeo force-pushed the perf/python-query-table-prepared-cache branch from 285bd62 to 4267cb9 Compare November 10, 2026 08:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Perf][Python] TsFileReader.query_table() does not reuse the dataset-layer prepared/cache machinery

1 participant