Pingora, the proxy that connects Cloudflare to the Internet
blog.cloudflare.com
blog.cloudflare.com
> In fact, Pingora crashes are so rare we usually find unrelated issues when we do encounter one. Recently we discovered a kernel bug soon after our service started crashing. We've also discovered hardware issues on a few machines, in the past ruling out rare memory bugs caused by our software even after significant debugging was nearly impossible.
That's quite the endorsement of Rust. A lot of people focus on the fact that Rust can't absolutely guarantee freedom from crashes and memory safety issues. Which I think misses the point that this kind of experience of running high traffic Rust services in production for months almost without a single issue is common in practice.
1. Only making valid states possible, to the greatest extent reasonably possible.
2. Treating error as regular data, not an afterthought - with language features to make that not too painful.
3. Not returning placeholder values (i.e. if you get back a parsed value from a parse function, it means it parsed correctly, not that either there's an error somewhere else or it parsed correctly).
Language features, in particular "enums" (aka algebraic data types, aka tagged unions), make this approach possible. You couldn't do it in go, for instance, even if there was a cultural decision to.
A type system that can express thread safety (Send/Sync traits) is incredibly valuable when building multi-threaded systems.
Universal definition of what is safe, and standard traits and borrowing rules, make APIs more predictable. Just from function's signature you know a lot about its behavior, without having to look for gotchas in the manual.
Mandatory error handling prevents cutting corners. Unit testing is built-in.
Generics, good inlining, and Cargo help split code into libraries without a performance or usability hit, which helps make focused, well-tested components.
Most of these things aren't groundbreaking, but Rust being new had a luxury of picking current best practices and sensible defaults.
- I think memory safety is a baseline. You'll note that memory safe languages already tend to be much more reliable than non-memory-safe languages in general.
- Then you have the error handling. A lot of unreliability in my code in other languages comes from unhandled exceptions that only occur rarely. Rust generally puts all possible error conditions in the type signature of the function. Meaning it's actually feasible to handle every failure case.
- Speaking of unhandled exceptions, a lot of those in typed languages tend to be caused by null. Rust does not have null. Instead it has Option, and it is impossible to access the contents of an option without doing the equivalent of a null check. So that entire class of errors is gone.
- Both Result (used for error handling) and Option (used instead of null) are what Rust calls enums, and what are more generally called Sum Types. I think these are a huge deal. They allow you to safely represent data that may be one thing or another with very strict type checking. These are broadly very useful in API design, and in my experience lead to much more robust code than the class hierarchies you need in OOP languages or unions which lack the safety checks. (Aside: sum types would be quite simple to add to other languages. I have no idea why they haven't been added yet).
- Speaking of classes, inheritance is not supported. So that's a bunch of confusing code that just isn't possible to write. This can add a bit of boilerplate to Rust code, but it makes it more straightforward and less bug prone.
- You mentioned the borrow checker. That definitely helps. It's yet another tool that allows you to write APIs that cannot be misused. A great example would be Rust's Mutex type. It can prove at compile time that code does not hold on to references to the protected data beyond the duration that the lock is held.
- Speaking of Mutex. Rust's Send and Sync traits provide very good thread safety. You almost don't need to worry about thread safety at all in Rust. Most concurrency bugs are prevented by the compiler (you can still do things like cause data races).
- Newtypes allow you to check invariants once and then have the fact that they remain satisfied enforced by the type system.
- All type casts are explicit.
- Lots of other little things
One final thing that I think is often overlooked. Rust is strict, and all of these checks apply not only to the code your write, but to all of your dependencies. That means that Rust libraries tend to be much more reliable than libraries from other ecosystems. That probably is partly because of a culture of reliability. But it's also because the language itself makes it hard to write sloppy code. And that the code you are building on is likely to be reliable makes it both less effort and more worthwhile to make your own code reliable (including for library authors), leading to virtuous circle of reliable code.
This means you get complexity, especially on the "initial phases".
* The initial learning curve is steeper than usual . You have x types of string instead of one or two, you have lifetimes, etc, etc. Fortunately you pay it once and then can use it many times (maybe with some revisits to the docs :P)
* The initial building of a feature might take a bit longer than in other less complex languages (the borrow checker gives you a particularly tricky error, etc). But what is happening here is that the language is picking up problems that other languages ... don't. They "defer to production".
Others are mentioning that cargo is another feature that helps. I think it does, but indirectly. What cargo does is improving the developer experience. Having all the packages at the tip of your fingers, without needing to jump over hoops like in other languages is just ... nice. Given the initial ramp up in complexity, and the initial harder-than-usual first write, anything that improves the UX goes towards alleviating that. I personally would put cargo on the same bucket as "helpful compiler messages" and "good docs".
> You have x types of string instead of one or two
Funny how you said X, while there are really only 3 types of strings in standard library. You generally only encounter 2 of them: String and OsString. OsString, while annoying, but it's one of the things that makes rust safe. You only ever deal with 3rd type, CString, when you work with FFI, at which point you better already know how strings work on C.
So all in all, about a dozen kinds of strings.
I feel bad for them. I wanted to fix something in NeoVim's built-in lsp recently and lost all interest because of all the things you have to do to:
- Get parent directory for a path - Create that directory if it exists
Why was I annoyed by that? Well, different OSs use different path separators, in rust this is trivial because `Path` and `PathBuf` are target-specific.
That's not a type of string though, it's a path, the fact that it uses `OsString` underneath is irrelevant.
> Vec<u8> or Vec<Char>
There isn't really anything special to learn about it them, though? I doubt newbie encounter this while learning. Slices are the same thing, you just can't extend it, what is there to learn?
This is my experience (albeit at smaller scale) exactly. If this wasn't the case, Rust would be snake oil and I'd be the first to admit it. Until my most recent endeavor with a rust backend, among other things, I simply didn't know it was possible to not have to debug crashes. Seriously. The pleasure of maintaining rust software in production is so wildly different from anything I've ever experienced in the past that I gladly submit to the compiler over and over and over again. It's worth the investment 100 fold.
0. https://dropbox.tech/infrastructure/how-we-migrated-dropbox-...
1. https://news.ycombinator.com/item?id=32572153
2. https://twitter.com/eastdakota/status/1024515150546493440
Some of the OSS papercuts with nginx:
- nginx has always used a "submit a patch to a mailing list" style of contributions. Many contributions, my own attempt a decade ago, just get ghosted: https://mailman.nginx.org/pipermail/nginx-devel/2010-Decembe...
- Neither the contributing page (http://nginx.org/en/docs/contributing_changes.html) nor the Mercurial repo (http://hg.nginx.org/) redirect to HTTPS!
- Tests were a later addition and in a distinct repo with a bespoke harness. I'm sure it has advantages, but it also takes extra work for contributors to figure out.
- They use Trac?! I loved Trac circa 2008 but had no idea it was still a thing. I can't even login to it without it timing out.
I don't want to nitpick an excellent project like nginx, but I think it's clear that easing third party contributions has never been a high priority.
Oh wow! I totally forgot about Trac. That was the first ticket management software I used and I completely forgot about it's existence. Thanks for the impromptu trip down memory lane.
I think we can scale our setup up to 200 Gbit/s but we are too small. Total traffic is ~2 Tbit/s.
Most challenging is the missing support of QUIC/http3 and KTLS in Golang. Also 100G NIC supply chain is difficult. We use NVIDIA Connect-X 6, but it's impossible to get a version with TLS offloading.
I think it starts with a Wa ... you don’t have to say. I kind of remember to have been stumbled on a Twitter engineering ipv6 tweet. Maybe I m wrong.
For me it’s impressive to get so much data through a computer. But I have one question, what does count as a node, is a node like 1 machine with dual sockets, a lot of ram and a lot of nics or is it like multiple machines combined that act as 1 node like a whole 19 inch rack.
Edit: or linear TV means the recent stream can be all in ram?
[1]: https://github.com/lucas-clemente/quic-go [2]: https://github.com/lucas-clemente/quic-go/issues/2877 [3]: https://github.com/lucas-clemente/quic-go/issues/2607 [4]: https://github.com/lucas-clemente/quic-go/issues/341 [5]: https://github.com/caddyserver/caddy/pull/4707
I think I read two weeks ago what F5 was going to focus more on improving the open source version. Probably because the competition is getting harder and they're noticing it in a market share decline, but whichever the reason is, this was good to hear.
Also it was good to see it no longer being part of a Russian company, even though the devs and owners are good people. You never know how a government can enforce some problematic behavior, specially one which is known for liking to throw people out of high rise windows.
Everything else is...hard. Writing plugins - arcane knowledge with next no none documentation. 3rd party plugins, multiple times "acquired" (read: paid the author to work on a new plugin) an open-source plugin and made a much better version available in this plus offering.
gRPC support added only in 2018, http2 support was late, proper web sockets support was late.
Many features that were available "for free" with OpenResty were only on the paid version - good luck writing your own plugin.
They were doomed before F5 probably around round time when they raised series B1 in 2014. All this money and it still had nothing "new" to show. For every Plus plugin, there was most likely a better solution available, and often enough that solution was free or cheaper.
Almost nothing has changed since F5 aquired them.
It'd be interesting to see another Cloudflare blog post that just goes into detail on the weird protocol behaviour they've had to work around over the years. I imagine they have more insight into this than pretty much any other organisation on the planet.
Any cloudflarer involved in this project mind sharing some basic metrics like LOCs, team size, how long from design to first deployment.
Just curious.
If you have the time for enhancement, then:
1. Option to hit the cache before workers. (Why we never use workers).
2. Rules for blocking traffic during nights (time-based rules).
3. Make sure every product is a replacement. If you offer the same thing as a cloud provider. Don’t make us write a lot of custom code.
It's not unusual for e.g. ecommerce sites to crank up automated fraud prevention "at night" because staffing is so much lower.
TL;DR Most sites' usage patterns exhibit a pronounced day/night cycle that's not too far off from natural day/night cycles where the bulk of the team lives.
(it's getting rarer, but it does still happen. Fraud prevention cranked up is definitely a thing on any large enough ecommerce website)
While I agree that in selecting a proxy to use whether it is open source is one of the most important considerations, if that's all you look for in this article, you may be missing something.
I thought the article did a good job of describing how they went about making the choice of whether to continue contributing to an existing (nominally open source) system or to build a new one. And of course, it did a good job of showcasing the strengths of Rust (reliability guarantees strong enough that they could identify when problems were due to hardware.)
* They already have some pretty deep Rust experience on staff
* They were already dissatisfied with the performance penalty from Lua's GC, so Go's GC was presumably unattractive as well
* Rust is worth more internet points than Go (just kidding, mostly)
In several major areas, I actually like the available Rust crates for a given task more than I like the available npm modules. Rust has many fewer third-party libraries available, but the quality is often good.
There is a more-than-6-year-old proposal to introduce non-blocking I/O API (https://github.com/golang/go/issues/15735) but so far it's not gaining much traction. Maybe in Go2.
async fn
and .await();
in the source code. What did you want to know? .await();
Hmm does the code compile at all?The main issue for me with Rust is that it takes significantly more resources (time, space, memory, CPU) to build projects from source. Building Haproxy is comparatively quick and easy.
The haproxy plus lua static binary (musl, no pcre) I use is already growing rather large. I will bet that Pingora binaries using shared libraries will be at least twice, maybe three times the size.
I would have liked to have seen more explanation of why they decided to build their own rather than use haproxy though.
I don't remember the exact specifics off the top of my head, but in one case a certain kind of invalid config resulted in a crash rather than a normal error message. In another, there was an error in a bounds check, where if you chained multiple converters in the right way, you could end up capturing some additional data from other http headers.
Build time can be annoying, absolutely, but that's the tradeoff of Rust -- compiler does a lot of work so the program can be beastly fast and very reliable at run time.
I don't like big binaries either, mind you, but I view this as a very minor concern for a mission-critical piece of software.
Choosing speed and correctness at run time is what I would have done as well.
In the end, you end up with a binary that is statically linked, except for `glibc` (unless you use libc like musl) and whatever C library you choose to dynamically link against.
Since this is an in-house project, they can build their packages with whatever toolchain they choose and distribute packages for deployment. So whatever toolchain provided by distro is irrelevant unless this packages is part of the distro's official package repository (which it isn't in this case).
[1] https://blog.cloudflare.com/workers-open-source-announcement...
What a surprise, replacing an interpred dynamic language with a AOT compiled static language leads to performance improvements.
I guess the learnings using Tcl as configuration language for Apache based proxies 20 years ago was lost in newer generations.
In this case, where the code is coming from Cloudflare, you can be sure that they know what they're doing, that there is a high chance that it will be very high quality code.
Apart from this, it's an interesting article from a technical perspective due to the various topics it touches.
Also, in my case, where I'm looking for an new language to program in, where Rust and Go are the my main options, articles like these help me with the decision process on which one to choose.
There's much more value to this article than it just being an announcement that sometime in the near future they will be releasing an open source project.
It almost sounded like a "give me the code or shut up"-message.