Open divega opened 7 years ago
@davidfowl, @anpete could you please detail a bit more (and better) what you had in mind here?
@roji I'd like to get a good idea of the threading model and buffering strategy you chose for Npgsql. That'll help with my mental model (I'd like to do some experiments with pipelines).
@davidfowl are you actively working on this?
@davidfowl it's great to be having this conversation with you guys. Below are some notes on Npgsql works, please don't hesitate to ask more questions.
{Read,Write}Int32()
.NetworkStream.Read{,Async}()
call takes place for the entire read buffer size (8k). At this point it's probable that many result rows are read into the buffer, and subsequent calls to NpgsqlDataReader.Read()
will be memory-only - until the read buffer is exhausted and another I/O call occurs.CommandBehavior.Sequential
when executing the command - this will switch to a more advanced mode where we don't attempt to buffer the entire row in memory (or anything else for that matter), and it's the user's responsibility to read the row's columns in the order in which they arrive from the database (this is a standard ADO.NET API feature). This really only makes sense for scenarios with very big rows (e.g. binary data).async
flag down the stack. At the very bottom, when actual I/O has to occur, we test on the flag and execute either Read()
or ReadAsync()
. This allows us to avoid duplicating code (i.e. same functionality written once as a regular method and once as an async method) without any significant overhead (I hope!).NpgsqlConnection.Open()
and NpgsqlConnection.Close()
are called and which is responsible for assigning a physical connection (what Npgsql calls a "connector") to the user's NpgsqlConnection.I hope this is a first fair description of Npgsql's internals, whipped up just before I head off for work :) Please feel free to ask more questions, I'll be closely monitoring this thread and the other performance-related ones. I'd definitely welcome experimentation with a different I/O model (e.g. pipelines).
@davidfowl are you actively working on this?
No, it's on my (now shorter) list of things to look at. I'm looking at using minimal code to build a simple postgres driver based on a protocol spec that @mikeharder shared a while back. Basically a single query with results to benchmark some of the patterns.
@roji Is something like this interesting for npgsql?
@davidfowl do you mean building a non-ADO.NET driver for PostgreSQL, possibly non-managed (i.e. based on libpq)? Or just an attempt to build things properly from the ground up to see where it leads?
Npgsql is obviously a fully managed ADO.NET driver, but of course anything you do in terms of PostgreSQL connectivity interests me - you're also welcome to ask me questions, I've had a lot of experience working with the wire protocol. Also, at some point in the email exchanges I mentioned the possibility of splitting Npgsql (or any other ADO.NET driver) into two parts: a low-level ultra-efficient API, on top of which we'd built an ADO.NET adapter. This would allow performance-hungry users to drop down to the low-level API as needed. The low-level API could simply be libpq, or it could be fully-managed implementation (possibly libq first and managed later). There's also the question of whether this low-level component would expose some sort of standardized new database API - a high-performance competitor to ADO.NET, which, among other things, would allow you to include database sockets in an epoll/kqueue programming model (currently impossible with ADO.NET).
All this is a bit theoretical, and this would obviously mean very big changes for Npgsql (it would definitely be Npgsql 4.0 :)). Let me know if this is the direction you were thinking of etc.
Or just an attempt to build things properly from the ground up to see where it leads?
This. I wasn't thinking about going as far as building an entire driver. The end goal would be to improve npgsql. The idea is to write a minimal low level API that does the working and protocol so we can focus on things like buffering, IO, async and threading models. Ideally we'd take a very simple query and write the simplest code possible then build up from there. I'd also like to see this example using the lower layer of npgsql (if that's possible).
So yeah, I'm absolutely interested... Feel free to reach out if any questions arise during implementation. Note that at the moment Npgsql doesn't really have a low-level layer internally which can be easily separated from an higher-level ADO.NET layer - that was more an idea for the future.
@roji, I'm going to try to get something basic working within the next few days. I'll push the code to this repository (in a PR).
Great, once you push something I'll definitely take a look - it'll be interesting to compare with Npgsql.
On my side I'm working on bringing the dev branch back to working order (there are some pending issues) and then the plan is to implement command-level caching (https://github.com/npgsql/npgsql/issues/1701), which will provide quite a substantial boost.
Here are the result based on @anpete 's implementation of the protocol for the fortunes query.
Same environment as TE on cloud (Azure D3V2 machines). Database server scaled up to 8 cores as it was a limiting factor for the max perf. The new driver is compared to npgsql in async mode with full cached strategy (see https://github.com/aspnet/DataAccessPerformance/issues/3 for explanation).
This configuration we actually max out the client CPU, the client Network and the Database CPU, changing any of these would not change the overall result significantly.
What we can learn from that is that we reached pgbench baseline numbers with a fully managed .NET Core implementation.
We also tried this driver in the TE Fortunes benchmark. We are seeing 45K rps, compared to 16K with in raw ADO.NET mode with npgsql. On this infrastructure the current best result (undertow framework) is at 30K rps.
Next step is to decide what to do with that data, and more specifically if we want to go further with the idea of a different abstraction layer than ADO.NET, or even no abstractions at all but custom providers for each micro/full ORM.
@anpete and @sebastienros, it's really great to see Peregrine and its associated benchmarks. It indeed looks like a solid baseline of where we might end up with a managed implementation, and I think it shows that looking at a native solution (e.g. libpq wrapper) or a radically different I/O approach may not be worthwhile.
Here are some notes after reviewing Peregrine. Some of these will point out missing features, which are totally understandable in an early-stage attempt such as this, and the idea is mainly to get us thinking and to evaluate to what extent the high performance is a result of a lack of needed functionality.
@roji
As requested, here is and updated graph with your experimental version. This one is from Linux on the async
mode. The improvements are impressive.
A note however is that it seems to have issues with the sync
mode, with a lot of variation and nearing 0 sometimes.
We have some ideas that we want to try, which involve giving low-level networking for ADO.NET providers a similar makeover treatment to what did with Kestrel some time ago.
@davidfowl (and @anpete?) volunteered to do some experimentation with PostgreSQL, but this could apply to any database.