Cuong Le (@cuongleqq) header
Cuong Le (@cuongleqq) profile picture

VictoriaLogs core maintainer @VictoriaMetrics | ex-Head of Engineering @FeralFile | Writing about high-performance Rust, Go & database internals

Joined September 2017
Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)Photo by Cuong Le (@cuongleqq)
I spent 3 months benchmarking and digging into the internals of Loki, Elasticsearch, OpenSearch, ClickStack, SigNoz and VictoriaLogs, and condensed it into a 20-minute read. TL;DR: I realized who ingests fastest is only one side of the story. Every fast number had a price. I abused each of them with a flood of structured, Kubernetes-style logs on identical machines, until they choked. 1. Loki ingested the most: 139k lines/s. It indexes only stream labels and keeps lines as plain text, pushing the cost to query time. Any search or aggregation on a field, the kind every dashboard runs, is expensive and can take minutes: Loki has to decompress and scan every line in matching streams. 2. Elasticsearch finds a single word almost instantly, because it indexes every word of every line. The price: all that indexing is CPU-heavy, so it topped out at 48k lines/s. And it needed almost 5x the disk of VictoriaLogs. 3. SigNoz and ClickStack store logs in ClickHouse, a columnar database like VictoriaLogs. I expected them to be close. They only reached 10k and 46k lines/s. The bottleneck wasn't ClickHouse. It was the OpenTelemetry collector in front of it, which used more CPU than ClickHouse itself. 4. VictoriaLogs (I work on it) ingested 120k lines/s with the least memory and disk. It stores each field in its own column and reads only the columns a query needs, so every test query returned within seconds. The catch: it shines on structured logs, where columns help most. On plain-text logs (from older apps or network devices), it still works, but loses most of its edge.
Replying to @cuongleqq
@cuongleqq published data ingestion benchmark results for open-source databases for logs: VictoriaLogs, Loki, Elasticsearch, OpenSearch, SigNoz and ClickStack (the last two are based on ClickHouse). The benchmark measures CPU usage, memory usage, disk space usage and disk IO usage for different ingestion rates of Kubernetes-like logs. It also measures performance for production-like queries. Hint: VictoriaLogs isn't the fastest one for data ingestion. victoriametrics.com/blog/str…
1
1
23
977
Cuong Le retweeted
Go changed the implementation behind sync.Map to a hash trie. The API is still the same, but this is a good time to revisit how sync.Map works, what happens internally, and when to use it instead of a regular map protected by a mutex. Detailed walkthrough here: victoriametrics.com/blog/go-…
4
50
2
358
24,622
Cuong Le retweeted
noCopy is an interesting zero-byte field used in almost every sync struct. But you can still copy the sync struct, it doesn't stop the compiler, it doesn't stop your program from running. So what does it actually do? Here :) func25.dev/posts/go-sync-noc…
2
21
8
301
151,193
Cuong Le retweeted
Chapter 8 of The Anatomy of Go is now available. It discusses channels, timers, concurrency, goroutine creation and lifecycle, runtime startup, preemption, scheduling, I/O handling, the system monitor... and a bunch of other things. The book is now complete at 873 pages, and early access will be closing soon after the editing phase, (which may add a few more pages). As usual, here is an excerpt:
7
46
4
444
21,251
Cuong Le retweeted
The Zig compiler project rejects AI contributions for very concrete reasons. - The contributor pipeline is a core aspect of our business model and produces value FAR BEYOND the code. Breaking it is an existential threat to Zig. kristoff.it/blog/contributor… - For some projects it's a problem to rely on vibecoded infrastructure. Some have learned this the hard way with Bun recently. Zig is general purpose programming language for building critical infrastructure and we want to give maximum freedom to our users. See quoted message.
I wrote these words 7 months ago. I am more grateful today for Andrew's leadership of the ZSF, that the foundations of TigerBeetle, our compiler, should not be vibed out beneath us. Standing up to “trillion dollar” big corp… Zig is hard core quality. tigerbeetle.com/blog/2025-10…
25
39
8
482
98,707
Cool video. Postgres handles large scans differently -- and I find this one equally clever. Its shared_buffers cache uses clock-sweep instead of an LRU list. Each buffer slot has a usage_count. When Postgres needs a victim, it sweeps through slots until it finds one: • pinned: skip • usage_count > 0: decrement and skip • usage_count == 0 and unpinned: victim found For large scans (table blocks > 1/4 of shared_buffers slots), Postgres allocates a small ring buffer -- just 256KB, roughly 32 slots for 8KB pages. The scan fills victims into this ring, then cycles back to reuse them. If a buffer in the ring has become hot (pinned or usage_count > 1), clock-sweep finds a replacement from the main pool instead. Net effect: a full table scan mostly churns through a tiny ring, typically around 256KB, instead of blowing out the whole shared_buffers cache. Simple idea, surprisingly effective.
Table pages are cached, and this is how databases get a performance boost. But, a single full table scan can destroy such cache because, but here's how MySQL cleverly prevents this... When a full table scan happens, it can flush out all valuable cached pages and replace them with pages that are often never needed again. Given that every single page that is accessed is put into the cache. This 'cache pollution' forces subsequent queries to hit slow disk storage. MySQL solves this with the particularly simple yet clever midpoint insertion strategy, and I have explained this approach in my video. Give it a watch. Hope this helps.
4
301
Something is seriously wrong with how people contribute to open source these days. People submit AI-generated code they clearly don't understand. When reviewers comment, they feed it back to the AI and submit again. The loop never ends. Low-effort PR requires extremely high effort to review. That math is never fair. You think this is how you build a reputation in open source? No, no, and no. You're just burning out maintainers. --- Use AI to understand the codebase instead. Ask it to explain things. Build your own judgment. It's ok to use AI as a tool for coding, but make sure you filter out any AI slop before submitting. You gain the skills. You gain the reputation. The project actually benefits. Is it that hard?
3
2
19
1,446
If you're using Go for years and feel stuck at intermediate level, I know the feeling. It's not you, it's your project. CRUD + business logic + REST APIs - no matter how big the codebase, this stack won't teach you advanced Go. But these projects will: • VictoriaMetrics / VictoriaLogs (custom storage engine, block compression, zero-alloc hot paths) • etcd (Raft consensus, WAL durability, election timeouts) • containerd (OCI runtime stack, cgroups, plugin architecture) • Prometheus (TSDB, memory-mapped chunks, query engine) There are many more. Pick one, read the code, send a PR. These projects can't afford slow code, so you'll learn escape analysis, zero-alloc patterns, and real concurrency whether you plan to or not.
1
9
272
Cuong Le retweeted
FFmpeg is moving to Rust 🦀 Our use of C and Assembly in FFmpeg has been an unacceptable violation of safety. FFmpeg will be running 10x slower - but we're doing it for your safety. All your videos will appear green - safety first, working software later.
1,496
3,458
989
43,041
2,024,252
Contributing to Rust has been a dream of mine for a long time. Last December, I finally did it. A small but real contribution to Rust Clippy. Here's what I learned that no tutorial will teach you. 1. "It works" ≠ "it belongs here" My code had zero logic errors from the first submission. Tests passed. The lint fired correctly. It still took multiple review rounds before it was accepted. Each round, the reviewer flagged something I hadn't considered. A different API, a different factoring, a different order of checks. None were correctness issues. All were belonging issues. Mature codebases have a shape. A vocabulary. Conventions no doc can fully capture. And when that codebase operates on AST and HIR, the learning curve is steeper than most. Getting your code to work is step one. Getting it to fit is step two. Only the first is obvious to newcomers. 2. Performance is a first-class design constraint I spent years building web and backend systems where readability almost always beats micro-optimization. Clippy changed that. The reviewer pushed back on how I structured my checks. His version was slightly harder to read, but it was faster. I had optimized for clarity. He optimized for performance. Both were valid, but only one was right for Clippy. The reasoning: this code runs on every file, every build, for every Rust developer in the world. Nanoseconds x millions of daily builds = something real. Readability-first is a fine default. But in performance-critical code like compilers, runtimes, and toolchains, scale flips the equation. Know which world you're writing for. 3. Don't let ego get in the way of the reviewer I was surprised by how many specific changes were requested. It felt strict at first. Then I paused and thought about it. Every request was reasonable. The reviewer has lived with this codebase. He understands every design decision and genuinely cares about keeping it in good shape. His knowledge was the most valuable resource I had access to, and I almost let pride get in the way of using it. In a review from someone who knows the codebase far better than you: every comment is a gift. Receive it that way. The flip side: if you're ever reviewing a first contribution, that same dynamic works in reverse. Your patience, or lack of it, is the whole experience for them. 4. A good test suite makes new contributors brave My biggest fear as a first-timer: breaking something silently. Clippy's golden-file tests killed that fear. The exact expected output is checked in alongside the code. Shift a diagnostic message even slightly, the test fails and shows you exactly what changed. Clippy even runs its own lints on itself. Knowing the safety net was that solid made me willing to experiment instead of playing it safe. A comprehensive test suite isn't just quality assurance. For anyone contributing to an open source project, it's a confidence multiplier. The difference between experimenting freely and playing it safe. --- That was my first time contributing to a real, large-scale open source project. Small contribution. Big lessons. More to come as I go deeper.
2
22
1,341
Rust Beats C++ by a mile security.googleblog.com/2025… The numbers are crazy IMO. • Rust: ~0.2 memory bugs per million lines, C/C++: ~1000 per million • Rust code reviews move ~25% faster • ~20% fewer review revisions • Rust changes get reverted far less often, roughly 4x better Google is pushing Rust into more of Android: drivers, firmware, the whole stack
1
1
4
313
Couldn't agree more with Jon Gjengset in this interview: youtube.com/watch?v=nOSxuaDg… Rust adoption isn't held back by the learning curve. It's the switching costs of existing codebases. Honestly, learning to write safe concurrent C++ is even harder.
4
466