I talk about these problems in the "How hard can sharding be?" section of the article — long story short, not all business requirements can be dealt with easily, but surprisingly many can if you choose a smart sharding key.
You can also still do optimistic concurrency across shards! That covers most of the remaining ones. Anything that requires anything more complex — sagas, 2PC, etc. — is relatively rare, and at scale, a traditional SQL OLTP will also struggle with those.
- The transactions that only touch one shard is simple
- The transactions that read multiple shards but only write shard can use simple optimistic concurrency control
- The transactions that writes (and reads) multiple shards stay complex. Can be avoided by designing a smart sharding key. (hard to do if business requirement is complex)
If you anticipate you will encounter the third type a lot, and you don't anticipate that you will need to shard either way, what I'm talking about here makes no sense for you.
It's a different kind of complexity. Essentially, your app layer needs shift from:
- transaction serializability
- atomicity
- deadlocks (generally locks)
- occ (unless you do VERY long tx, like a user checkout flow)
- retries
- scale, infrastructure, parameter tuning
towards thinking about
- separating data into shards
- sharding keys
- cross-shard transactions
which can be sometimes easier, sometimes harder. I think there are a surprising amount of problems where it's much easier to think about sharding than about race conditions!
> But with B2B, we have accounts ranging from 100 users per organization to 200k users per organization.
You'd be surprised at how much traffic a single core (or machine) can handle — 200k users is absolutely within reach. At some point you'll need even more granular sharding (eg. per user within organization), but at that point, you would need sharding anyways (no matter your DB).
If you have to think about cross-shard transactions then you have to think about all the things on your first list too, as they are complexities related to transaction. I fail to see how that could possibly be simpler.
Cross-shard transactions are only a tiny fraction of transactions — if the complexities of dealing with that is constrained to some transactions instead of all of them, you're saving yourself a lot of headaches.
Actually, I'd argue a lot of apps can do entirely without cross-shard transactions! (eg. sharding by B2B orgs)
> Rust is is not a "silver bullet" that can solve all security problems, but it sure helps out a lot and will cut out huge swatches of Linux kernel vulnerabilities as it gets used more widely in our codebase.
> That being said, we just assigned our first CVE for some Rust code in the kernel: https://lore.kernel.org/all/2025121614-CVE-2025-68260-558d@gregkh/ where the offending issue just causes a crash, not the ability to take advantage of the memory corruption, a much better thing overall.
> Note the other 159 kernel CVEs issued today for fixes in the C portion of the codebase, so as always, everyone should be upgrading to newer kernels to remain secure overall.
> > That being said, we just assigned our first CVE for some Rust code in the kernel: https://lore.kernel.org/all/2025121614-CVE-2025-68260-558d@g... where the offending issue just causes a crash, not the ability to take advantage of the memory corruption, a much better thing overall.
That indicates that Greg Koah-Hartman has a very poor understanding of Rust and the _unsafe_ keyword. The bug can, in fact, exhibit undefined behavior and memory corruption.
His lack of understanding is unfortunate, to put it very mildly.
What are some compiler flags that would compile the code such that an attacker could take advantage? And what would the attack be?
Or is this just a theoretical argument, "it is hypothetically possible to create a technically-spec-compliant Rust compiler that would compile this into dangerous machine code"? If so it should still be fixed of course, but if I'm patching my Linux kernel I'd rather know what the practical impact is.
To play a bit of devil's advocate, I don't think the problem is necessarily with the compiler output. It's more that it's not always easy to definitively state the precise consequences of a particular issue, especially when it comes to memory safety-/UB-related issues. For example, consider this Project Zero writeup about using a single NUL byte buffer overflow as part of a root privilege exploit [0] despite some skepticism about whether that overflow was actually exploitable.
To be fair, I'm not saying that Greg KH is definitely wrong; I'm only willing to claim that in the general case observing crashes due to corrupted pointers does not necessarily mean that there's no ability to actually exploit said corruption. Actual exploitability will depend on other factors as well, and I'm far from knowledgeable enough to say anything on the matter.
That's not how it works. A larger codebase to scrutinize means that there's more chance of missing a memory safety bug. If you can keep the Rust unsafe block bug-free, you don't need to worry about them anymore in safe Rust. They're talking about attention getting divided all over the code where this distinction is not there (like C code). They always have been.
On top of that, there is something else they say. You have to uphold the invariants inside the unsafe blocks. Rust for Linux documents these invariants as well. The invariant was wrong in this case. The reason I mention this is because this practice has forced even C developers to rethink and improve their code.
Rust specifies very clearly what sort of error it eliminates and where it does that. It reduces the surface area of memory safety bugs to unsafe blocks, and gives you clear guidelines on what you need to ensure manually within the unsafe block to avoid any memory safety bugs. And even when you make a human error in that task, Rust makes it easy to identify them.
There are clear advantages here in terms of the effort required to prevent memory safety bugs, and in making your responsibilities explicit. This has been their claim consistently. Yet, I find that these have to be repeated in every discussion about Rust. It feels like some critics don't care about these arguments at all.
To be honest, I find Ryan Cavanaugh's argument against this quite convincing. It's weird to have something documented if you import the .ts file, but not if you import a .d.ts generated from it. If you want to show the value of the default argument of a function, you should probably just add it to the doc comment — not the type.
reply