blob: 890ab75646187815743f7bc3ecfe98566e8d49b8 [file] [view]
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
# Chunks
The TsFile data section consists of marker-delimited Chunk Groups, Chunks, and Operation Index
Ranges. It starts at offset `7` without a file encryption header, or at `7 + E` when a Page AEAD
[FileEncryptionHeader](Encryption.md#file-encryption-header) of total serialized size `E` occupies
offset `7`. That optional header occurs exactly once, before the data section.
The top-level grammar is:
```text
FilePrefix := magic[6] version[1] FileEncryptionHeader?
DataSection := (ChunkGroup+ OperationIndexRange?)*
ChunkGroup := ChunkGroupHeader Chunk*
Separator := byte(0x02)
```
Each batch contains one or more ChunkGroups followed by an optional OperationIndexRange. The
`0x02` separator follows the data section; it is not part of a Chunk Group. A Chunk Group has no
total-length field. After reading each Chunk, the reader examines the next marker: another
Chunk continues the group, while a new group, operation-index record, or separator ends it.
```text
File
└── Chunk Group a batch of data for one device
├── Non-aligned Chunk one measurement column with its own time stream
│ └── Page(s)
└── Aligned Chunk Set measurement columns sharing a time column
├── Time Chunk
│ └── Time Page(s)
└── Value Chunk(s)
└── Value Page(s)
```
## Markers
| Value | Name | Following structure |
| ---: | --- | --- |
| `0x00` | `CHUNK_GROUP_HEADER` | `DeviceID` |
| `0x01` | `CHUNK_HEADER` | Multi-page non-aligned Chunk |
| `0x02` | `SEPARATOR` | End of data section and start of metadata section |
| `0x03` | `VERSION` | Unused since v3; identifier reserved |
| `0x04` | `OPERATION_INDEX_RANGE` | Two `int64` values |
| `0x05` | `ONLY_ONE_PAGE_CHUNK_HEADER` | Single-page non-aligned Chunk |
| `0x06` | `ENCRYPTION_HEADER` | File encryption header at offset `7` only; not a Chunk |
| `0x41` | `VALUE_CHUNK_HEADER` | Multi-page aligned Value Chunk |
| `0x45` | `ONLY_ONE_PAGE_VALUE_CHUNK_HEADER` | Single-page aligned Value Chunk |
| `0x81` | `TIME_CHUNK_HEADER` | Multi-page aligned Time Chunk |
| `0x85` | `ONLY_ONE_PAGE_TIME_CHUNK_HEADER` | Single-page aligned Time Chunk |
The high two bits of a Chunk marker identify the column role. The low six bits identify the Page
count category:
```text
bit 7 = 1: aligned time column
bit 6 = 1: aligned value column
low 6 bits = 0x01: more than one Page
low 6 bits = 0x05: one Page or no Page
```
An aligned Value Chunk with `data_size = 0` can contain no Page.
Read markers only after consuming the preceding structure. A Page body can contain any byte
value, including bytes that look like markers.
## Chunk Group Header
```text
ChunkGroupHeader :=
byte marker = 0x00
DeviceID device_id
DeviceID :=
UVarInt segment_count
VarString segment[segment_count]
```
A DeviceID is an ordered list of string segments; individual segments can be null. Serialization
removes trailing nulls, but at least one segment must be non-null. The special `segment_count = 0`
form is read as one empty-string segment.
Chunks following a Chunk Group Header belong to that device until the next Chunk Group Header,
Operation Index Range, or Separator.
`segment_count` counts segments. Each segment has its own byte length. Keep the segments separate
when comparing device identities: joining them with dots for display can be ambiguous if a segment
itself contains a dot.
## Chunk Header
The marker is the first field of the Chunk Header:
```text
ChunkHeader :=
byte chunk_type
VarString measurement_id
UVarInt data_size
byte data_type
byte compression_type
byte encoding_type
int64 chunk_ordinal?
int32 chunk_authentication_size?
byte chunk_authentication[chunk_authentication_size]?
```
The final three fields appear only in Page AEAD files. The file encryption header selects this
suffix; the Chunk marker remains the same. The ordinal, proof format, and validation rules are
described in [AEAD Chunk Header](Encryption.md#aead-chunk-header). Like the rest of the Header,
the suffix is excluded from `data_size`.
The Chunk Header contains no Statistics field. The first Page Header immediately follows the
Chunk Header. When that Page is non-empty and the Chunk has multiple Pages, its Statistics follows
the two length fields, so those Statistics bytes are physically adjacent to the Chunk Header but
belong to the Page Header. Chunk-level Statistics are stored with the Chunk Metadata in the footer;
a single-Page Chunk omits Page Statistics and relies on the Chunk or series Statistics. See
[Data Pages](README.md) and [Statistics](../Metadata.md#statistics).
`data_size` is the total size of all Page Headers and stored Page data in the Chunk. It does not
include the Chunk Header. A reader consumes exactly `data_size` bytes after the Chunk Header.
If `H` is the offset immediately after the Chunk Header, then the next structure begins at:
```text
next_structure_offset = H + data_size
```
The sum of every serialized Page Header and its `compressed_size` bytes must equal `data_size`.
The value is therefore both a skip length and a containment boundary for validating Pages.
For an aligned Time Chunk, `measurement_id` is an empty string and `data_type` is `VECTOR (0x06)`.
For an aligned Value Chunk or non-aligned Chunk, `data_type` is the measurement value type.
Aligned data is stored Chunk by Chunk, not as interleaved Time Pages and Value Pages. The Time
Chunk stores the complete sequence of Time Pages, and each Value Chunk stores its own sequence of
Value Pages. Pages correspond by ordinal position.
An aligned Chunk set is one Time Chunk followed by its Value Chunks. It has no separate set
header. The relationships within a set are:
- The Time Chunk appears before the Value Chunks that use it.
- A Value Chunk identifies an individual measurement; the Time Chunk uses an empty
`measurement_id` and `VECTOR (0x06)` as its physical type marker.
- Value Page `i` covers the logical positions of Time Page `i`.
- A Value Chunk may be absent when the measurement has no data in the set. An existing aligned
Value Chunk can also have `data_size = 0`.
For a non-aligned Chunk, every logical point carries a timestamp and a value. Consequently, the
decoded time stream and decoded value stream of each Page must contain the same number of
elements.
## Chunk Scan State Machine
A streaming or recovery reader can process the data section with this state machine:
1. Validate the prefix, inspect offset `7`, and consume the optional `0x06` encryption header.
Resolve its provider/profile before parsing Chunks; continue at `data_start = 7 + E` (`E = 0`
when absent). A later or repeated `0x06` is invalid. Read the first data-section marker there.
2. For `0x00`, read the DeviceID and enter that Chunk Group.
3. For a Chunk marker, read its complete Header, including the AEAD suffix when selected, and
verify the Chunk-position proof before using its identity. Advance by exactly `data_size` bytes
after optionally parsing its Pages.
4. For `0x04`, consume the following 16 bytes as an operation-index range.
5. For `0x02`, stop. The following byte belongs to the indexed metadata region.
6. Reject an unrecognized top-level marker or a structure whose declared boundary exceeds the
available bytes.
## Operation Index Range
```text
OperationIndexRange :=
byte marker = 0x04
int64 min_operation_index
int64 max_operation_index
```
This optional record lets a storage engine associate a flushed batch with its operation log.
The two values are the minimum and maximum operation sequence numbers in that batch. They help
coordinate checkpoints, snapshots, backups, and recovery; they are unrelated to timestamps or
file offsets.
A general-purpose writer can omit this record. A reader that does not use operation indexes
still consumes both `int64` fields before reading the next marker. Several records can appear
when several batches are flushed into the same file.