JetStream file-store ~65x slower than memory-store on KV mirror (83% of seq space is deleted) #8417
Environment
Stream layoutThe hub owns the authoritative KV bucket
Both mirrors share the same source config: Both end up with identical stream state after sync: The bucket is a KV, so every Observation 1: consumer throughput differs ~65xSame machine, same client, same consumer code — only the mirror's // Effective options passed to nats.go
consumer, _ := stream.CreateOrUpdateConsumer(ctx, jetstream.ConsumerConfig{
DeliverPolicy: jetstream.DeliverLastPerSubjectPolicy,
AckPolicy: jetstream.AckNonePolicy,
FilterSubject: "$KV.DNS.>",
InactiveThreshold: time.Hour,
Replicas: 1,
})
iter, _ := consumer.Messages(
jetstream.PullMaxBytes(4 * 1024 * 1024), // 4 MB per batch
jetstream.PullExpiry(5 * time.Second),
jetstream.PullThresholdMessages(500),
)
for {
msg, err := iter.Next()
if err != nil { break }
total.Add(1)
totalSize.Add(uint64(len(msg.Data())))
}Memory-store mirror: File-store mirror (everything else identical): Sustained ~4 k msg/s, ~0.5 MB/s. The per-window rate is very lumpy (1.5 k → 8 k → 3.5 k …), which we suspect corresponds to blocks of varying seq density. Observation 2: mirror sync from hub is also slow on file-storeWhen we first create the file-store mirror, initial sync from hub reports (excerpt): That's roughly 2 k live-msg/s reaching the leaf (~12 k seq/s counting deletes). The memory-store mirror finishes syncing the same 2 M messages in seconds. What we ruled out
Where we think the bottleneck may beReading the file-store path, this is what we see (best guess, not verified with a profile yet):
So even though physical compact packs the bytes well on the leaf side (on-disk 6.0 GiB vs live We don't fully understand why the total gap is 65× — we'd expect the AVL + lock overhead to hurt but not by that much. Something else in the delivery path may be contributing that we haven't traced yet. Questions
Happy to gather more data — a CPU profile, a minimal reproducer, or run whatever experiments would help. Attachment 1 — top -H during file-store consumption (3 snapshots, 2 s apart)Attachment 2 — iostat -xdm 1 5 during file-store consumption
Attachment 3 — on-disk block layout: hub source vs leaf file mirrorFor the same ~2 M live messages (identical Hub (authoritative Ratio: live 5.89 GiB / on-disk 7.3 GiB ≈ 81 % — the source has accumulated many small, sparsely-populated blocks over time (many Leaf (file mirror Ratio: live 5.89 GiB / on-disk 6.0 GiB ≈ 98 % — the mirror re-packs the same data into ~3× fewer blocks with almost no dead bytes, because it replays messages sequentially rather than in-place. Comparison:
Consumer throughput is ~4 k msg/s on both. So neither the number of blocks nor the on-disk fragmentation appears to explain the slowness on its own. |
Replies: 3 comments 8 replies
|
@derekcollison @kozlovic Hi both, would love to hear your thoughts on this. |
|
@neilalexander or @MauriceVanVeen could supply some guidance here. |
|
I'm working on an improvement in the filestore for a situation like this where the number of deletes are greater than the number of unique subjects. In the meantime, could you test with the consumer's FilterSubject removed? It seems that you're not actually intending to filter and instead want to consume everything as fast as possible. If that is significantly faster/on par with the memstore, then we'll know what's going on. |
I'm working on an improvement in the filestore for a situation like this where the number of deletes are greater than the number of unique subjects.
In the meantime, could you test with the consumer's FilterSubject removed? It seems that you're not actually intending to filter and instead want to consume everything as fast as possible. If that is significantly faster/on par with the memstore, then we'll know what's going on.