-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Prepared play nicer with connection pools #95
Comments
Been doing a lot of thinking about prepared statements and connection pools, and wanted to put down my thoughts so far. Current downsides:Current design of prepared statements (while a vast improvement over the pre-v1.0.0
Why these problems?I believe, for the most part, these issues are ultimately symptomatic of the Prepared abstraction not accurately matching the reality. Thus, it needs high-level rethinking: Currently, It's also wrong on a higher level: Conceptually, to a database's human user, a prepared statement is...an SQL statement...that just has special "slots" for parameters and provides certain benefits. To a user, that's it, nothing more. That these particular statements happen to be tied to individual connections is merely an implementation detail of the communications protocol. I believe these disconnects are the main cause of the five above problems with Proposed solution for v2.0.0:Originally, I was thinking about (if anything at all) maybe offering an additional abstraction over top So I think a re-design is warranted, and here's what I'm currently thinking:
I think that neatly addresses all five problems above. Does all this mean mysql-native is getting too high-level for its charter?Mysql-native was originally intended as a low-level client lib on which higher-level DB libs, ORMs, etc, could be based. There is need for and value in that. Yes, IMO, modern mysql-native has been growing higher level than its original charter, and this Prepared redesign takes that further. However, I believe these higher-level interfaces are more than worthwhile and moving in a very good direction for the average D database user. So what of the need for a stripped-down low-level API?First off all, there's nothing stopping other high-level DB libs from basing their MySQL/MariaDB support on top of mysql-native's higher-level interfaces. That's entirely viable, and may even make things easier for the lib developers. And mysql-native does intend to reduce any overhead it does have and keep that to a minimum, even for high-level functionality (IMO, that's part of D's own charter, after all). But aside from that: For the sake of sensible code hygeine and maintainability, my intention for the near-term future of this library is to clean up the internal design and separate all the low-level communications code out of the higher-level interfaces. This will have the additional benefit of opening the door for further cleanup of the internal API, which will then become a new, optional, low-level API - once which I think should work better than the old original pre-v1.0.0 design (which I believe had somewhat of a high/low-level identity crisis and the API had various dark dangerous corners because of that). In long-term, I'm hoping that will also open up the door for additional DB backends, such as Postgres. |
Regarding prepared statements: I generally use them once. I only use prepared statements instead of SQL directly because of the security features and benefits of doing so (I'm used to building SQL strings with PHP and doing escapes, and it's really annoying). I think possibly the only time I used the advantage of executing a prepared statement more than once was to insert an array of data, but that's very few and far between. Regarding the abstractions, I like the idea of the prepared statement being an "implementation detail" for a connection, and having the connection automatically build prepared statements when it needs to, freeing them when it wants to. You may even be able to tune the freeing of statements based on some last-used cache. However, I also agree that this makes the abstraction too high-level. I've been wanting to port mysql-native to iopipe as a test to see how easy network protocols might be with iopipe, and I also think there is a large benefit to having direct access to the lower-level protocols. For one, you can avoid the whole For what it's worth, I'm doing a similar thing with iopipe, where I don't want to deal with the actual i/o, but just the nice layer on top of it. Eventually, iopipe will depend on some low-level i/o library like https://github.com/MartinNowak/io. |
It's inherent because they can't delete prepared statements otherwise.
Yes, Cassandra-Driver works like that https://docs.datastax.com/en/developer/java-driver/3.1/manual/statements/prepared/ neatly.
Careful with implicit expenses, I'd prefer an explicit method over hidden performance issues.
This looks like one idea, a prepared statement cache. Another good idea, make the ConnectionPool Thread/Fiber-affine, so application code always gets the same connection. |
For SQL statements that are only executed once, I much prefer Adam Ruppe's sqlite.d API, where you just bind SQL placeholders directly via a variadic template function:
Of course, this is not very efficient if you're running the same SQL very frequently. But conceivably, the |
I like the idea of a connection-agnostic prepared statement. It's actually exactly what I have built on top of mysql-native currently to use them at all. My usage for a prepared statement looks like this:
Implement like this:
|
@schveiguy Adam Ruppe's In my latest fit of NIH, I reinvented
This allows me to write stuff like:
Of course, once that's working, I went further by allowing automatic transcription to struct if the field names matched the column names:
And of course, taken to the logical conclusion, automatic conversion of the result set to an array:
No fuss, no muss, abstract away the dirty implementation details, and let the underlying code worry about how to actually map database types to struct fields. |
Some responses:
I've had some trouble trying to make changes to mysqln's low-level i/o code in the past (and it is a bit of a mess), so one of my current top priorities for mysqln is to seriously clean up the internal protocol code. First step is to move all comms-handling code into the "protocol" sub-package (#144). Then, some further cleanup, including some of the "consume..." stuff. (I've actually been wanting to get to this for a long while, but mysqln unittests were...umm...seriously lacking at the time so I needed to hold off until now, now that I can be reasonably sure low-level changes aren't causing major breakages.) Porting mysqln to iopipe might be difficult and problematic before that's done, so I'd recommend waiting until then, though you're certainly free to try.
Yea, @schveiguy IIRC has also suggested that, and I agree it's a good feature. It's on the roadmap for mysqln: #118 I don't want any further delays to v2.0.0 besides getting in this reworking of Prepared, but I'm hoping to have #118 in the following release.
Doesn't Variant.coerce do that? If not, that is disappointing. |
The line marked |
Some refinements to my Prepared plans for v2.0.0:
|
Sounds awesome! |
But why would this matter? MySQL uses standard types such as More: |
It matters if the database backend uses, e.g., special structures for storing dates (if the database engine has native support for it). If I'm swapping backends from one that uses, say, |
I think the solution is to get |
It's not a straightforward "just use a different streaming library type". So it fundamentally will require rethinking how it's done. What I will appreciate, however, is having an example implementation I can look at and see how things actually are done. I may even have to read the protocol spec anyway. |
Me:
Turns out that delaying statement registration until after the next purge has some ugly consequences I wasn't anticipating[*]. Not sure why I was thinking it was needed anyway though - auto-purging when a statement is created isn't going to cause the kind of nasty surprise that auto-purging on statement release could've caused. So creating a prepared statement won't be delayed: It'll occur immediately, auto-purging any data that might be pending. As before, only release will be delayed to avoid a surprise auto-purging. ([*] It would've meant that an instance of Prepared could potentially be in a not-yet-ready state, lacking certain necessary info from the server. This state would have necessarily been distinct from "was created, but is now released", and it led to a bunch of mess.) |
@schveiguy I looked at the In my NIH SQLite binding, what I did was essentially:
I used Now, arguably, I could have use Not to mention that by deferring the actual call to the SQLite library inside |
Well, maybe then the answer is to use Algebraic instead. Surely the set of types the database supports is finite. |
And does std.conv.to support Algebraic? |
As @Marenz pointed out over here, since prepared statements are (as per the way MySQL works) tied to a specific connection, using them in combination with connection pools is awkward and leads to questionable benefit.
Need some mechanism, probably a connection-independent wrapper for Prepared, to help alleviate those issues.
The text was updated successfully, but these errors were encountered: