Skip to content

Filestore reads an uncompressed block as a compressed block when the first record length is 0x706D63 ("cmp") #8433

Description

@soundsonacid

Observed behavior

  • The server cannot read one message from a stream.
  • The server shows this error for each read of the message:
    [ERR] Received an error looking up message for consumer '$G > STREAM_NAME > CONSUMER_NAME': compression algorithm not known
  • All consumers of the stream stop at the sequence before this message.
  • The consumers do not move to the next sequence.
  • A read of the message with nats stream get gives error 10037, "no message found".
  • A delete of the message with nats stream rmm gives error 10057, "compression algorithm not known".
  • The data on the disk is correct. The server cannot decode it.

Root cause:

The filestore uses the first bytes of a block to find the compression metadata.
The function CompressionInfo.UnmarshalMetadata (server/filestore.go) does this test
(here in current main):

if b[0] != 'c' || b[1] != 'm' || b[2] != 'p' {
    return 0, nil
}
c.Algorithm = StoreCompression(b[3])

An uncompressed block does not start with metadata.
An uncompressed block starts with a record header:

[4 bytes: record length, little-endian, bit 31 = "has headers"]
[8 bytes: sequence]
[8 bytes: timestamp]
[2 bytes: subject length]
...

A record length of exactly 7,368,035 bytes is 0x706D63.
In little-endian order, these bytes are 63 6D 70.
These bytes are the ASCII text "cmp".
This is the same as the compression magic prefix.

When the message has headers, bit 31 of the length field is set.
Then the fourth byte is 0x80.
UnmarshalMetadata reads this byte as the compression algorithm.
Algorithm 128 does not exist.
Decompress then returns "compression algorithm not known".
Each subsequent load of the block fails with the same error.
The block stays unreadable until retention deletes it.

Expected behavior

  • The server reads each message that it stored, for all message sizes.
  • The size of a message has no effect on the ability of the server to read it.
  • If the server cannot read one message, the consumers continue with the next sequence.

Server and client version

nats-server v2.11.8
client: rust async-nats 0.45.0

Host environment

OS: Ubuntu 24.04.2 LTS, kernel 6.8.0-55-lowlatency, x86_64

Steps to reproduce

  1. Create a stream with file storage and no compression.
  2. Publish a message with headers. Make the total stored record length exactly 7,368,035 bytes (0x706D63). The record length is the sum of the 22-byte record header, the subject, the header block, the payload, and the checksum.
  3. Make sure the message is the first record of its block. A payload of this size starts a new block with the default block sizes.
  4. Remove the block from the cache. A server restart also does this.
  5. Try to read the message with nats stream get <stream> <seq>.
  6. The read fails with "compression algorithm not known".

Note: other lengths also cause the collision. Each length with the low three bytes 63 6D 70 causes it. For blocks smaller than 16 MB, only 7,368,035 is possible.

Information for Stream STREAM_NAME created 2025-09-28 05:08:32

            Subjects: stream.subject
            Replicas: 1
             Storage: File

Options:

           Retention: Limits
     Acknowledgments: true
      Discard Policy: Old
    Duplicate Window: 30m0s
          Direct Get: true

Limits:

    Maximum Messages: unlimited
 Maximum Per Subject: unlimited
       Maximum Bytes: 800 GiB
         Maximum Age: 1d1h0m0s
Maximum Message Size: unlimited
   Maximum Consumers: unlimited

Consumer state, captured while the consumer was stopped at the bad sequence (68,283,121):

Information for Consumer STREAM_NAME > CONSUMER_NAME created 2026-06-29 15:04:56

Configuration:

                    Name: CONSUMER_NAME
               Pull Mode: true
          Deliver Policy: From Sequence 61862235
              Ack Policy: Explicit
                Ack Wait: 30.00s
           Replay Policy: Instant
      Maximum Deliveries: 10
         Max Ack Pending: 10,000
       Max Waiting Pulls: 512

State:

  Last Delivered Message: Consumer sequence: 6,452,554 Stream sequence: 68,283,120 Last delivery: 36m12s ago
    Acknowledgment Floor: Consumer sequence: 6,452,554 Stream sequence: 68,283,120 Last Ack: 35m54s ago
        Outstanding Acks: 0 out of maximum 10,000
    Redelivered Messages: 1,885
    Unprocessed Messages: 5,115
           Waiting Pulls: 1 of maximum 512

Note: the consumer had active waiting pulls. The applications were healthy. The server did not deliver the next message. Four durable pull consumers on this stream showed the same state at the same sequence.

Metadata

Metadata

Assignees

No one assigned

    Labels

    defectSuspected defect such as a bug or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions