I've been thinking that performance should be better- I expect it to be more in the 10K RPS range than the 1K RPS range for a fast machine.
The way that we read TCP sockets is slightly out of the norm. Currently, we only read one MySqlPacket at a time from the socket. When dealing with result sets, this is equivalent to a single row. Result set rows are usually pretty small, so this means we are calling into the OS's socket API for every single row in a result set.
We may call socket.read a handful of times before we ever read a single TCP packet. This means that the TCP packet is sitting in the OS's socket buffer, taking up space. The socket buffer is unable to accept more packets until it has been completely read.
When activity is high, and many MySQL connections are reading a lot of data, the OS socket buffer is sitting full while connections read from it row by row. MySQL may try to send more data, but when the socket buffer is full, these packets get dropped and cause a TCP retransmission. TCP retransmissions are bad for performance.
To test this theory, I ran our stress tests and checked retransmissions after each test:
I wanted to see what other MySQL drivers out there could do, so I coded the GET /api/async endpoint in Go. It has a lot less retransmissions and is a lot faster:
The point of this comparison is not to say one language is better than another, it is to prove that the MySQL server can serve 10K requests in 1 second. I believe that async C# code should also be able to handle 10K requests in 1 second. Go uses M:N threading and async I/O, just like async C#.
I think that part of this performance improvement will come from making our library more "TCP friendly", and freeing up the OS Socket Buffer.
I've been thinking that performance should be better- I expect it to be more in the 10K RPS range than the 1K RPS range for a fast machine.
The way that we read TCP sockets is slightly out of the norm. Currently, we only read one MySqlPacket at a time from the socket. When dealing with result sets, this is equivalent to a single row. Result set rows are usually pretty small, so this means we are calling into the OS's socket API for every single row in a result set.
We may call socket.read a handful of times before we ever read a single TCP packet. This means that the TCP packet is sitting in the OS's socket buffer, taking up space. The socket buffer is unable to accept more packets until it has been completely read.
When activity is high, and many MySQL connections are reading a lot of data, the OS socket buffer is sitting full while connections read from it row by row. MySQL may try to send more data, but when the socket buffer is full, these packets get dropped and cause a TCP retransmission. TCP retransmissions are bad for performance.
To test this theory, I ran our stress tests and checked retransmissions after each test:
I wanted to see what other MySQL drivers out there could do, so I coded the GET /api/async endpoint in Go. It has a lot less retransmissions and is a lot faster:
The point of this comparison is not to say one language is better than another, it is to prove that the MySQL server can serve 10K requests in 1 second. I believe that async C# code should also be able to handle 10K requests in 1 second. Go uses M:N threading and async I/O, just like async C#.
I think that part of this performance improvement will come from making our library more "TCP friendly", and freeing up the OS Socket Buffer.