TiFlash: read only StringSizes for LENGTH / CHAR_LENGTH scans
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 1k
- Forks
- 423
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 24
Description
Feature Request
Is your feature request related to a problem? Please describe:
When a query needs only string length (LENGTH / CHAR_LENGTH) and not the payload, TiFlash still reads both the chars and StringSizes streams from DMFile.
A typical case is a full-table MAX(CHAR_LENGTH(col)):
SELECT
MAX(CHAR_LENGTH(c1)) AS c1,
MAX(CHAR_LENGTH(c2)) AS c2,
-- ... a dozen or more string columns
MAX(CHAR_LENGTH(c16)) AS c16
FROM t;
In one observation, TableScan outbound was ~66 GB of strings per node and Projection outbound ~38 GB of Int64. At the compute layer, LENGTH already uses offsets and ignores data; CHAR_LENGTH (utf8mb4) must scan payload to count code points. The IO layer has no “this column needs length only” signal, so TableScan still reads the fat chars stream and saturates cloud-disk bandwidth.
This is not limited to MAX(): any projection or aggregation that depends only on length, not on string contents, over-reads payload.
This request is a follow-up of https://github.com/pingcap/tiflash/issues/11049.
Describe the feature you'd like:
Push “length only, no payload” down to the DMFile reader, and choose substreams from the function and pack contents.
String columns in DMFile are already split into:
StringSizes: per-row length (~4 B/row)chars: payload- nullmap (if any)
Expected behavior:
-
LENGTH(col)
Read onlyStringSizesand the nullmap; skipchars. Byte length comes directly from sizes. -
CHAR_LENGTH(col)(utf8mb4)
Still readcharsby default to count code points. If a pack is proven all-ASCII (e.g. pack-levelall_ascii: no high-bit bytes), thenCHAR_LENGTH = LENGTHand that pack can also read sizes only. -
Push-down interface
Scan / Projection should declare a per-column read mode, e.g.NeedPayload/NeedByteLength/NeedCharLength.MAX(LENGTH(col))/MAX(CHAR_LENGTH(col))in HashAgg should pass that through to TableScan, instead of materializing full string columns and dropping payload in compute.
Expected effect: in the observation above, payload dominates outbound; sizes are ~4 B/row. Reading sizes only can drop disk IO for those columns by an order of magnitude. This is general: LENGTH(col), SUM(LENGTH(col)), MAX(LENGTH(col)), and similar queries all benefit.
Implementation notes:
CHAR_LENGTHon non-ASCII packs must fall back to readingchars, matching utf8mb4 semantics.- If
all_ascii(or equivalent) is used, store it in pack metadata; missing flag on old files is unknown → fall back tochars. - Nullable columns still need the nullmap; empty-string vs NULL length semantics stay unchanged.
- Complementary to pack-level
max_lenskip: skip packs that cannot refreshMAXfirst, then sizes-only on the rest. This request does not depend on skip; landing it alone still helps full-tableLENGTHscans.
Describe alternatives you've considered:
-
Pack-level
max_lenskip
ForMAX(CHAR_LENGTH)/MAX(LENGTH), prune with pack upper bounds and possibly skip entire packs. It does not help queries that must see every row’s length (SUM(LENGTH(col)),SELECT LENGTH(col)). Combined with this request it pays off most, but it does not replace sizes-only. -
Rewriting
CHAR_LENGTHtoLENGTHin SQL
Close on ASCII data, but TableScan still readscharstoday, so only CPU is saved. Without this request, changing SQL does not cut IO. -
Hidden / STORED generated column of per-row length
Broader coverage as a regular integer column, but more write amplification and schema cost. TiDB cannot add STORED generated columns viaALTER TABLE. The sizes stream already stores per-row byte length; prefer reading existingStringSizesrather than storing another column. -
Folding
CHAR_LENGTHinto HashAgg only
Reduces Int64 intermediates, notcharsreads. Wall time barely changes when IO-bound.
Teachability, Documentation, Adoption, Migration Strategy:
- Users: no SQL change. Queries that use only
LENGTH/CHAR_LENGTHand never the payload should automatically read lesscharson TiFlash. - When it applies: a read-path optimization; existing DMFiles need not be rewritten. Pack flags such as
all_asciiapply only to files written after the change (or after compaction); old files fall back to readingchars; results stay correct. - Observability: distinguish
sizesvscharsbytes read in TableScan /tiflash_scan, and count packs that skippedcharsdue to ASCII. - Compatibility: the reader must fall back safely without
all_ascii; it must not treatCHAR_LENGTHof non-ASCII data as byte length. - Docs: note in TiFlash scan docs that length functions prefer the string sizes stream; utf8mb4
CHAR_LENGTHstill reads payload when ASCII cannot be proven.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the DMFile reader and the TableScan, Projection, and HashAgg paths mentioned in the request, then inspect how StringSizes, chars, and nullmaps are selected. Define the per-column read modes and verify LENGTH, CHAR_LENGTH, nullable values, ASCII packs, and legacy files. Done means payload is skipped only when semantics permit, with correct fallback behavior and read-byte observability.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, sql
- Domain
- databases, performance
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100