Opened 15 years ago
Closed 12 years ago
#11569 closed Bug (fixed)
django.core.cache.backends.db has bad transaction handling
Reported by: | Glenn Maynard | Owned by: | Aymeric Augustin |
---|---|---|---|
Component: | Core (Cache system) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | gerdemb | Triage Stage: | Accepted |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
(This problem is manifesting in core.cache, but the real problem is in db.transaction, so I've marked this as a DB problem.)
The database backend for caching handles transactions badly.
When inserting a cache key, it searches for an existing cache key. If found, it does an UPDATE. If not found, it does an INSERT. The INSERT case can lead to a constraint violation, if another thread inserts that key between the SELECT and the INSERT. The code expects this, and catches DatabaseError.
This rolls back the whole transaction, including anything the caller is doing. It needs a savepoint for this, so it only rolls back its own work.
To handle this sanely, transaction support really needs to require savepoints, so it can expose a helper to start and commit a nested transaction, eg. db.transaction.run_in_transaction(func):
- If a transaction is started already, start a savepoint; when finished, release the savepoint.
- If a transaction is not started, start one (typically a no-op due to non-autocommit); when finished, commit.
- On exception, rollback whichever it started.
Savepoints are supported by all production-quality databases (including MySQL, Postgres and SQLite), so requiring them in the backend seems reasonable. I've hit the need for nested transactions in Django so many times I had to write my own wrapper to implement this.
Attachments (1)
Change History (10)
comment:1 by , 15 years ago
Triage Stage: | Unreviewed → Design decision needed |
---|
comment:2 by , 15 years ago
comment:3 by , 14 years ago
Severity: | → Normal |
---|---|
Type: | → Bug |
comment:4 by , 13 years ago
Cc: | added |
---|---|
Easy pickings: | unset |
UI/UX: | unset |
by , 13 years ago
Attachment: | cache_db_upsert.patch added |
---|
Patch to Django 1.3 to fix cache database backend from inserting duplicated cache_keys
comment:5 by , 13 years ago
The attached patch modifies the _base_set() function for the database backend so that the INSERT INTO statement will not insert duplicated cache keys. It uses an SQL "trick", but it is standard SQL and should be compatible with all the database backends. I've been using it with success on PostgreSQL.
comment:6 by , 13 years ago
Component: | Database layer (models, ORM) → Core (Cache system) |
---|---|
Triage Stage: | Design decision needed → Accepted |
comment:8 by , 12 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
comment:9 by , 12 years ago
Resolution: | → fixed |
---|---|
Status: | assigned → closed |
This bug is making the DB cache backend unusable. Even moderate load will start raising TransactionManagementError because rollback() is called even if there is no transaction.
In short, the block that catches the DatabaseError either wrecks your transaction (if there is a transaction) as Glenn mentioned or raises another exception (if there isn't a transaction).