-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Customize connection pool and/or queries for cluster instances #2253
Comments
Currently not supported. I wrote a quick (and dirty) work around that just juggles Knex instances until Knex can be updated with specific functionality. |
I'm working on this right now too... |
+10 |
@richraid21 You stated in #2339 that having read/write routing inside the pool would be the most robust solution. I disagree:
I would like to propose having this above the pool, as a router/layer that possibly encapsulates multiple tagged pools and serves
|
I also recently had a need for knex to be able to direct some table queries to a replica DB. It's d be great to see this feature added in the nearest future. +20 |
@rkaw92 I think we have the same idea, I just didn't express my thoughts adequately. I agree that this should not be done inside the pool. To clarify, if I'm understanding correctly, you are imagining a system in which:
When Thanks |
@richraid21 That's conceptually it, yeah. Additionally, this layer should live between knex and the pools themselves so as to be re-usable outside of knex and not add any bloat. |
Hey all. Not sure this is needed anymore for Aurora... The now offer a master-master serverless DB https://aws.amazon.com/rds/aurora/serverless/ |
@podtrackers Updated issue header to be more general, some need for this still remains . |
@podtrackers I think that's a good point. As far as I can tell, Aurora serverless looks to be more useful in terms of decreasing costs for low-load, non-prod environments. We'll utilize it where we can but will probably continue to run dedicated clusters in our prod environments where the traffic is more consistent. |
Having load balancing in db driver has many advantages by removing all the intermediate services (pgpool/virtual interfaces). As it was already mentioned there should be a separate pool per each DB node. But we'd also need health checks so that requests are not sent to servers that are offline (High Availability). This will also have to be implemented per db driver, because of a need to check replication lag and not send queries to servers that are too far behind master. Also we'd need to give API user the power to decide which queries go there, with an option to send all selects to slave servers. This could also be configured per query, e.g. query.fetch({slave: true}) I'm building a similar solution for PHP, where my code will juggle PDO connections for postgres DB. If PHP code works as intended, I'm willing to do some sort of knex implementation and share it. It would be only for postgres DB, however. And while writing this down, I realized knex is probably not the place for this kind of code :) +10 |
I took a stab at this here: #2847 |
I wonder is there anything new about this? We are using AWS RDS and we have read replicas. We use AdonisJs as backend framework and it uses knex.js as database provider. Without read replica support, I guess we have to change our stack. |
@ozziest I don't know another stack will automagically do this for you. The "simple" way is to have a knex handle for writes and a knex handle for reads. |
I have approached it by separating knex instance of To counter the situation, I have created a module, that will let you use a callback function to compute the connection config at runtime and opens up the possibility to round robin between multiple connection configs https://github.com/thetutlage/knex-dynamic-connection Lemme know if anyone has any thoughts :) |
Am now using aurora global database, where reads must be done in one region and writes made to another. I want to obviously distinguish these, even if it's as simple as having all select statements be automatically routed to the read endpoint. That's good enough for me. |
This is my two cents on the issue. Since aurora provide two endpoints: reader and writer (master) where the reader one load balance automatically between all read replicas, we can use only those two. I show you how I split my queries between read and write and use the correct endpoint. I also added a way (
|
@Frolanta Thanks for the idea. Are you sure this gonna work with Aurora pgsql with read replica? I have exactly the same stack (nodejs, aurora postgres) but I also included bookshelf.js above knex. but overall, it worth to implement this as a feature inside Knex API |
This is a real pain point for me as well. |
Just confirming that @Frolanta's design seems to work. It should be baked into knex. |
@jpike88 : Out of curiosity, what are the main pain points you encounter when using the "2 separate Knex pools" approach? Ex: I'm guessing that this approach becomes problematic when you try to use |
It becomes an issue when the codebase gets so large that it's painstaking to manually split all queries between the two pools. There's one issue with the above alternative though, it doesn't seem to play nice when I try to use the .transaction method, just says can't find includes of undefined. I'll keep messing with it |
I'll finally create a separate repo for knex-related utilities today, so if anyone would volunteer to build solution similar to the one proposed above, it could be included there. It sounds a little bit too high level to be included in Knex itself. |
Just to inform you that there is a small problem with the solution I provided few months ago. This is more a problem with AWS Aurora than knex but maybe it can be useful to you. You can't use the AWS read endpoint which load balance between your replicas. Let's say you have multiple read replicas. The read host only route to one of your replica "ip" and it change every second. So if like me your pool if almost full in less than 1sec then all your pool connections will be on the same replica. When one client connection timeout you will have a chance to be on an other replica, but again if your client connections almost never timeout like me you will only use one replica, or maybe 80% one and 20% another. The only solution that I found is to have an array of read pools. Since you have multiple read pool, you can select one randomly or use |
One can easily also use validate callback to implement counter after how many times connection can be fetched from pool before it will fail the validation as @devinivy suggested. Validation failure automatically evicts the connection from pool and creates a new one if necessary (or just fetches any other connection from the pool if there are connections available already). Both should be able to be implemented already by passing custom validate function to knex pool configuration. This is pretty specific usecase so adding copy-paste configuration recipe for aurora to knex cookbook could be enough. |
This could be nice, but we will still need a way to choose manually.
I'm not sure this is good idea, 1sec is really long.
The thing is that 100ms (replication time) is kind of long. perform /book POST (create a new book and return id) I don't know how you can imagine a way to lease a connection for this kind of scenario. For the replica connection I think the solution should be something a bit like sequelize do https://sequelize.org/v5/manual/read-replication.html (Didn't really dig into it so I don't know much about it) You should be able to provide an array of replica (read) connection params.
Also, read queries should also be performed sometime and your master (so master connection should also be included during the selection of the least used connection): Your application can be 99% read queries, so if your have one master and only one replica you will not use your master properly if we don't include it. What do you think ? |
@elhigu @lorefnon @briandamaged @maximelkin I've created https://github.com/knex/knex-utils (with placeholder content for now) for all kind of useful things that are outside of knex per se but can be useful for a wider audience. Ideas for possible features and submissions are more than welcome! |
Agreed that end-users should still be able to directly access the underlying pools. (Personally, I'm not sure how much I'd trust logic that tries to choose the appropriate pool automatically. It seems like there would be a lot of corner-cases that could cause it to make the wrong choice)
Agreed. I'm mostly trying to figure out if there are any oddball scenarios that we might need to be able to accommodate in the future. Like you said: it would be much more efficient just to obtain/update the complete set of IPs periodically. This would eliminate the need for any type of "warm-up" period.
Yeah -- I don't think connection leasing would be applicable in this example since it spans multiple HTTP transactions. Connection leasing is more applicable when you have multi-step logic on the server side. In that case, it provides a way to guarantee that the same connection will be used throughout the entire multi-step process. (Ex: this is important when you are performing operations inside of a Transaction) |
Would be useful if connection could be a function, eg: const knex = Knex({
client: 'pg',
async connection() {
/** select and return the appropriate server */
},
}) but ended up with this not so ugly hack: const knex = Knex({
client: 'pg',
connection: {},
})
const servers = [ /** list of servers */ ]
const { acquireRawConnection } = knex.client
knex.client.acquireRawConnection = async function _acquireRawConnection() {
knex.client.connectionSettings = null
for (let server of servers) {
/** logic to select the server */
if (server.hasSuperPowers) {
knex.client.connectionSettings = server
break
}
}
if (!knex.client.connectionSettings) {
return Promise.reject(new Error(`No read-write server detected`))
}
return acquireRawConnection.call(knex.client)
} using async cause i'm selecting the server based on |
Okay, so I've given it some thought and think we can realize read write clustering without too much effort.
Yet, the problem of so called sticky connections (using the same connection for one request) remains, but still I think that the developer has the option to chose the connection based on the code. |
With just a few changes to the original code I was able to extend the default There are just a couple of questions which I would like to discuss first before creating a PR.
|
I think that should be configurable, and considering that there are typically more than a few connections at a minimum, it shouldn't default to 1... maybe 3?
Never heard of Galera Cluster, likely the market share of ppl using it versus standard configurations is so small it's not worth consideration.
Good question... my main concern with your PR is keeping scope constrained so whatever it does, it does it well and with room for configuration I'm not a maintainer, just weighing in |
@llamadeus please before trying to push that solution to knex, first implement it as an external package, which uses knex internally. I would say that you are better off with having separate getters for read / write connections already becuase of transactions. Transactions are always needed when ever you are doing anything non-trivial. So you actually have to know what kind of connection you need already before making the queries. I haven't seen any reason why you couldn't implement that clustering functionality outside of the knex and I wouldn't like to see this prototyped in knex core code, since when its done, then maintenance will be in shoulders of knex core maintainers and handling bug reports etc. With external package you can:
In any case the most important feature you will need is to manually request read or write transaction. |
@llamadeus Let me know if there's anything I can do to help with your work. I'm not a maintainer, just a project consumer - we implemented Knex about nine months ago and have slowly been replacing Sequelize. However we're now to the scale where we need to implement read replicas in our Aurora cluster. I had assumed going in that Knex supported this out of the box - I'm surprised to find that it does not. We'll either need to help out with this issue, and get read/write replica support into Knex, or we'll have to bake our own internal solution. |
@llamadeus same here, I am not a maintainer, but I've been using Knex a lot recently. |
There's one shortcoming with @Frolanta's code, which has been useful to me anyway: If you're using asyncStackTraces, and there are undefined bindings in a query, an error gets thrown at toSQL() in the wrapper. This causes an useless error as the asyncStackTraces code never had time to execute. At first I lodged #4531 because I thought it was a Knex internal issue but have just realised the real issue. Here @Frolanta's code with my changes near the bottom:
@AlbertoMontalesi fffuuuu |
This comment has been minimized.
This comment has been minimized.
Anything new to handling clustered db with knex ? For switching db automatically ? Thanks |
+10 |
+1 |
I should add more context around this workaround I did: In that implementation it still blows away all slack outside the knex code, even with asyncstacktrace enabled... so that sucks. |
Actually, found the problem. knexWrapper itself needs to have asyncStackTrace enabled for the stack to be preserved. thank god |
@kibertoad @OlivierCavadenti any updates about this issue? |
+100 |
For anyone implementing any of the workarounds suggested here: We put together an implementation based on @jpike88 's code in this thread. That code will break ala:
Seems to work just fine. |
I was thinking instead of changing knex we can manage this at a higher level. Suppose we have servers R1, R2 and W.
This helper function can implement round robin or random scheme to select a node (in the selected class) in each call. |
+1 |
I'm looking to configure the mysql pool to handle reader and writer endpoints available for aws aurora clusters.
Basically read operations would point at one endpoint and write operations would point at another. Is there currently any configuration mechanism available for this use case?
I've looked for previously opened issues that seem related:
Its not clear to me if or how they've been resolved and if there's now a mechanism for handling that now. Any help is appreciated.
The text was updated successfully, but these errors were encountered: