-
Notifications
You must be signed in to change notification settings - Fork 442
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
compute_ctl: Streamline and Pipeline startup SQL #9717
base: main
Are you sure you want to change the base?
Conversation
Before, compute_ctl didn't have a good registry for what command would run when, depending exclusively on sync code to apply changes. When users have many databases/roles to manage, this step can take a substantial amount of time, breaking assumptions about low (re)start times in other systems. This commit reduces the time compute_ctl takes to restart when changes must be applied, by making all commands more or less blind writes, and applying these commands in an asynchronous context, only waiting for completion once we know the commands have all been sent. Additionally, this reduces time spent by batching per-database operations where previously we would create a new SQL connection for every user-database operation we planned to execute.
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.
Looks really nice. Any benchmarks?
5490 tests run: 5247 passed, 0 failed, 243 skipped (full report)Flaky tests (3)Postgres 16
Postgres 15
Postgres 14
Code coverage* (full report)
* collected from Rust tests only The comment gets automatically updated with the latest test results
d796cc2 at 2024-11-15T15:27:42.216Z :recycle: |
Is there a control plane aspect here? Not obvious to me if so. |
Not that I'm aware of. I'm betting you got the ping due to the compute spec reading code. |
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 have general comment not directly related with this PR and which may be should be addressed separately. Right now we applying all this statements concurrently with user statements. In other words, at the moment of executing this statements, database already accepts user's connections and so can execute user's queries.
It may lead to some unexpected and confusing behaviour: for example user may observe old version of Neon extension, because it was not yet upgraded. I actually reproduced such problem myself in one of the tests.
So I wonder if we could (should?) somehow prevent user from accessing this database instance before compute_ctl completes node update? Not sure what is the bestway tp do it:
- lock some catalog table
- configure different port
- somehow block connections at proxy level
...
} | ||
|
||
#[derive(Clone, Debug)] | ||
pub enum ApplySpecPhase { |
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 have even more radical proposal than @tristan957 suggested (use include_str!
): can we specify all statements which should be executed in some file and read and apply them from this file?
Yes, I certainly understand that we need some parameters for this statements (roe names database names,...). Its can be solved in multiple ways: placeholders for parameters, per-roles and per-database files (so per-role file contains list of statements which should be applied for each role and, for example {role}
should be substituted with role name. In the same way - per-database file contains statement which should be executed for each database and {db}
should be substituted with database name.
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 don't think that's easy, and that it'll take much more effort, especially as some statements need to be executed N_DATABASES times, some are conditional on the database (not) being owned by a user, etc. There is complex logic that I prefer to apply before starting/checking the SQL session.
Maybe it can be done in a future iteration, but I take "done" over "perfect".
Don't we only start allowing connections from outside the container after we've transitioned to the Running state? During startup we're the sole user of the database, right? At least, that's what's been diagnosed as causing the For
I think that's more related to |
The previous iteration always connected to every database, which slowed down startup when most databases had no operations scheduled.
Addresses @ tristan957's comment, and allows separate review of these queries.
db_owner, | ||
), | ||
comment: Some(String::from( | ||
"granting anon extension tables permissions", |
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.
s/granting/grant will match the verb tense of the other comments
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 other comments have mixed tenses, with this verb tense actually being slightly more common, and much more common for the anon
steps:
- dropping x1
- renaming x2
- creating x2 (1x anon)
- clearing x1
- deleting x1
- changing x1
- initializing x1 (1x anon)
- granting x4 (4x anon)
- change x1 (1x anon)
- add x1
- install x1
- make x1
- alter x1
- update x1
Instead of Mutex, using an RwLock on shared modifyable state helps speed up read-only parallel steps (i.e. most parallel steps). This makes the per-database jobs actually work in parallel, rather than serialize on accessing the database- and roles tables. Additionally, this patch reduces the number of parallel workers to 1/3rd of max_connections, from max_connections - 10. With the previous setting I consistently hit issues with "ERROR: no unpinned buffers available", now it works like a charm.
Performace reviewNo-op sync of 1024 roles and 1024 databases, on my WSL box (16vCPU, enough RAM/SSD):
Config
First I let the system apply the changes, then shut the database down. Finally, measure start time with nothing to be done. Future workMany of the modifying queries generated for the system database (e.g. role- and database creation, deletions) can be parallellized within their respective phases, too. Maybe we can look into that at a later date, as creating 1k databases sequentially takes a lot of time (and are thus likely to hit |
Before, compute_ctl didn't have a good registry for what command would run when, depending exclusively on sync code to apply changes. When users have many databases/roles to manage, this step can take a substantial amount of time, breaking assumptions about low (re)start times in other systems.
This commit reduces the time compute_ctl takes to restart when changes must be applied, by making all commands more or less blind writes, and applying these commands in an asynchronous context, only waiting for completion once we know the commands have all been sent.
Additionally, this reduces time spent by batching per-database operations where previously we would create a new SQL connection for every user-database operation we planned to execute.
Problem
Performance of starting compute with 100s of users and 100s of databases is quite suboptimal. This is one way to reduce the pain.
Summary of changes
async
to apply the SQL changes built in the above systemcompute_ctl
even further to batch all operations done in each database, so that we don't needlessly (re)connect to the same database over and over when a large amount of roles was deleted.Note for CP reviewer: I don't expect much to have changed on your part, as it is mostly data flow changes in the tool itself, with no definition changes on the Compute Spec side.
Checklist before requesting a review
Checklist before merging