9mm
Difficult debugging problem
I’m having a very frustrating issue debugging something, and I’m now resorting to the forum for help.
I’m sure people will recognize this question as I’ve exhausted a lot of options trying to solve it.
I have an API that currently gets ~100,000 incoming requests/minute (1,600 RPS). For each incoming request, I create 2 to 4 outbound requests which each have a 500ms timeout. If any requests don’t complete within 500ms, I take the ones which completed successfully, analyze the results, and then return the response back to the original incoming API request. So basically 100K incoming equates to 200K-400K outgoing.
I need to scale incoming requests to 25K RPS (to start) and beyond… upwards of 200K incoming RPS (400K-800K outgoing)
I log all outgoing request error codes, timeouts, etc.
I’m encountering an error where outgoing requests will start to report timeouts at ~20K incoming req/min (a very low number… thats only about 1000 outgoing RPS). If it goes much higher than that, I will see outgoing request timeouts as high as 80% to 95%.
I’ve hired a few people to help me figure this out, and everyone is stumped. Here are my current findings:
- Increased ulimit
- Added
+Kand+Q 134217727(total ports) in vm.args - Migrating from AWS to Google Cloud helped by about a factor of 2-3x but didn’t fix the issue. Currently I’m on Google Cloud and the issue still happens at ~20K/min (incoming)
- I know for a fact it’s not the APIs themselves (I’ll spare you all those details)
- I’ve tried multiple HTTP libraries (Tesla, Buoy, Machine Gun, HTTPoison), all have basically the same results with different levels of timeouts and errors
- I’ve tried not using a pool, and adjusting pool settings. I don’t remember the exact specifics of how it affected it, I just know it didn’t do much
Most importantly… observer seems to report no issues. Every single person that looks is like “hmm… you’re right, observer shows no MsgQ backups, and Reds is a normal number. Theres no IO load.”
So far, everyone has thought this is some issue lying outside of Erlang (all things seem to point to it, especially considering migrating from AWS to GKE helped).
However, last night I did a test which 100% confirms to me the issue is not ‘outside’ but within Erlang/Elixir/the HTTP library.
First, I created a node pool in Kubernetes with a single node: a highcpu-64 (64 CPUs and 240GB memory). There are zero other nodes running.
For those not familiar with kubernetes, a “node” is a server instance, and a “pod” is an instance of the app running.
I then made a single pod running on that node. I observed the app would start having timeouts at ~20K/min. Obviously the CPU and Memory on the machine was at like 1% on each because its a huge server. Again, when timeouts occur, observer has no backed up MsgQ and Reds is fairly normal.
I then increased pods to 12, all running on the same node (no changes were made to the node, networking settings, etc. All variables are the same).
Each instance of the app logs stats, so now instead of seeing 1 set of stats @ 20K/min I see 12 sets of stats @ 1.6K/min each. Low and behold, the problem magically goes away. In fact, I can now increase the traffic to 10K per app instance (10000 * 12 = 120K/min), with < 1% timeouts.
To phrase it another way: 1 app on 1 64-cpu server = 20K/min max throughput before errors. 12 apps on SAME 64-cpu server with no other changes = 10K throughput/min per app without errors. Thats 120K total/min. 120K is almost an order of magnitude greater than 20K/min so the max throughput without errors absolutely increased by changing only erlang/elixir and no outside server settings/firewall settings, etc. I’m sure it could even up to ~20K (each) again before it starts getting errors but its difficult to control the traffic level.
This to me proves the problem lies in the elixir/erlang app space and not some external issue (UNLESS each instance of erlang is somehow allocating outside resources based on a limit, and creating new app instances is “fixing it” because its getting more of those outside resources).
I guess I’m out of ideas of what to think about / try. If anyone has any complicated linux commands I could run, like using strace or something to help debug I’d love to hear. I could sit here all day playing with pool settings and http libraries but my gut says its not that, because in every library I try there’s always some low limit, which makes me think the problem is somewhere else.
Sorry this isn’t more specific, theres so many things I’ve tried and SO many knobs and dials to turn that it makes this extremely difficult to get a 100% cause / effect, especially considering the traffic level always fluctuates. So one minute I’ll be testing something at 8K/min but then i’ll change something and the traffic level goes to 13K/min, changing variables.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #elixirconf-us
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #performance










First 10 of 91 Posts
outlog
what cpu/memory limits have you configured for the pods?
how much memory is the Beam VM using when timeouts starts to occur?
ckhrysze
Based just on reading through this, nothing immediately stands out. My main purpose in replying is just to say that my experience with kubernetes heavily suggests
is a very likely an issue.
You mention gaining some benefit going from AWS to GCP, what instance types were used on each? Did you get any benefit from using images with higher resources?
benwilson512
As hinted at by the first reply, when scaling Elixir you’ll always want to first increase the number of CPUs available per pod before you scale the number of pods. There’s no point in running 12 1 cpu pods when you could just do 1 pod with 12 cpus. The latter will perform much better.
EDIT: When you spawn it with just 1 pod, how many schedulers does the log print out are available?
chrismcg
This is pretty far outside what I know so the following is just a theory… What do you see when you run
ss -sin the container to get a summary of the socket states when the problem is happening?My (probably wrong!) theory is that you’re running out of space in a NAT table somewhere. You’re connecting the same container IP to the same backend service IPs over and over again so if those connections aren’t being closed properly (because you timeout after 500ms and maybe don’t let them shut down properly) then they could be stuck in
CLOSE_WAIT. Those connections won’t be removed until they’ve timed out (kube-proxy docs say 1 hour though I don’t know if this the actual relevant timeout).I’ll stress again I don’t really know much about k8s networking but I have read that outgoing connections are SNAT’d.
9mm
I don’t believe I have any “limits” imposed. Is that in my YAML file? I will double check if you tell me that’s where they are.
Here is my results from inside the docker container:
bash-4.4# ulimit -a
bash-4.4# sysctl -a
9mm
Interesting. I’m using “Google container-optimized OS” and that command isn’t available. Is that something I can install with apt-get or does it have another name? I don’t have it on my mac either so I can’t read about it
chrismcg
maybe
netstat -sis available?benwilson512
Definitely do a test where you explicitly set 12 CPUs and 24 schedulers in your vm.args.
In your pod spec do:
and in your vm.args
+S 24.One aside to check here if you’re on K8s is DNS lookups. Each outbound request from within K8s will do like a half dozen DNS attempts to see if there are any local services that use that domain name before actually heading to the outside world. Google may optimize better for this. You can get around this by prefixing the domains you hit with a
.to indicate that they are fully qualified.ALSO be sure to either configure the KubeDNS pods with some CPU requests / limits or ensure they’re on a different node, otherwise you can use so much CPU that KubeDNS can get throttled and stop responding properly.
In fact I’d consider trying to run a test outside of K8s just to eliminate this specific issue.
9mm
Awesome suggestions! Let me check some of this stuff. So you’re saying on the domain thing that the domain I give to the HTTP lib is like this?
http://.example.com?9mm
Oddly enough that works on my mac (huge output) but when I run it on the server the command works but with different options: