---
title: "TSDB index format v4 | Grafana Loki documentation"
description: "Describes the on-disk binary layout of version 4 of the Loki TSDB index."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# TSDB index format v4

Version 4 is written for periods with schema version `v14`. This version is experimental. It keeps the paging layout of [version 3](/docs/loki/next/reference/tsdb-index-format/v3/) and appends an ingestion timestamp to every chunk meta, which allows retention to be measured from the time a chunk was ingested rather than from the timestamps of the log lines it contains. See [TSDB index format](/docs/loki/next/reference/tsdb-index-format/) for the encoding notation used on this page.

## Differences from v3

- Each chunk meta ends with an `ingested_at delta` field, encoded at day precision relative to `max_time`.
- The field is 0 for all chunks that were not backfilled.

## File layout

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
+-----------------------------------------------------------+
| magic(0xBAAAD700) <4b> | version(4) <1b>                  |
+-----------------------------------------------------------+
| symbol table              (TOC.symbols)                   |
+-----------------------------------------------------------+
| series                    (TOC.series)                    |
+-----------------------------------------------------------+
| label indices             (TOC.label_indices)             |
+-----------------------------------------------------------+
| postings                  (TOC.postings)                  |
+-----------------------------------------------------------+
| label offset table        (TOC.label_offset_table)        |
+-----------------------------------------------------------+
| postings offset table     (TOC.postings_offset_table)     |
+-----------------------------------------------------------+
| fingerprint offsets table (TOC.fingerprint_offsets)       |
+-----------------------------------------------------------+
| TOC                       (last 76 bytes of the file)     |
+-----------------------------------------------------------+
```

## Symbol table

Unchanged from v2 and v3: a lexicographically sorted, deduplicated table of all label names and values, referenced by ordinal position.

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
+-----------------------------------------------------------+
| len <4b>                                                  |
+-----------------------------------------------------------+
| #symbols <4b>                                             |
+-----------------------------------------------------------+
| symbol_0 <uvarint_str>                                    |
+-----------------------------------------------------------+
| ...                                                       |
+-----------------------------------------------------------+
| CRC32 <4b>                                                |
+-----------------------------------------------------------+
```

## Series

The series header is unchanged. Entries start on a 16-byte boundary and the series reference is the entry offset divided by 16.

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
+-----------------------------------------------------------+
| len(entry) <uvarint>                                      |
+-----------------------------------------------------------+
| fingerprint <8b>                                          |
+-----------------------------------------------------------+
| #labels <uvarint>                                         |
+-----------------------------+-----------------------------+
| ref(name_0) <uvarint>       | ref(value_0) <uvarint>      |
+-----------------------------+-----------------------------+
| ...                         | ...                         |
+-----------------------------+-----------------------------+
| chunks                          (see below, v4 layout)    |
+-----------------------------------------------------------+
| CRC32 <4b>                                                |
+-----------------------------------------------------------+
```

## Chunks

The markers section is identical to v3. Each chunk meta carries one additional trailing field, `ingested_at delta`.

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
+-----------------------------------------------------------+
| #chunks <uvarint>                                         |
+-----------------------------------------------------------+
| len(markers) <4b>               (skip to chunk metas)     |
+-----------------------------------------------------------+
| #markers <uvarint>                                        |
+-----------------------------------------------------------+
| marker_0:                                                 |
|   #chunks in page <uvarint>                               |
|   KB <4b>                       (sum over page)           |
|   #entries <4b>                 (sum over page)           |
|   offset <uvarint>              (page start, relative to  |
|                                  the first chunk meta)    |
|   min_time <varint>             (page bounds)             |
|   max_time - min_time <varint>                            |
+-----------------------------------------------------------+
| ...                                                       |
+-----------------------------------------------------------+
| marker_m-1: (same fields, may cover a partial page)       |
+-----------------------------------------------------------+
| chunk_0:                                                  |
|   min_time delta <varint>       (vs. previous max_time)   |
|   max_time - min_time <uvarint>                           |
|   KB <uvarint>                                            |
|   #entries <uvarint>                                      |
|   checksum <4b>                                           |
|   ingested_at delta <uvarint>   (v4 only, see below)      |
+-----------------------------------------------------------+
| ...                                                       |
+-----------------------------------------------------------+
| chunk_n-1: (same fields)                                  |
+-----------------------------------------------------------+
```

Because the field is appended to the chunk meta, a v4 chunk meta cannot be read by a v3 reader. The index version in the header selects the decoder, so a cluster only needs to know that indices written under schema `v14` cannot be read by a version of Loki that predates v4 support.

### Ingestion timestamp encoding

`ingested_at` records when a chunk was flushed to storage, not when its log lines were received. It is only recorded for backfilled chunks, that is, chunks of a stream carrying the `__backfill__="true"` label. It is stored at day precision, relative to the chunk’s `max_time`:

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
day               = 86400000 ms
delta_days        = ceil_day(ingested_at) - floor_day(max_time)
ingested_at delta = zigzag(delta_days) + 1
```

- The value 0 is a sentinel meaning “no ingestion timestamp”. Every non-backfilled chunk stores 0, so the field costs a single byte for the common case.
- `ingested_at` is rounded up to the next UTC day boundary. Rounding must never move the timestamp earlier, otherwise retention measured from ingestion could expire a chunk before its window elapsed. The cost is retaining a chunk for up to one day longer.
- The zigzag mapping keeps small negative deltas cheap: 0 days encodes as 0, -1 day as 1, +1 day as 2. Adding 1 keeps the encoded 0 free for the sentinel.
- Rounding is stable. Re-encoding an already rounded `ingested_at` yields the same delta, so compaction does not drift the value.

For example, if `max_time` is 2026-01-10 14:30 UTC and the chunk was ingested at 2026-01-12 08:00 UTC, ingestion rounds up to 2026-01-13 00:00 UTC, the day of `max_time` is 2026-01-10, and the stored delta is +3 days.

## Remaining sections

The label indices, postings, label offset table, postings offset table, fingerprint offsets table, and TOC are identical to v2 and v3. See:

- [Label indices](/docs/loki/next/reference/tsdb-index-format/v2/#label-indices)
- [Postings](/docs/loki/next/reference/tsdb-index-format/v2/#postings)
- [Label offset table](/docs/loki/next/reference/tsdb-index-format/v2/#label-offset-table)
- [Postings offset table](/docs/loki/next/reference/tsdb-index-format/v2/#postings-offset-table)
- [Fingerprint offsets table](/docs/loki/next/reference/tsdb-index-format/v2/#fingerprint-offsets-table)
- [TOC](/docs/loki/next/reference/tsdb-index-format/v2/#toc)
