Quick one. I had a process that would behave for hours, then balloon and drag a box to its knees. The systemd unit had MemoryLimit= set, so in theory it should have been capped. In practice it kept blowing past, because the limit was on the memory controller but the thing was choking on page cache and dirty writeback that nobody was accounting against it.
Under cgroups v1 the controllers live in separate hierarchies. Memory here, CPU there, IO somewhere else, and they don't have to agree about which cgroup a task is even in. You can pin memory and still watch a process strangle the disk, because the IO controller never got the memo. It's not broken exactly, it's just a collection of independent knobs pretending to be a system.
cgroups v2 puts everything in one unified hierarchy. One tree, every controller agreeing on membership, and crucially memory.high and the IO controller working together instead of past each other. I moved the unit over (MemoryHigh= plus IOWeight=, with systemd on the unified hierarchy), and the runaway process now gets throttled smoothly under memory pressure instead of being OOM-killed or taking the box with it.
systemctl show myservice -p MemoryHigh -p IOWeight
The lesson isn't "v2 is magic". It's that v1's split-brain controllers let a problem hide in the gap between them. Once one tree owned the whole picture, the misbehaviour had nowhere left to go.