Skip to content

Commit eed79f7

Browse files
authored
docs(backend): add explanation to serializers & get_attrs (#15768)
Add some more flavor to the docs about the `get_attrs` method in the context of serializers, and how it can be used to prevent N+1 queries
1 parent fbe610c commit eed79f7

File tree

1 file changed

+18
-3
lines changed

1 file changed

+18
-3
lines changed

develop-docs/backend/api/serializers.mdx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,28 @@ class ModelSerializer(Serializer):
161161
...
162162
```
163163

164-
**get_attrs Method**
164+
**Using get_attrs to avoid N+1 queries**
165165

166-
Why do this when Django Rest Framework has similar functionality? The `get_attrs` method is the reason. It allows you to do a bulk query versus multiple queries. In our example, instead of calling `ExampleTypes.objects.get(...)` multiple items, I can filter for the ones I want and assign them to the item in question using python. In the case of `attr` dictionary, the `key` is the item itself. and the `value` is a dictionary with the name of the attribute you want to add and it's values.
166+
For API calls that involve serializing multiple objects (for example, several Organization instances), the top-level [`serialize` function](https://github.com/getsentry/sentry/blob/f5bb22601361802007d628a0b3652256c812c7b5/src/sentry/api/serializers/base.py#L30-L80) is designed to optimize database access and avoid N+1 query problems. The process works in two steps:
167+
168+
1. **Batch calls in `get_attrs`**: The function calls the serializer's `get_attrs` method **once**, passing in the entire list of objects that need to be serialized. This is where you should perform any bulk queries. For example, if you need additional related data (like each object's owner), perform a single query here to fetch all the owners for the list of objects, rather than querying per object. Collect everything you need in advance and construct a mapping (dictionary) of attributes, like so: `attrs[item] = {'attribute_name': attribute}`.
169+
170+
2. **Serialize each object**: Then, the `serialize` method is called once per object in the original list, each time receiving the current object and its corresponding attributes as prepared by `get_attrs`. This ensures no extra or repeated queries are made inside `serialize`—all extra data should already be available via the `attrs` mapping.
167171

168172
```python
169-
attrs[item] = {'attribute_name': attribute}
173+
# Top-level serialize function (from base.py)
174+
def serialize(objects, user=None, serializer=None, **kwargs):
175+
# Step 1: get_attrs called ONCE with entire list
176+
attrs = serializer.get_attrs(
177+
item_list=[o for o in objects if o is not None],
178+
user=user,
179+
**kwargs,
180+
)
181+
182+
# Step 2: serialize called ONCE PER OBJECT
183+
return [serializer(o, attrs=attrs.get(o, {}), user=user, **kwargs) for o in objects]
170184
```
185+
This design is why Sentry uses its own serializer pattern instead of something like Django Rest Framework: the explicit use of `get_attrs` allows you to batch-fetch all related data up front, and pass it along efficiently to each per-object serialization call.
171186

172187
**Serialize Method**
173188

0 commit comments

Comments
 (0)