-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
perf(postgres): optimize the expired rows cleanup routine in postgres connector #10331
Conversation
local connector = connect(config) | ||
|
||
local cleanup_start_time = now_updated() | ||
local ttl_escaped = connector:escape_identifier("ttl") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be better if we define ttl_escaped
as a global variable so that we won't have to run escape_identifier every time for the same result.
By the way, personally I don't think either ttl
or expired_at
need to be escaped, neither of them is a keyword in current postgres-sql syntax.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The escape_identifier
depends on creating a Postgres connector in pgmoon(which makes it feel weird to reuse at module level), and it is merely just string manipulation so the cost is small, so I think it's okay to keep the current state
connector:escape_identifier(table_name), | ||
" WHERE ", | ||
column_name, | ||
" < TO_TIMESTAMP(%s) AT TIME ZONE 'UTC' LIMIT ", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be helpful if we force db to use index here? I think it might not be better, but at least it wouldn't be worse than seq scan when using index.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a google and found no way of using force index in Postgres... it seems that this is not doable
SELECT
ing a batch of rows here should be fine since we already have ttl
index for these table, so let's just leave the job for query engine to decide whether it should use index
local time_elapsed = tonumber(fmt("%.3f", cleanup_end_time - cleanup_start_time)) | ||
log(DEBUG, "cleaning up expired rows from table '", table_name, | ||
"' took ", time_elapsed, " seconds") | ||
connector:disconnect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a connection pool and the usage of connection pool could be in our control, we should try to use it rather than disconnecting. Processing connection is a expensive action for DB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose to create a new connection for each table here intentionally, to avoid problems caused by connection reuse(such as a former connection waiting for locks and timed-out, re-use on this connection will be problematic, please check OpenResty's tcpsocket.receive
document on read timeout)
Since this timer will only run on worker 0 and a cluster mutex is also added in this PR, the number of new concurrent connections running cleanup job in this cluster will be 1 constantly, so I think there is also no problem with creating too many connections
0087b53
to
447be41
Compare
log - add cluster mutex to limit concurrency across traditional cluster
447be41
to
0b431f9
Compare
Question: what if we drop the Postgres cleanup routine altogether, and instead create Postgres triggers on entities that have The |
Hmm I'm a bit concerned about the But yes, dropping the background timer and adding a trigger(and batch deleting in the function perhaps?) is more straightforward, we could re-consider this option after the CP-DP refactor thing happens. What do you think about it? @bungle |
@bungle Could you please show me where can I change to make this guarantee? I can confirm that all these ttl enabled tables have a proper index on ttl fields in the migration files, but I don't know if there is any DAO mechanics that can guarantee the schema field is indexed. |
Yes, just by looking our Postgres schemas that |
The batch deleting as you added here is basically Postgres feature. Thus it is easy to add it in trigger delete too.
We can tune this inside trigger. E.g. delete only sometimes etc. What I mean that the current logic is very complicated as in this PR. I think in most cases what we already had is better than what we have here. So then we have probably a single pain point that is oauth2 tokens. Perhaps oauth2 tokens ttling should be implemented differently while the rest are fine with current or triggers. Perhaps triggers can be made to work with oauth2 tokens problem too. |
superseded by #10389 , after discussion |
* feat(db): batch cleanup expired rows from postgres ### Summary The PR #10405 changed cleanup to only happen on nodes that have Admin API listeners. This PR makes deletion to happen in maximum of 50.000 row batches. Inspired from #10331 and #10389. * change to batch delete on every table * update changelog * add test for ttl cleanup --------- Co-authored-by: windmgc <windmgc@gmail.com>
Summary
This PR tries to optimize the expired rows cleanup routine in the Postgres strategy connector.
Currently, the cleanup logic is managed by a
timer.every
, which will run multipleDELETE
sub-queries in one big query and delete those rows that got expired. Concerns have been raised about such queries may lead to high load on database if the number of expired rows is large. The idea originates from FTI-4746 and KAG-516, but the code that got changed in this PR may not be directly relevant to the root cause of the production issue. The logic itself is definitely worth some optimizing after all.This PR brings the following changes to the cleanup logic:
pg_expired_rows_cleanup_interval
is introduced to control the interval of triggering the timer.DELETE
sub-queries has been split into multiple queries, with more obvious logging, so that if any errors are encountered, the table which caused such an error can be located easily. And each query will use its own connection so that if any of them got timed out on Kong's side(for example, waiting for locks in the Pg but the connection itself is timed out), the following cleanup will not be affected.Checklist
Full changelog
pg_expired_rows_cleanup_interval
to control the interval of expired rows cleanup routine.Issue reference