#24459·loki

Blooms: case-insensitive regex literals prune matching structured metadata

Author: hundredwattCreated Sep 9, 2026Updated Sep 14, 2026
Labelsfeature/blooms

Describe the bug

When Bloom Gateway filtering is enabled, a pipeline label filter with a case-insensitive regex literal can return no lines even though the structured-metadata value matches.

logql
{app="a"} | device_id=~"(?i)a66347031de50be5"

The same filter works without blooms, and these variants also work with blooms:

logql
{app="a"} | device_id=~"a66347031de50be5"          # case-sensitive literal
{app="a"} | device_id=~"(?i)a66347031de50be5.*"    # extra wildcard
{app="a"} | device_id=~"(?i)a66347031de50be5$"     # explicit anchor
{app="a"} | device_id=~"^(?i)a66347031de50be5"     # explicit anchor

To Reproduce

  1. Loki with bloom creation and Bloom Gateway filtering enabled for the tenant.
  2. Ingest a log with structured metadata device_id="a66347031de50be5" (lowercase). Wait until a bloom is built for that chunk.
  3. Query:
logql
{app="a"} | device_id=~"(?i)a66347031de50be5"
  1. Observed: 0 lines.
  2. Repeat with | device_id=~"a66347031de50be5" or | device_id=~"(?i)a66347031de50be5.*". Those return the line.

Without blooms, pkg/logql / logqltest already treats (?i)a66347031de50be5 as an equality match (with case folding) and returns both a66347031de50be5 and A66347031DE50BE5.

Expected behavior

| device_id=~"(?i)a66347031de50be5" should return every line whose device_id value equals that hex string, ignoring case. Bloom filtering should not drop a chunk that contains a case-folded match.

Cause

ExtractTestableLabelMatchers in pkg/storage/bloom/v1/ast_extractor.go simplifies (?i)a66347031de50be5 to a KeyValueMatcher whose Value is the regex engine's canonical literal:

go
case regexsyn.OpLiteral:
    return KeyValueMatcher{
        Key:   key,
        Value: string(reg.Rune),
    }

regexp/syntax with FoldCase stores that literal in uppercase (A66347031DE50BE5). Blooms are built from the stored metadata as-is (a66347031de50be5), so the bloom test misses and the gateway prunes the chunk.

Patterns that do not simplify to a bare OpLiteral (.*, ^, $, character classes) become UnsupportedLabelMatcher, for which bloom tests always pass. That is why adding a wildcard or anchor "fixes" the query.

Confirmed with:

{app="a"} | device_id=~"a66347031de50be5"           → KeyValueMatcher{Key:"device_id", Value:"a66347031de50be5"}
{app="a"} | device_id=~"(?i)a66347031de50be5"       → KeyValueMatcher{Key:"device_id", Value:"A66347031DE50BE5"}
{app="a"} | device_id=~"(?i)a66347031de50be5.*"     → UnsupportedLabelMatcher{}
{app="a"} | device_id=~"(?i)a66347031de50be5$"      → UnsupportedLabelMatcher{}
{app="a"} | device_id=~"^(?i)a66347031de50be5"      → UnsupportedLabelMatcher{}

Workaround

Disable bloom_gateway_enable_filtering for the tenant, or rewrite the filter so the extractor cannot turn it into a case-folded equality ((?i)….*, ^, $).