-
Notifications
You must be signed in to change notification settings - Fork 1
feat: cached prepared queries for postgres #68
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
base: main
Are you sure you want to change the base?
Conversation
| def __init__(self, db_name: str): | ||
| self.db_name = db_name | ||
| self.db_url = os.path.join(env.PG_DB_URL, db_name) | ||
| self.db_url = f"{env.PG_DB_URL.rstrip('/')}/{db_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.
Note: os.path.join technically works on Unix OS, but it is not the right way to use it, better use simple f strings
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.
Really? That's surprising, why are f strings preferred to using os.path.join?
| conn._mlpa_stmt_cache = stmt_cache | ||
| if query not in stmt_cache: | ||
| stmt_cache[query] = await conn.prepare(query) | ||
| return stmt_cache[query] |
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.
Won't this blow up the memory of our pods? Each request is stored until pod restart right? Is there a max_store variable we could add? Maybe store for up to 1 day? What do you think?
| counter, | ||
| ) | ||
| stmt = await self._get_prepared_statement(conn, query) | ||
| await stmt.execute(key_id_b64, counter) |
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.
This is one example of SQL where I don't think it makes sense to cache, this is a very unique query that really ever only runs once given the key_id_b64 and counter unique pair
| await self.pg.execute( | ||
| "DELETE FROM challenges WHERE key_id_b64 = $1", key_id_b64 | ||
| ) | ||
| async with self.pg.acquire() as conn: |
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 would also recommend maybe leave caching off of this one? Deleting a challenge is sort of a 1-time operation
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.
Unless the caching takes place before the key_id_b64 is added? Or is the value added to the string, and then it's cached?
| key_id_b64, | ||
| challenge, | ||
| ) | ||
| stmt = await self._get_prepared_statement(conn, query) |
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.
This is another I'd recommend no caching on given on how unique each request is, unless I'm misunderstanding how the caching works
What's new:
Prepared statements pre-parse and cache query execution plans on the database server, eliminating parsing overhead on repeated queries.
Instead of parsing the SQL string on every execution, the database reuses the prepared plan.
Benchmarks:
I've tested it locally with 100k random users
methods were used in the benchmark:
Performance improvement: