[Django] #28586: Automatically prefetch related for "to one" fields as needed.

62 views
Skip to first unread message

Django

unread,
Sep 11, 2017, 4:01:14 PM9/11/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon | Owner: nobody
Wrigley |
Type: New | Status: new
feature |
Component: Database | Version: master
layer (models, ORM) |
Severity: Normal | Keywords: prefetch_related
Triage Stage: | Has patch: 0
Unreviewed |
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
-------------------------------------+-------------------------------------
When accessing a 2one field (foreign key in the forward direction and
one2one in either direction) on a model instance, if the fields value has
not yet been loaded then Django should prefetch the field for all model
instances loaded by the same queryset as the current model instance.

There has been some discussion of this on the mailing list
https://groups.google.com/forum/#!topic/django-developers/EplZGj-ejvg

Currently when accessing an uncached 2one field Django will automatically
fetch the missing value from the Database. When this occurs in a loop it
creates 1+N query problems. Consider the following snippet:

{{{#!python
for choice in Choice.objects.all():
print(choice.question.question_text, ':', choice.choice_text)
}}}

This will do one query for the choices and then one query per choice to
get that choice's question.
This behavior can be avoided with correct application of prefetch_related
like this:

{{{#!python
for choice in Choice.objects.prefetch_related('question'):
print(choice.question.question_text, ':', choice.choice_text)
}}}

This has several usability issues, notably:
* Less experienced users are generally not aware that it's necessary.
* Cosmetic seeming changes to things like templates can change the fields
that should be prefetched.
* Related to that the code that requires the prefetch_related (template
for example) may be quite removed from where the prefetch_related needs to
be applied (view for example).
* Subsequently finding where prefetch_related calls are missing is non
trivial and needs to be done on an ongoing basis.
* Excess fields in prefetch_related calls are even harder to find and
result in unnecessary database queries.
* It is very difficult for libraries like the admin and Django Rest
Framework to automatically generate correct prefetch_related clauses.

The proposal is on the first iteration of the loop in the example above,
when we first access a choice's question field instead of fetching the
question for just that choice, speculatively fetch the questions for all
the choices returned by the queryset.
This change results in the first snippet having the same database behavior
as the second while reducing or eliminating all of the noted usability
issues.

Some important points:
* 2many fields are not changed at all by this proposal as I can't think of
a reasonable way of deciding which of the many to fetch
* Because these are 2one fields the generated queries can't have more
result rows than the original query and may have less.
* This feature will never result in more database queries.
* It will not change the DB behavior of code which is full covered by
prefetch_related (and select_related) calls at all.
* This will inherently chain across relations like choice.question.author,
the conditions above still hold under such chaining.
* It may result in larger data transfer between the database and Django in
some situations.

On that last point an example would be this:
{{{#!python
qs = Choice.objects.all()
list(qs)[0].question
}}}
Such examples generally seem to be rarer and more likely to be visible
during code inspection (vs {{choice.question}} in a template). And larger
queries are usually a better failure mode than producing hundreds of
queries.
For this to actually produce inferior behavior in practice you need to:
a. fetch a large number of choices
b. filter out basically all of them
c. in a way that prevents garbage collection of the unfiltered ones
If any of those aren't true then automatic prefetching will still produce
equivalent or better database behavior than without.

Several optin/optout options were discussed in the mailing list, I will
attempt to summarize these below. Most of them are compatible with each
other, however in the interests of having a clean interface we probably
want to limit how many we implement.
1. A global option in settings. So as to not accidentally fix existing
code this could default to disabled if not specified.
2. Per queryset either as auto_prefetch_related(value) or
prefetch_related(auto=value) where value would determine enabled,
disabled, default.
3. Per object, similar to the per queryset version.
4. Per model in meta, it's not clear if this was intended to be on
a. the model used in the original queryset
b. the model the field is on
c. the model the field refers to
5. As a context manager (this could then easily be applied in middleware
or a view decorator)
6. On the field, similar to on_delete


P.S. I've been using this in my own code with no optin / optout for
sometime and have had literally no problems with it.

--
Ticket URL: <https://code.djangoproject.com/ticket/28586>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Sep 11, 2017, 4:02:06 PM9/11/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Gordon Wrigley):

I hope to have a first version of a pull for this up tomorrow

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:1>

Django

unread,
Sep 11, 2017, 4:40:52 PM9/11/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Adam (Chainz) Johnson):

* cc: Adam (Chainz) Johnson (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:2>

Django

unread,
Sep 12, 2017, 12:17:07 PM9/12/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Gordon Wrigley):

Since there was some discussion over optin / optout strategies I have for
the moment gone with one that seems safe and easy to implement. So
currently the feature is off by default and enabled by calling
auto_prefetch_related() on a queryset.

Related to that I have not addressed documentation at all.

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:3>

Django

unread,
Sep 12, 2017, 12:27:04 PM9/12/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Gordon Wrigley):

For curiosity sake I tried running the test suite with
auto_prefetch_related enabled by default. There were 3 test failures, two
were looking for queries that are removed by auto_prefetch_related.
The third attempts to fetch more rows than the sqlite backend can handle
in a single 'in' clause, which I'd think is an issue with the 'in'
implementation.
Looking at the test it is currently unintentionally doing some four and a
quarter thousand DB queries. And attempting to fix it with an explicit
prefetch fails in the exact same manner as the automatic prefetch.

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:4>

Django

unread,
Sep 12, 2017, 3:18:02 PM9/12/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Ryan Hiebert):

* cc: Ryan Hiebert (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:5>

Django

unread,
Sep 13, 2017, 5:10:59 AM9/13/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Description changed by Gordon Wrigley:

Old description:

New description:

When accessing a 2one field (foreign key in the forward direction and

one2one in either direction) on a model instance, if the field's value has


not yet been loaded then Django should prefetch the field for all model
instances loaded by the same queryset as the current model instance.

There has been some discussion of this on the mailing list
https://groups.google.com/forum/#!topic/django-developers/EplZGj-ejvg

Currently when accessing an uncached 2one field, Django will automatically

when we first access a choice's question field, instead of fetching the


question for just that choice, speculatively fetch the questions for all
the choices returned by the queryset.
This change results in the first snippet having the same database behavior
as the second while reducing or eliminating all of the noted usability
issues.

Some important points:
* 2many fields are not changed at all by this proposal as I can't think of

a reasonable way of deciding which of the many to fetch.


* Because these are 2one fields the generated queries can't have more

result rows than the original query and may have less. This eliminates any
concern about a multiplicative query size explosion.
* This feature will never result in more database queries as a prefetch
will only be issued where the ORM was already going to fetch a related
object.
* Because it is triggered by fetching missing related objects it will not
at all change the DB behavior of code which is full covered by
prefetch_related (and select_related) calls.


* This will inherently chain across relations like choice.question.author,
the conditions above still hold under such chaining.
* It may result in larger data transfer between the database and Django in
some situations.

An example of that last point is:

--

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:6>

Django

unread,
Sep 13, 2017, 5:31:11 AM9/13/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Ed Morley):

* cc: Ed Morley (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:7>

Django

unread,
Sep 24, 2017, 5:19:35 AM9/24/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jonas Haag):

* cc: Jonas Haag (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:8>

Django

unread,
Oct 6, 2017, 1:10:50 PM10/6/17
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Tim Graham):

* needs_docs: 0 => 1
* has_patch: 0 => 1
* stage: Unreviewed => Accepted


Comment:

[https://github.com/django/django/pull/9064 PR]

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:9>

Django

unread,
Mar 25, 2020, 6:38:22 AM3/25/20
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: master
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------

Comment (by Gordon Wrigley):

My existing code for this is now available as a pypi package
https://github.com/tolomea/django-auto-prefetch

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:10>

Django

unread,
Nov 28, 2023, 12:36:05 PM11/28/23
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev

(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Adam Johnson):

* owner: nobody => Adam Johnson
* needs_docs: 1 => 0
* has_patch: 1 => 0
* status: new => assigned


Comment:

I’m working on a PR for Django core now, based on
[https://github.com/django/django/pull/16090/files Andreas Pelme’s
recently-closed PR] and discussions with Andreas and Simon Charette.

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:11>

Django

unread,
Dec 19, 2023, 4:46:27 AM12/19/23
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0

Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by şuayip üzülmez):

* cc: şuayip üzülmez (added)


--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:12>

Django

unread,
Mar 3, 2025, 8:33:42 AMMar 3
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* has_patch: 0 => 1

Comment:

Adam invites additional reviews on
[https://github.com/django/django/pull/17554 PR]
--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:13>

Django

unread,
Apr 15, 2025, 7:05:01 PMApr 15
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Adam Johnson):

I split off a [https://github.com/django/django/pull/19381 PR for
GenericForeignKey] which should be simpler to review.
--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:14>

Django

unread,
Aug 29, 2025, 5:28:58 PM (7 days ago) Aug 29
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 1
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_better_patch: 0 => 1
* needs_docs: 0 => 1

Comment:

Main PR has some outstanding questions and merge conflicts. GFK PR needs a
deprecation path, I think.
--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:15>

Django

unread,
Sep 5, 2025, 3:37:37 PM (11 hours ago) Sep 5
to django-...@googlegroups.com
#28586: Automatically prefetch related for "to one" fields as needed.
-------------------------------------+-------------------------------------
Reporter: Gordon Wrigley | Owner: Adam
| Johnson
Type: New feature | Status: assigned
Component: Database layer | Version: dev
(models, ORM) |
Severity: Normal | Resolution:
Keywords: prefetch_related | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 1
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Jacob Walls):

* needs_docs: 1 => 0

--
Ticket URL: <https://code.djangoproject.com/ticket/28586#comment:16>
Reply all
Reply to author
Forward
0 new messages