Skip to content

feat: schema cache to prevent thundering herd#1258

Open
levkk wants to merge 6 commits into
mainfrom
levkk-db-global-schema-cache
Open

feat: schema cache to prevent thundering herd#1258
levkk wants to merge 6 commits into
mainfrom
levkk-db-global-schema-cache

Conversation

@levkk

@levkk levkk commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Sharded deployments with dozens / hundreds of users will stampede the servers to load the schema. With large schemas, e.g., thousands of tables, and multiple replicas, this is pretty slow and will cause issues.

The cache is using the database name and shard number, which uniquely identifies a host in pgdog.toml.

@levkk
levkk requested review from meskill and sgrif July 23, 2026 21:54
@blacksmith-sh

This comment has been minimized.

@codecov

codecov Bot commented Jul 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment on lines +47 to +49
let schema = shard.fetch_schema().await?;

*guard = schema.clone();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in theory here could be a tricky data race with SchemaCache::global().clear();:

on replace_databases the following happens: clear -> new_databases.launch -> old_databases.shutdown

if for some reason we trigger replace_databases while schemas are still loading for old (like with retries due to errors) we may try to write into the cache that is basically associated with the new databases already.

The shutdown token would've be helpful, but this is called after the clear and launch, leaving this race window.

There is another race that is not important I think: lock -> fetch_schema -> reload_databases -> set to old guard. But it's not a big issue since the guard will not point to the cache map entry anymore and that'd be a separate mutex.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I know what to do here. I'll add a global "config is reloading" signal that can cancel all of these long-running loops. I've been meaning to add something like this; making everything more deterministic.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might also have to be versioned too. Each config reload increments the config version so each Cluster knows which "epoch" it belongs to.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*race condition, not a data race.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this one is kinda tough. We may need to refactor the config system from an ArcSwap to a watch, or something. to ensure this works across the whole app.

Right now, the race condition is real, but it may not have a real impact. i.e., if they race, they happen around the same time, so the schema is probably identical anyway. This was built specifically to busted when the user wants it to, e.g., they create a new table, then hit RELOAD in admin. Either way, the table already exists, and the schema loaded in the cache is fresh.

@levkk levkk Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured it out. We should attach all config-scoped "globals" to the Cluster. Duh. It's already versioned in a way that each config version gets its own Cluster, so as long as the cache is scoped to the lifetime of that Cluster, we don't have a race between config versions.

We should ban globals lol. Everything from now on should be Cluster-scoped.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in 2fe3347

Comment thread pgdog/src/backend/schema/cache/mod.rs Outdated
Comment thread pgdog/src/backend/schema/cache/mod.rs Outdated
Comment thread pgdog/src/backend/schema/cache/mod.rs
Comment thread pgdog/src/backend/schema/cache/mod.rs
Comment thread pgdog/src/backend/schema/cache/mod.rs
Comment thread pgdog/src/backend/schema/cache/mod.rs Outdated
levkk and others added 2 commits July 24, 2026 10:58
pub regex_parser_limit: usize,
pub pub_sub_enabled: bool,
pub identity: &'a Option<String>,
name: &'a str,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meskill Ignore this diff, just removed pub.

@levkk
levkk requested a review from meskill July 24, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants