Django REST Framework ์๋ฆฌ์ผ๋ผ์ด์ ์ฌ์ธต ๋ถ์: ์ ํจ์ฑ ๊ฒ์ฌ, ์ค์ฒฉ ๋ฐ N+1 ๋ฌธ์
DRF ์๋ฆฌ์ผ๋ผ์ด์ ์ ๊ณ ๊ธ ๊ธฐ๋ฒ์ ์์ธํ ๋ค๋ฃน๋๋ค. ์ปค์คํ ์ ํจ์ฑ ๊ฒ์ฌ, ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ , N+1 ๋ฌธ์ ํด๊ฒฐ ๋ฐฉ๋ฒ, ์ฑ๋ฅ ์ต์ ํ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ํฌ๊ด์ ์ผ๋ก ์ค๋ช ํฉ๋๋ค.

Django REST Framework(DRF)์์ ์๋ฆฌ์ผ๋ผ์ด์ ๋ API์ ์ ์ถ๋ ฅ์ ์ ์ดํ๋ ํต์ฌ ์ปดํฌ๋ํธ์ ๋๋ค. 2026๋ ํ์ฌ, DRF 3.15 ์ดํ ๋ฒ์ ์์๋ ์๋ฆฌ์ผ๋ผ์ด์ ๊ธฐ๋ฅ์ด ๋์ฑ ๊ฐํ๋์ด ์ ์ฐํ๊ณ ํจ์จ์ ์ธ API ๊ฐ๋ฐ์ด ๊ฐ๋ฅํด์ก์ต๋๋ค. ๋ณธ ๋ฌธ์์์๋ ์๋ฆฌ์ผ๋ผ์ด์ ์ ์ ํจ์ฑ ๊ฒ์ฌ, ์ค์ฒฉ ๊ตฌ์กฐ ์ฒ๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ํ๋ก๋์ ํ๊ฒฝ์์ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋๋ N+1 ์ฟผ๋ฆฌ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์ธํ ์ค๋ช ํฉ๋๋ค. ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ๊น์ด ์ดํดํ๋ฉด ๊ฒฌ๊ณ ํ๊ณ ์ฑ๋ฅ์ด ๋ฐ์ด๋ RESTful API๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
DRF์ ์๋ฆฌ์ผ๋ผ์ด์ ๋ Python ๊ฐ์ฒด(์ฃผ๋ก Django ๋ชจ๋ธ)์ JSON/XML ๋ฑ์ ๋ฐ์ดํฐ ํ์์ ์ํธ ๋ณํํ๋ ์ปดํฌ๋ํธ์ ๋๋ค. Django์ ํผ๊ณผ ์ ์ฌํ ์ญํ ์ ์ํํ๋ฉฐ, ๋ฐ์ดํฐ์ ์ ํจ์ฑ ๊ฒ์ฌ, ์๋ฆฌ์ผ๋ผ์ด์ฆ(๊ฐ์ฒดโJSON), ๋์๋ฆฌ์ผ๋ผ์ด์ฆ(JSONโ๊ฐ์ฒด)๋ฅผ ๋ด๋นํฉ๋๋ค. ์ ์ ํ ์ค๊ณ๋ ์๋ฆฌ์ผ๋ผ์ด์ ๋ API์ ์ผ๊ด์ฑ๊ณผ ๋ณด์์ ๋ณด์ฅํฉ๋๋ค.
๊ธฐ๋ณธ ์๋ฆฌ์ผ๋ผ์ด์ ๊ตฌ์กฐ
DRF์์๋ Serializer์ ModelSerializer ๋ ๊ฐ์ง ๊ธฐ๋ณธ ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ModelSerializer๋ Django ๋ชจ๋ธ์ ๋์ํ๋ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ๋น ๋ฅด๊ฒ ์์ฑํ ์ ์์ผ๋ฉฐ, Serializer๋ ์์ ํ ์ปค์คํ
ํ ๊ตฌ์กฐ๋ฅผ ์ ์ํ ๋ ์ฌ์ฉํฉ๋๋ค.
# serializers.py
from rest_framework import serializers
from .models import Article, Author, Tag
class AuthorSerializer(serializers.ModelSerializer):
"""Author model serializer with computed field"""
full_name = serializers.SerializerMethodField()
class Meta:
model = Author
fields = ['id', 'first_name', 'last_name', 'full_name', 'email', 'bio']
read_only_fields = ['id']
def get_full_name(self, obj):
return f"{obj.first_name} {obj.last_name}"
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug']SerializerMethodField๋ฅผ ์ฌ์ฉํ๋ฉด ๋ชจ๋ธ์ ์กด์ฌํ์ง ์๋ ๊ณ์ฐ ํ๋๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. read_only_fields๋ฅผ ์ง์ ํ๋ฉด ํน์ ํ๋๊ฐ ์
๋ฐ์ดํธ ์ ๋ฌด์๋๋๋ก ๋ณด์ฅํฉ๋๋ค.
์ปค์คํ ์ ํจ์ฑ ๊ฒ์ฌ ๊ตฌํ
DRF์ ์ ํจ์ฑ ๊ฒ์ฌ๋ ์ธ ๊ฐ์ง ๋ ๋ฒจ์์ ์คํ๋ฉ๋๋ค: ํ๋ ๋ ๋ฒจ, ๊ฐ์ฒด ๋ ๋ฒจ, ๊ทธ๋ฆฌ๊ณ ๋ฐธ๋ฆฌ๋ฐ์ดํฐ ํด๋์ค์ ๋๋ค. ์ด๋ค์ ์กฐํฉํ์ฌ ๋ณต์กํ ๋น์ฆ๋์ค ๊ท์น์ ๊ตฌํํ ์ ์์ต๋๋ค.
# serializers.py
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
from django.utils import timezone
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
"""Article serializer with multi-level validation"""
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'content', 'author', 'tags',
'published_at', 'status', 'view_count']
read_only_fields = ['id', 'view_count', 'slug']
validators = [
UniqueTogetherValidator(
queryset=Article.objects.all(),
fields=['author', 'title'],
message="This author already has an article with this title."
)
]
# Field-level validation
def validate_title(self, value):
"""Ensure title meets requirements"""
if len(value) < 10:
raise serializers.ValidationError(
"Title must be at least 10 characters long."
)
if value.isupper():
raise serializers.ValidationError(
"Title cannot be all uppercase."
)
return value
def validate_published_at(self, value):
"""Prevent publishing in the past"""
if value and value < timezone.now():
raise serializers.ValidationError(
"Publication date cannot be in the past."
)
return value
# Object-level validation
def validate(self, attrs):
"""Cross-field validation"""
status = attrs.get('status')
published_at = attrs.get('published_at')
content = attrs.get('content', '')
# Published articles must have publication date
if status == 'published' and not published_at:
raise serializers.ValidationError({
'published_at': 'Publication date is required for published articles.'
})
# Published articles need minimum content length
if status == 'published' and len(content) < 100:
raise serializers.ValidationError({
'content': 'Published articles must have at least 100 characters.'
})
return attrsํ๋ ๋ ๋ฒจ ์ ํจ์ฑ ๊ฒ์ฌ๋ validate_<field_name> ๋ฉ์๋๋ก ์ ์ํ๋ฉฐ, ๋จ์ผ ํ๋ ๊ฒ์ฆ์ ์ฌ์ฉํฉ๋๋ค. ๊ฐ์ฒด ๋ ๋ฒจ ์ ํจ์ฑ ๊ฒ์ฌ๋ validate ๋ฉ์๋๋ก ์ ์ํ๋ฉฐ, ์ฌ๋ฌ ํ๋ ๊ฐ์ ์ ํฉ์ฑ ๊ฒ์ฌ์ ์ฌ์ฉํฉ๋๋ค. UniqueTogetherValidator์ ๊ฐ์ ๋ฐธ๋ฆฌ๋ฐ์ดํฐ ํด๋์ค๋ Meta.validators์์ ์ ์ธ์ ์ผ๋ก ์ง์ ํฉ๋๋ค.
์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ์ฒ๋ฆฌ
API์์๋ ๊ด๋ จ ๊ฐ์ฒด๋ฅผ ์ค์ฒฉํ์ฌ ๋ฐํํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์ ์ ํ ๊ตฌํํ๋ฉด ํด๋ผ์ด์ธํธ๋ ํ ๋ฒ์ ์์ฒญ์ผ๋ก ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
# serializers.py
from rest_framework import serializers
from .models import Article, Author, Tag, Comment
class CommentSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.username', read_only=True)
class Meta:
model = Comment
fields = ['id', 'content', 'author_name', 'created_at']
class ArticleDetailSerializer(serializers.ModelSerializer):
"""Detailed article serializer with nested relations"""
author = AuthorSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
comment_count = serializers.IntegerField(read_only=True)
# Write-only fields for creating/updating
author_id = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
source='author',
write_only=True
)
tag_ids = serializers.PrimaryKeyRelatedField(
queryset=Tag.objects.all(),
source='tags',
many=True,
write_only=True
)
class Meta:
model = Article
fields = [
'id', 'title', 'slug', 'content',
'author', 'author_id',
'tags', 'tag_ids',
'comments', 'comment_count',
'published_at', 'status'
]
read_only_fields = ['id', 'slug']์ด ์ค๊ณ์์๋ ์ฝ๊ธฐ ์์๋ ์์ ํ ๊ฐ์ฒด(author, tags)๋ฅผ ๋ฐํํ๊ณ , ์ฐ๊ธฐ ์์๋ ID(author_id, tag_ids)๋ฅผ ๋ฐ์ต๋๋ค. source ํ๋ผ๋ฏธํฐ๋ก ์๋ฆฌ์ผ๋ผ์ด์ ํ๋์ ๋ชจ๋ธ ํ๋๋ฅผ ๋งคํํ ์ ์์ต๋๋ค.
์ฐ๊ธฐ ๊ฐ๋ฅํ ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์
๊ธฐ๋ณธ์ ์ผ๋ก ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ๋ ์ฝ๊ธฐ ์ ์ฉ์
๋๋ค. ์ฐ๊ธฐ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ ค๋ฉด create์ update ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํด์ผ ํฉ๋๋ค.
# serializers.py
from django.db import transaction
from rest_framework import serializers
from .models import Order, OrderItem, Product
class OrderItemSerializer(serializers.ModelSerializer):
product_name = serializers.CharField(source='product.name', read_only=True)
subtotal = serializers.DecimalField(
max_digits=10, decimal_places=2, read_only=True
)
class Meta:
model = OrderItem
fields = ['id', 'product', 'product_name', 'quantity', 'price', 'subtotal']
read_only_fields = ['id', 'price', 'subtotal']
class OrderSerializer(serializers.ModelSerializer):
"""Order serializer with writable nested items"""
items = OrderItemSerializer(many=True)
total_amount = serializers.DecimalField(
max_digits=10, decimal_places=2, read_only=True
)
class Meta:
model = Order
fields = ['id', 'customer', 'items', 'total_amount',
'status', 'created_at', 'updated_at']
read_only_fields = ['id', 'total_amount', 'created_at', 'updated_at']
def validate_items(self, value):
"""Ensure order has at least one item"""
if not value:
raise serializers.ValidationError(
"Order must have at least one item."
)
return value
@transaction.atomic
def create(self, validated_data):
"""Create order with nested items"""
items_data = validated_data.pop('items')
order = Order.objects.create(**validated_data)
for item_data in items_data:
product = item_data['product']
item_data['price'] = product.price # Set current price
OrderItem.objects.create(order=order, **item_data)
return order
@transaction.atomic
def update(self, instance, validated_data):
"""Update order and replace items"""
items_data = validated_data.pop('items', None)
# Update order fields
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
# Replace items if provided
if items_data is not None:
instance.items.all().delete()
for item_data in items_data:
product = item_data['product']
item_data['price'] = product.price
OrderItem.objects.create(order=instance, **item_data)
return instance@transaction.atomic ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ถ๋ชจ ๊ฐ์ฒด์ ์์ ๊ฐ์ฒด์ ์์ฑ์ด ํ๋์ ํธ๋์ญ์
์ผ๋ก ์คํ๋์ด ๋ฐ์ดํฐ ์ ํฉ์ฑ์ด ๋ณด์ฅ๋ฉ๋๋ค.
Django ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
N+1 ๋ฌธ์ ์ ์ดํด์ ํด๊ฒฐ
N+1 ๋ฌธ์ ๋ ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์ฌ์ฉํ ๋ ๊ฐ์ฅ ํํ ๋ฐ์ํ๋ ์ฑ๋ฅ ๋ฌธ์ ์ ๋๋ค. ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก N๊ฐ์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์จ ํ, ๊ฐ ๊ฐ์ฒด์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด N๋ฒ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
# views.py - N+1 problem example
from rest_framework import generics
from .models import Article
from .serializers import ArticleDetailSerializer
# BAD: This causes N+1 queries
class ArticleListView(generics.ListAPIView):
queryset = Article.objects.all() # N+1 problem!
serializer_class = ArticleDetailSerializer์ ์ฝ๋์์๋ ๊ธฐ์ฌ ๋ชฉ๋ก์ ๊ฐ์ ธ์ฌ ๋ ๊ฐ ๊ธฐ์ฌ์ ์์ฑ์์ ํ๊ทธ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ์คํ๋ฉ๋๋ค. 100๊ฐ์ ๊ธฐ์ฌ๊ฐ ์๋ค๋ฉด ์๋ฐฑ ๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
select_related์ prefetch_related๋ฅผ ํตํ ํด๊ฒฐ
# views.py - Optimized queries
from rest_framework import generics
from django.db.models import Count, Prefetch
from .models import Article, Comment
from .serializers import ArticleDetailSerializer
class ArticleListView(generics.ListAPIView):
serializer_class = ArticleDetailSerializer
def get_queryset(self):
return Article.objects.select_related(
'author' # ForeignKey - single JOIN
).prefetch_related(
'tags', # ManyToMany - separate query
Prefetch(
'comments',
queryset=Comment.objects.select_related('author')
.order_by('-created_at')[:5] # Latest 5 comments
)
).annotate(
comment_count=Count('comments') # Computed in DB
).order_by('-published_at')select_related๋ ForeignKey์ OneToOne ๊ด๊ณ์ ์ฌ์ฉํ๋ฉฐ, JOIN์ ํตํด ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. prefetch_related๋ ManyToMany์ ์ญ๋ฐฉํฅ ForeignKey ๊ด๊ณ์ ์ฌ์ฉํ๋ฉฐ, ๋ณ๋์ ์ฟผ๋ฆฌ๋ก ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ์ผ๊ด ์กฐํํฉ๋๋ค. Prefetch ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ๊ด๋ จ ๋ฐ์ดํฐ์ ์ฟผ๋ฆฌ๋ฅผ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์์ต๋๋ค.
์ปค์คํ Manager๋ฅผ ํตํ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ต์ ํ
# models.py
from django.db import models
from django.db.models import Count, Prefetch
class ArticleQuerySet(models.QuerySet):
def with_relations(self):
"""Eager load common relations"""
return self.select_related(
'author'
).prefetch_related(
'tags'
)
def with_comments(self, limit=5):
"""Include latest comments"""
from .models import Comment
return self.prefetch_related(
Prefetch(
'comments',
queryset=Comment.objects.select_related('author')
.order_by('-created_at')[:limit]
)
)
def with_stats(self):
"""Include computed statistics"""
return self.annotate(
comment_count=Count('comments', distinct=True),
tag_count=Count('tags', distinct=True)
)
def published(self):
"""Filter to published articles only"""
return self.filter(status='published')
class ArticleManager(models.Manager):
def get_queryset(self):
return ArticleQuerySet(self.model, using=self._db)
def with_relations(self):
return self.get_queryset().with_relations()
def published_with_details(self):
"""Common query for public article list"""
return self.get_queryset().published().with_relations().with_stats()
class Article(models.Model):
# ... fields ...
objects = ArticleManager()์ปค์คํ
QuerySet๊ณผ Manager๋ฅผ ์ฌ์ฉํ๋ฉด ์ต์ ํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ๋ฉ์๋๋ก ์บก์ํํ ์ ์์ต๋๋ค. ๋ทฐ์์๋ Article.objects.published_with_details()๋ฅผ ํธ์ถํ๊ธฐ๋ง ํ๋ฉด ์ ์ ํ ์ต์ ํ๋ ์ฟผ๋ฆฌ๊ฐ ์คํ๋ฉ๋๋ค.
์๋ฆฌ์ผ๋ผ์ด์ ์ฑ๋ฅ ์ต์ ํ
N+1 ๋ฌธ์ ์ธ์๋ ์๋ฆฌ์ผ๋ผ์ด์ ์์ฒด์ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค.
# serializers.py
from rest_framework import serializers
from .models import Article
class ArticleListSerializer(serializers.ModelSerializer):
"""Lightweight serializer for list views"""
author_name = serializers.CharField(source='author.full_name', read_only=True)
tag_names = serializers.SlugRelatedField(
many=True, read_only=True, slug_field='name', source='tags'
)
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'author_name', 'tag_names',
'published_at', 'comment_count']
class ArticleDetailSerializer(serializers.ModelSerializer):
"""Full serializer for detail views"""
author = AuthorSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
comments = CommentSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'content', 'author', 'tags',
'comments', 'published_at', 'status', 'view_count']๋ชฉ๋ก ์กฐํ์ฉ๊ณผ ์์ธ ์กฐํ์ฉ์ผ๋ก ์๋ก ๋ค๋ฅธ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ถํ์ํ ๋ฐ์ดํฐ ์๋ฆฌ์ผ๋ผ์ด์ฆ๋ฅผ ํผํ ์ ์์ต๋๋ค. ๋ชฉ๋ก ์กฐํ์์๋ ๊ฒฝ๋ํ๋ ArticleListSerializer๋ฅผ, ์์ธ ์กฐํ์์๋ ์์ ํ ArticleDetailSerializer๋ฅผ ์ฌ์ฉํฉ๋๋ค.
๋์ ํ๋ ์ ํ
ํด๋ผ์ด์ธํธ๊ฐ ํ์ํ ํ๋๋ง ์์ฒญํ ์ ์๋๋ก ํ๋ฉด ์ถ๊ฐ์ ์ธ ์ต์ ํ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
# serializers.py
class DynamicFieldsSerializer(serializers.ModelSerializer):
"""Base serializer with dynamic field selection"""
def __init__(self, *args, **kwargs):
fields = kwargs.pop('fields', None)
exclude = kwargs.pop('exclude', None)
super().__init__(*args, **kwargs)
if fields is not None:
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
if exclude is not None:
for field_name in exclude:
self.fields.pop(field_name, None)
class ArticleSerializer(DynamicFieldsSerializer):
author = AuthorSerializer(read_only=True)
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'slug', 'content', 'author', 'tags',
'published_at', 'status']
# views.py - Usage
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
def get_serializer(self, *args, **kwargs):
# Allow ?fields=id,title,author query param
fields_param = self.request.query_params.get('fields')
if fields_param:
kwargs['fields'] = fields_param.split(',')
return super().get_serializer(*args, **kwargs)DRF ๋ฉด์ ์์ ์์ฃผ ๋ฌป๋ ์ง๋ฌธ
2026๋ Django ๊ฐ๋ฐ์ ๋ฉด์ ์์๋ DRF ์๋ฆฌ์ผ๋ผ์ด์ ์ ๋ํ ๊น์ ์ดํด๊ฐ ์๊ตฌ๋ฉ๋๋ค. ๋ค์์ ์์ฃผ ์ถ์ ๋๋ ์ง๋ฌธ๊ณผ ๋ต๋ณ์ ๋๋ค.
Q1: Serializer์ ModelSerializer์ ์ฐจ์ด์ ์ ๋ฌด์์ด๋ฉฐ, ๊ฐ๊ฐ ์ธ์ ์ฌ์ฉํฉ๋๊น?
ModelSerializer๋ Django ๋ชจ๋ธ์ ๋์ํ๋ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. ํ๋ ์ ์, ๋ฐธ๋ฆฌ๋ฐ์ดํฐ, create/update ๋ฉ์๋๊ฐ ๋ชจ๋ธ์์ ์ถ๋ก ๋ฉ๋๋ค. ๋ฐ๋ฉด Serializer๋ ์์ ํ ์๋์ผ๋ก ์ ์ํด์ผ ํ๋ฉฐ, ๋ชจ๋ธ์ ๋์ํ์ง ์๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ(๋ก๊ทธ์ธ ํผ, ๊ฒ์ ์ฟผ๋ฆฌ ๋ฑ)์ ์ฌ์ฉํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅํ์ง ์๋ ๋ฐ์ดํฐ์ ๊ฒ์ฆ, ์ฌ๋ฌ ๋ชจ๋ธ์ ์กฐํฉํ ๋ณต์กํ ๊ตฌ์กฐ, ์ธ๋ถ API์์ ์ฐ๋ ๋ฑ์์๋ Serializer๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๋๋ถ๋ถ์ CRUD ์์
์์๋ ModelSerializer๋ก ์ถฉ๋ถํฉ๋๋ค.
Q2: ์๋ฆฌ์ผ๋ผ์ด์ ์ ์ ํจ์ฑ ๊ฒ์ฌ๊ฐ ์คํ๋๋ ์์๋ฅผ ์ค๋ช ํด ์ฃผ์ญ์์ค.
DRF์ ์ ํจ์ฑ ๊ฒ์ฌ๋ ๋ค์ ์์๋ก ์คํ๋ฉ๋๋ค. (1) ํ๋์ to_internal_value ๋ฉ์๋์ ์ํ ํ์
๋ณํ ๋ฐ ์ ํจ์ฑ ๊ฒ์ฌ. (2) ๊ฐ ํ๋์ ๋ฐธ๋ฆฌ๋ฐ์ดํฐ(validators ์์ฑ) ์คํ. (3) validate_<field_name> ๋ฉ์๋์ ์ํ ํ๋ ๋ ๋ฒจ ์ ํจ์ฑ ๊ฒ์ฌ. (4) Meta.validators์์ ์ ์๋ ๋ฐธ๋ฆฌ๋ฐ์ดํฐ ์คํ. (5) validate ๋ฉ์๋์ ์ํ ๊ฐ์ฒด ๋ ๋ฒจ ์ ํจ์ฑ ๊ฒ์ฌ. ์ด๋ ๋จ๊ณ์์๋ ValidationError๊ฐ ๋ฐ์ํ๋ฉด ๋๋จธ์ง ์ ํจ์ฑ ๊ฒ์ฌ๋ ์คํ๋์ง ์์ต๋๋ค.
Q3: N+1 ๋ฌธ์ ๋ ๋ฌด์์ด๋ฉฐ, DRF์์ ์ด๋ป๊ฒ ํด๊ฒฐํฉ๋๊น?
N+1 ๋ฌธ์ ๋ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก N๊ฐ์ ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์จ ํ, ๊ฐ ๊ฐ์ฒด์ ๊ด๋ จ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด N๋ฒ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ์
๋๋ค. 100๊ฐ์ ๊ธฐ์ฌ์ ๊ฐ ๊ธฐ์ฌ์ ์์ฑ์๋ฅผ ๊ฐ์ ธ์ฌ ๊ฒฝ์ฐ, 1(๊ธฐ์ฌ ๋ชฉ๋ก)+100(๊ฐ ์์ฑ์)=101๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํฉ๋๋ค. ํด๊ฒฐ ๋ฐฉ๋ฒ์ผ๋ก, ForeignKey ๊ด๊ณ์๋ select_related๋ฅผ ์ฌ์ฉํ์ฌ JOIN์ผ๋ก ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํตํฉํ๊ณ , ManyToMany ๊ด๊ณ์๋ prefetch_related๋ฅผ ์ฌ์ฉํ์ฌ ๋ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค. ๋ทฐ์ get_queryset ๋ฉ์๋์์ ์ ์ ํ ํ๋ฆฌํ์น๋ฅผ ์ค์ ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
Q4: ์ฐ๊ธฐ ๊ฐ๋ฅํ ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ๊ตฌํํ ๋ ์ฃผ์ํ ์ ์ ๋ฌด์์ ๋๊น?
๊ธฐ๋ณธ์ ์ผ๋ก ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ ๋ ์ฝ๊ธฐ ์ ์ฉ์
๋๋ค. ์ฐ๊ธฐ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค๋ ค๋ฉด create์ update ๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํด์ผ ํฉ๋๋ค. ์ฃผ์ํ ์ ์ผ๋ก, (1) ํธ๋์ญ์
์ ์ฌ์ฉํ์ฌ ๋ถ๋ชจ-์์ ๊ฐ์ฒด์ ์ ํฉ์ฑ์ ๋ณด์ฅํฉ๋๋ค. (2) ์
๋ฐ์ดํธ ์ ๊ธฐ์กด ์์ ๊ฐ์ฒด๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง(์ญ์ ํ ์ฌ์์ฑ, ๋ณํฉ ๋ฑ)๋ฅผ ๋ช
ํํ ํฉ๋๋ค. (3) ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ ์ ํ ์ค์ฒฉํฉ๋๋ค. (4) ์ฑ๋ฅ์ ๊ณ ๋ คํ์ฌ bulk_create๋ bulk_update ์ฌ์ฉ์ ๊ฒํ ํฉ๋๋ค.
Q5: source ํ๋ผ๋ฏธํฐ์ ์ฉ๋์ ์ฌ์ฉ ์์๋ฅผ ์ค๋ช
ํด ์ฃผ์ญ์์ค.
source ํ๋ผ๋ฏธํฐ๋ ์๋ฆฌ์ผ๋ผ์ด์ ํ๋๋ฅผ ๋ชจ๋ธ์ ๋ค๋ฅธ ์์ฑ์ ๋งคํํฉ๋๋ค. ์ฉ๋๋ก๋, (1) ํ๋๋ช
๋ณ๊ฒฝ: author_email = serializers.EmailField(source='author.email'). (2) ๊ด๋ จ ๊ฐ์ฒด์ ์์ฑ ์ ๊ทผ: category_name = serializers.CharField(source='category.name'). (3) ๋ฉ์๋ ํธ์ถ: full_name = serializers.CharField(source='get_full_name'). (4) ์ฝ๊ธฐ/์ฐ๊ธฐ ํ๋ ๋ถ๋ฆฌ: ์ฝ๊ธฐ ์์๋ author(๊ฐ์ฒด), ์ฐ๊ธฐ ์์๋ author_id(ID) ์ฌ์ฉ. source='*'๋ฅผ ์ง์ ํ๋ฉด ๊ฐ์ฒด ์ ์ฒด๊ฐ ์ ๋ฌ๋ฉ๋๋ค.
ํ๋ก๋์ ํ๊ฒฝ์์์ ๋ชจ๋ฒ ์ฌ๋ก
ํ๋ก๋์ ํ๊ฒฝ์์ DRF ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์ฌ์ฉํ ๋๋ ๋ค์์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
# serializers.py - Production patterns
from rest_framework import serializers
from django.core.cache import cache
from .models import Article
class CachedAuthorSerializer(serializers.ModelSerializer):
"""Serializer with caching for expensive computations"""
article_count = serializers.SerializerMethodField()
class Meta:
model = Author
fields = ['id', 'full_name', 'email', 'article_count']
def get_article_count(self, obj):
cache_key = f'author_{obj.id}_article_count'
count = cache.get(cache_key)
if count is None:
count = obj.articles.filter(status='published').count()
cache.set(cache_key, count, timeout=3600) # 1 hour
return count๊ณ์ฐ ๋น์ฉ์ด ๋์ ํ๋์๋ ์บ์๋ฅผ ์ ์ฉํ์ฌ ์๋ต ์๊ฐ์ ๊ฐ์ ํฉ๋๋ค. ๋ํ Django์ ๊ณ ๊ธ ORM ๊ธฐ๋ฒ์ ํ์ฉํ๋ฉด ์ถ๊ฐ์ ์ธ ์ต์ ํ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. Python์ ๋น๋๊ธฐ ๊ธฐ๋ฅ๊ณผ ๊ฒฐํฉํ๋ฉด ๋์ ๋ถํ์ API์์๋ ํ์ฅ ๊ฐ๋ฅํ ์ฑ๋ฅ์ ์คํํ ์ ์์ต๋๋ค.
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
๊ฒฐ๋ก
Django REST Framework์ ์๋ฆฌ์ผ๋ผ์ด์ ๋ API์ ํ์ง๊ณผ ์ฑ๋ฅ์ ๊ฒฐ์ ํ๋ ์ค์ํ ์ปดํฌ๋ํธ์ ๋๋ค. ๋ณธ ๋ฌธ์์์ ์ค๋ช ํ ์ฃผ์ ๋ด์ฉ์ ์ ๋ฆฌํฉ๋๋ค.
- ์ ํจ์ฑ ๊ฒ์ฌ: ํ๋ ๋ ๋ฒจ, ๊ฐ์ฒด ๋ ๋ฒจ, ๋ฐธ๋ฆฌ๋ฐ์ดํฐ ํด๋์ค์ 3๊ณ์ธต์ผ๋ก ๊ฒฌ๊ณ ํ ๋ฐ์ดํฐ ๊ฒ์ฆ์ ๊ตฌํํฉ๋๋ค
- ์ค์ฒฉ ์๋ฆฌ์ผ๋ผ์ด์ : ์ฝ๊ธฐ์ ์ฐ๊ธฐ์์ ์๋ก ๋ค๋ฅธ ํ๋๋ฅผ ์ฌ์ฉํ๊ณ ,
create/update๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ์ฌ ์ฐ๊ธฐ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค - N+1 ๋ฌธ์ :
select_related์prefetch_related๋ฅผ ์ ์ ํ ์ฌ์ฉํ์ฌ ์ฟผ๋ฆฌ ์๋ฅผ ์ต์ํํฉ๋๋ค - ์ฑ๋ฅ ์ต์ ํ: ๋ชฉ๋ก์ฉ๊ณผ ์์ธ์ฉ์ผ๋ก ์๋ก ๋ค๋ฅธ ์๋ฆฌ์ผ๋ผ์ด์ ๋ฅผ ์ฌ์ฉํ๊ณ , ๋์ ํ๋ ์ ํ์ผ๋ก ์๋ต ํฌ๊ธฐ๋ฅผ ์ค์ ๋๋ค
์๋ฆฌ์ผ๋ผ์ด์ ์ ๋ํ ๊น์ ์ดํด๋ 2026๋ Django ๊ฐ๋ฐ์ ๋ฉด์ ์์ ์ค์ํ ์ฐจ๋ณํ ์์๊ฐ ๋ฉ๋๋ค. ๋ณธ ๋ฌธ์์ ์ฝ๋ ์์ ์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ์ค์ตํ๋ฉด ํ๋ก๋์ ์์ค์ RESTful API๋ฅผ ๊ตฌ์ถํ๋ ์ญ๋์ ๊ฐ์ถ ์ ์์ต๋๋ค.
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

Django ๋ฉด์ ์ง๋ฌธ: ORM, ๋ฏธ๋ค์จ์ด, DRF ์ฌ์ธต ๋ถ์
Django ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ ORM ์ต์ ํ(select_related์ prefetch_related), ๋ฏธ๋ค์จ์ด ์ํคํ ์ฒ, Django REST Framework ์๋ฆฌ์ผ๋ผ์ด์ ์ฑ๋ฅ, ๊ถํ ์ค์ , ํ์ด์ง๋ค์ด์ ํจํด์ ์์ธํ ๋ค๋ฃน๋๋ค.

2026๋ Django ๋น๋๊ธฐ ๋ทฐ์ ASGI: ์ฑ๋ฅ๊ณผ ๋ฉด์ ์ง๋ฌธ
2026๋ Django ๋น๋๊ธฐ ๋ทฐ์ ASGI ์ฌ์ธต ๋ถ์: ๋ด๋ถ ๋์ ์๋ฆฌ, ๋ฐฐํฌํ ์๋ฒ ์ ํ, ๋น๋๊ธฐ ORM๊ณผ SynchronousOnlyOperation ํจ์ , ๊ทธ๋ฆฌ๊ณ ๋ฉด์ ์ง๋ฌธ๊น์ง ๋ค๋ฃน๋๋ค.

2026๋ Django์ PostgreSQL: ์ธ๋ฑ์ฑ, ์ ๋ฌธ ๊ฒ์, ๋ฉด์ ์ง๋ฌธ
Django PostgreSQL ์ต์ ํ ์ค์ ๊ฐ์ด๋: B-tree, ๋ถ๋ถ ์ธ๋ฑ์ค, ์ปค๋ฒ๋ง ์ธ๋ฑ์ค, SearchVector์ GIN์ ํ์ฉํ ์ ๋ฌธ ๊ฒ์, ๊ทธ๋ฆฌ๊ณ 2026๋ ๋ฉด์ ์ง๋ฌธ๊น์ง ๋ค๋ฃน๋๋ค.