#30533: Delete cascade can break postgresql database: too large 'DELETE FROM a
WHERE a.b_id IN (...)'
-------------------------------------+-------------------------------------
Reporter: Thomas | Owner: nobody
Riccardi |
Type: Bug | Status: new
Component: Database | Version: 2.0
layer (models, ORM) | Keywords: queryset delete
Severity: Normal | cascade batch bulk_batch_size
Triage Stage: | postgresql crash
Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
== Scenario
- Model A has a foreign key to model B with on_delete=CASCADE
- many B objects
- execute `B.objects.delete()`
Expected result:
- deletion works
Actual result:
- when there are many B objects to delete (in our case it was 14M rows),
the database process gets killed before completion:
{{{
2019-05-28 19:17:42.237 CEST
[1]: [2-1] db=,user= LOG: server process (PID 12911) was terminated by
signal 9: Killed
2019-05-28 19:17:42.238 CEST
[1]: [3-1] db=,user= DETAIL: Failed process was running: DELETE FROM "a"
WHERE "a"."b_id" IN (17271, 17272, 17273, 17274, 17275, <truncated
enormous list>
}}}
- with a smaller database it worked (2M rows)
== Analysis
It seems the related objects A get deleted in one query with an unbound
`IN (...)` list of B objects.
In fact this pattern already lead to an issue with the sqlite backend
([#link0]: sqlite supports only 999 parameters per query)
This was fixed in django 1.8 by adding batch query [#link1], [#link2] with
a size specified per backend:
- sqlite: 999 [#link3]
- oracle: 64k [#link4]
- all others (base): not limited [#link5]
== Workaround
As a temporary workaround we monkey patched the `connection` instance own
`bulk_batch_size` and limit to 64k.
{{{#!python
import types
def monkey_patch_connection_bulk_batch_size(connection):
def limited_bulk_batch_size(self, fields, objs):
"""
PostgreSQL can crash with too many parameters in a query
e.g. 'DELETE FROM x WHERE x.y IN (...large list...)'
=> limit to 64k
"""
return 2**16
connection.ops.bulk_batch_size =
types.MethodType(limited_bulk_batch_size, connection.ops)
}}}
It worked great in our case: we used it in a migration.
workaround limitations:
- no idea where to monkey patch for global usage
- no idea how to choose the bulk size value
- didn't handle the more complex computation using `fields` and `objs`
parameters
(Related remark: this is for deleting the related objects, then for the
main objects deletion django already uses batch deletion, but with much
smaller batch size: `GET_ITERATOR_CHUNK_SIZE = 100`; with some latency to
the database it's a really small value (0.2ms per query))
== Context
- postgresql 9.6 (GCP Cloud SQL)
- django 2.0.13 (but related code has not changed in current master it
seems)
---
link 0 [=#link0] https://code.djangoproject.com/ticket/16426
link 1 [=#link1]
https://github.com/django/django/blob/f6075fb333bae29ee213b050e91eaadef75496dd/django/db/models/deletion.py#L224-L225
link 2 [=#link2]
https://github.com/django/django/blob/f6075fb333bae29ee213b050e91eaadef75496dd/django/db/models/deletion.py#L167
link 3 [=#link3]
https://github.com/django/django/blob/f6075fb333bae29ee213b050e91eaadef75496dd/django/db/backends/sqlite3/operations.py#L27-L40
link 4 [=#link4]
https://github.com/django/django/blob/f6075fb333bae29ee213b050e91eaadef75496dd/django/db/backends/oracle/operations.py#L592-L596
link 5 [=#link5]
https://github.com/django/django/blob/f6075fb333bae29ee213b050e91eaadef75496dd/django/db/backends/base/operations.py#L65-L71
--
Ticket URL: <https://code.djangoproject.com/ticket/30533>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/058.ad44325afe87c8e8d1fc6425228fa695%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.