The Container Throttling Problem
danluu.com
danluu.com
This point seems wrong to me, bound too much by requiring solutions to be done by a small team of engineers who already have a mandate to work on the problem.
With numbers like that Twitter could, profitably, hire dozens of engineers that do literally nothing else. Just tweak thread pool sizes all day, every day, for service after service. Even though it's a boring, manual, "noncomplex" thing, this type of work is clearly valuable and should have happened years ago.
Most likely Twitter's job ladder, promotion process, and hiring pipeline is highly incentivizing people to avoid such work even when it has clear impact. They are very much not alone in that regard.
You are correct in that most companies are not equipped to staff issues like this. Most places just accept their bills as a cost of doing business, not something that can be optimized.
In reality, you want both. A good systems person can save you a ton of money.
Seriously, some employers I’ve know seem to take exactly that approach. Just fire all the ops guys, and tell the devs that they’re now going to be doing DevOps.
Result is that maximising income is generally better than reducing cost. Of course, as with all generalisation, there are situations where this approach doesn’t hold true. But as a high level first order strategy, it’s a good one to adopt.
Thats plain wrong, the gloval market for cars, bicycles and what have you has a limited size. Every large company that's a market leader understands that.
If you do the math, then increasing your revenue by 2x doesn’t cost 2x the effort. But one additional percentage point of cost reduction might well cost 2x the effort.
Sadly, most employers I’ve known do not seem to understand this thing called math.
Instead, when something has a payoff of 3 years, executives get antsy in orgs that have a 2-year cycle on exec positions.
In my team, the key is for the state of the project/product we manage, cost optimization is likely one of the lowest ROI activities we can spend too much time on. That doesn't mean we don't tackle some clear low hanging fruit when we see it, or use low hanging fruit as training opportunities to onboard new team members, but that we need to be conscious on where we make investments, and for the stage we're at, the more important investment is into areas that make our product more appealing to more customers.
I think it's easy to say someone, like an intern could pay for themselves with savings. But this to me overlooks that someone has to manage that intern, get them into change management, review the work, investigate mistakes or interruptions from the changes, etc. And then they're still the lowest earning employee, since most of us aren't hired to pay for ourselves, but actually to turn a profit for the company.
So while I'm not sure I agree with the message "whereas increases in income are larger and compound better.", I certainly understand and have pushed a similar message, that we be conscious on where we're spending our time, and that we're selecting the highest impact activities based on the resources and team we have. Sometimes that may be fixing high wastage, but very frequently that will be investing into the product. And I think for the stage of the product we manage, that is the best choice for us.
The issue is that once you hire a dozen engineers to do this (say for 5M a year in total), and they do it for a year, they save mid 8 figures (keep in mind this was the largest service, so the savings across other services will be smaller).
Then can they keep saving mid 8 figures every year?
I'll paraphrase something I previously wrote privately, but imagine you have some team that's able to save 10% of your fleetwide resources this year. They densify and optimize and improve defaults. So you can now grow your services by 10% without any additional cost increase, and you do! The next year, they've already saved the easiest 10%. Can they save 10% again? Can they keep it up every year? How long until they're saving 3% a year, or 1% a year? And that's if you keep the team the same size, where its clearly loosing money! If you could afford a dozen people to save 10%, you can only really afford 1-2 to save 1%, but then you're likely to get an even smaller return.
Unless you expect to be able to maintain the same relative value of optimizations every year, 3 or 5 years out, its not worth it to hire an FTE to work on them.
I should note that I've experienced this myself: I was working in an area where resource optimization could lead to "significant" savings (not 8 figures, but 6 or maybe 7). My first 6 months working in this area, I found all sorts of low hanging fruit and saved a fair amount. The second six months, 5-10x less ROI. I gave up even trying in the third six moths, if I come across a thing, I'll fix it, but its no longer worthwhile to look.
If you're looking across the business as a whole, it seems likely that there is a lot of this kind of work lying around because there is not much incentive for people to tackle it as described in this comment: https://news.ycombinator.com/item?id=29691847.
The issue is still the same: what do you do in the second year (or the third), after you've fixed all the low hanging fruit. If it takes a constant amount of time to investigate a particular service, it's not even worth examining the long tail, because the investigation is costlier than the savings. So once you've saved the high 8 figures by fixing the low hanging fruit in the biggest 20 services or whatever, what next?
The broader point is that while it is often very worthwhile to have individual employees work on optimizations, it is much less often worthwhile to task teams entirely with optimizations, especially in the way you describe. Having some group in charge of generalizable fleetwide optimizations is possibly useful. Doing some kind of resource quota thing where you get fewer resources than you're forecast to need (to force you to do some kind of local optimization) may make sense, but having a strike team whose job it is to tune JVM parameters isn't useful. A central team writing a document on best practices, setting better defaults, or using/building some kind of grid-search optimizer are all probably better investments.
Good large engineering organizations have these teams, and understand their value. For smaller companies, it's not as clear if you can reasonably fund a team for that kind of work.
Then you destaff the team, collect your tens of millions of dollars, and be happy about a successfully finished project? The problem with going too far down this line of reasoning is that you leave even low-hanging fruit laying around forever.
Like I've said twice now, you can solve this problem without stupid business practices, and the you're arguing with a straw man. If you have to say "going too far down this line", you're no longer responding to the argument presented, but a bad faith misrepresentation. That's inappropriate on HN.
In case anyone missed it, the removal of throttling in certain circumstances saved twitter ~$5mm/year, if I read it correctly. With a naive kernel patch. While it takes dedicated engineers decades of knowledge to know where to aim an intern, an intern still banged out a kernel scheduling patch that made, what I assume, is a huge difference.
Dan Luu is a gem.
[1]: https://netflixtechblog.com/predictive-cpu-isolation-of-cont...
[p.s. thanks for the replies]
In short, an offset from the base process priority.
We had two lumps of compute:
1) huge render farm, at the time it was 36k CPU. The driving goal was 100% utilisation. it was a shared resource, and when someone wasn't using their share it was aggressively loaned out. (both CPU and licenses) Latency wasnt an issue
2) much smaller VM fleet. Latency was an issue. Even though the contention was much less, as was the number utilisation.
Number two was the biggest issue. We had a number of processes that needed 100% of one CPU all the time, and they were stuttering. Even though the VM thought they were getting 100% of a core, they were in practice getting ~50% according to the hyperviser. (this was a 24 core box, with only one CPU heavy process)
After much graphing, it turned out that it was because we had too many VMs on a machine defined with 4-8CPU. Because the hypervisor won't allocate only 2 CPUs to a 4 CPU VM, there was lots of spin locking waiting for space to schedule the VM. This meant that even though the VMs thought they were getting 100% cpu, the host was actually giving the VM 25%
The solution was to have more smaller machines. The more threads you ask for to be scheduled at the same time reduces the ability to share.
We didn't see this on the big farm, because the only thing we constrained was memory, The orchestrator would make sure that a thing configured for 4 threads was put in a 4 thread slot, but we would configure each machine to have 125% CPU allocated to it.
The way we solved this? 1. Kernel settings. Check e.g. the settings of the Ubuntu low latency kernel for example. 2. CFS tuning. Short timeslices. There are good documentations on how to do that 3. CPU pressure. We cordoned and load shedded overloaded nodes (k8s-pressurecooker).
By limiting the maximum CPU pressure to 20% you can say "every service will get all the CPU it needs at least 80% of the time on most nodes". This is what you want. A low chance of seeing CPU exhaustion. This is needed for predictable and stable tail latencies.
There are a few more knobs. E.g. scale services such that you use at least one core as requests are effectively limits under congestions and you can't get half a core continuously.
Very nice to see that people go public about this. We need to drop the footprint of services. It is straight up wasted money and CO2.
The thread pool size negotiation seems a necessary fix - applications shouldn't be pre calculating their pool sizes on their own anyway. But you get additional (smaller) problems, like giving more or less threads to some service depending on their priority.
One of the big problems here as I understand it is trying to use a resource whose "size" changes dynamically (Max CPU usage on a cgroup, which can change depending on whether other prioritised service is currently running or not) with a fixed sized resource (nr of threads when a service starts).
As the number of cores per CPU grows, I wonder if this whole approach of scheduling tasks based on their CPU "usage" makes any sense. At some point, the basic scheduling unit should be one core, and tasks should be assigned a number of core units on the system for a given time.
Then, let a human go in and say "How come request#77 hasn't yet been processed at this point, even though CPU#3 is working on printing unused debug data for a low priority request and #77 is well after its deadline!??".
Then you debug deeper and deeper, adjusting parameters and patching algorithms till you can get a CPU trace that a human can look at and think "yeah, I couldn't adjust this schedule by hand to get this work done better".
In this process, most people/teams will find at least 10x performance gains if they've never done it before, and usually still 2x if you limit changes to one layer of the stack ('eg. Im just tweaking the application code - we won't touch the runtime, VM, OS or hypervisor parameters').
A CPU scheduling trace wouldn't easily show you the details of the kernel-level group throttling that was causing a lot of issues, for example. They weren't having an issue with threads fighting other threads, they were having an issue with threads being penalised now for activity from several seconds ago, drastically reducing the amount of available CPU.
The article clearly shows a lot of debugging and diagnostic patching ability, so it's unlikely they missed the simple options. Rather, they probably didn't mention them because they were obvious to try and didn't help.
Exactly... They would have found this out much quicker with a trace. They would have seen "how come this application level request is being handled on thread number X, yet that thread is not running on any core, and many cores are idle"? Then quickly they could see the reason that thread isn't scheduled by enabling extra tracing detail seeing the internal data structures used by the scheduler to see why something is schedulable or not at that instant.
Your original message would probably be better received if you'd omitted the "I think this problem would have been debugged and solved much quicker [...]" and its insulting implications and instead started with "Sometimes, I find that CPU activity traces can really help with diagnosing this sort of problem".
In a big company, getting the person with the most skills to solve a problem to be the one actually tasked with solving the problem is very hard. This particular problem had many avenues to find a solution - and while I think my proposed route would have been quicker, if you aren't aware of those tools or techniques, then other avenues might be much quicker. When starting an investigation like this, you don't know where you're going to end up either - if it turned out that the performance cliff was caused by CPU thermal throttling, it would be hard to see in a scheduling trace - everything would just seem universally slow all of a sudden.
If you gave this service a dedicated, non-co-located fleet, running the JVM directly on the OS, and ran basic autoscaling of the number of hosts, you'd eliminate a huge number of the moving parts of the system that are causing these issues.
Yes, that would add to ops costs (edit: human ops costs) for this service, but when you're spending 8 figures per year in it, clearly the budget is available.
To quote the great philosopher Avril Lavigne: "Why'd you have to go and make things so complicated?"
Wouldn't fewer moving parts mean lower operational costs?
Telling someone whose car is making some funny noises that it's simpler to go back to horse-and-buggy times would both increase costs and decrease the number of user-servicable moving parts. There's some significant overhead attached.
Should you wish to really stretch that car analogy, maybe a bit more appropriate than a horse would be: If you aren't happy with your travel agency aren't booking your taxi trips in time, try booking with the taxi company directly.
It would lower the operations costs of hardware, hopefully (that's the entire goal of this article) but you'd need more people resources to manage it, I would guess. Mesos and containers automate a lot of thinking work.
That seems significantly more expensive at scale.
Or if you've broken things up into small pieces that aren't big enough to use a whole server, that can feel inefficient as well.
https://dzone.com/articles/what-can-we-learn-from-twitters-m...
https://kubernetes.io/docs/tasks/administer-cluster/cpu-mana...
This is at the kernel level, opposed to paravirtualization. And I guess this is Twitter’s use case, but should not be confused by the typical vCPUs offers one sees in most cloud providers, which is usually done through hypervisors such as Qeme/KVM, VMware, or Xen.
I’m not sure why Mesos (maybe this one tried and didn’t succeed), nor K8S (available through external Intel code) or even Docker, never really thought about that, but I guess they want to keep their internal (operational) overheads up to a limit, and possibly also to maintain the metastability of their services [1]. But now we see where it leads to, with all these redacted numbers in the article.
[1] https://sigops.org/s/conferences/hotos/2021/papers/hotos21-s...
Ps: edits for clarifications.
The graphs seems to validate my general assumption that large-load tasks just suck at scaling whereas small-load tasks can be horizontally scaled easier without falling over. The general assumption being that for most applications, if you ignore everything else about an operation and assume a somewhat random distribution of load, smaller-load services use up more available resources on average than a single large-load service. That's just been an assumption in my head, I can't remember any data to back that up.
Back in the day when I worked on a large-traffic internet site, we tried jiggering with the scheduler and other kernel tweaks, and in the end we literally just routed certain kinds of requests to certain machines and said "these requests need loads of cache" (memcache, local disk, nfs cache, etc) and "these requests need fast io" and "these requests need a ton of cpu". It was "dumb" engineering but it worked out.
It feels like you can eliminate most of this problem in other languages by using a much smaller pool and then leveraging userland concurrency/ scheduling. You probably don't want to have N cores and N + K threads, but in some languages you don't have much choice. Java has options for userland concurrency but they're pretty ugly and I don't think you'll find a lot of integration.
Containers make this a bit harder, and the Linux kernel sounds like it had a pretty silly default behavior, but how much of this is also just Java?
One could imagine a vm or runtime that is async and multiprocess that also enforces quotas on cycles and heap such that these types of “noisy neighbor” events aren’t a problem.
In this direction there have been solutions that haven’t caught on; a multi tenant jvm existed at one time, and at least one js implementation has this ability. I’ve often thought Lua would be ideal for this.
But it also looks like the GC was a major contributor, so that would not be as influenced by the differences between dotnet and Java.
That'd be the wrong thing to pick as the proxy destination. Go is also a “language runtime designed to optimize multi processor machines” and, as the article explains, doesn't trigger this the same way.
Try something like "total number of threads in all the pools used by the application overwhelms the CPU".
% numactl -C 0,5 ./ssp 12 elapsed time: 99943 ms
cpu.cfs_quota_us = 200000 cpu.cfs_period_us = 100000 % cgexec -g cpu:cgtestq ./ssp 12 elapsed time: 420888 ms
cpu.cfs_quota_us = 2000 cpu.cfs_period_us = 1000 % cgexec -g cpu:cgtestqx ./ssp 12 elapsed time: 168104 ms
Also interesting was in our app some RR thread priorities are used, and those do not get controlled via the cgroup cpu.cfs settings.
Operating Systems: Three Easy Pieces.
Most important parts of my undergrad. Much more so than Algorithms or a anything mathematical.
Wonder what mechanism could be used to communicate the available timeslice length so that the app/thread could stop taking on a request when throttling is imminent.
From the bit I had patience to read it sounds like "we made a complicated thing and it's doing complicated things wrong in complicated ways".
It is hard to believe that some of these CPU heavy, latency sensitive servers should really be in containers. Why are they not on dedicated machines? KISS.