Skip to main content

๐Ÿ“š Django ๋‚ด๋ถ€ ๋™์ž‘ ์›๋ฆฌ์™€ Best Practice

1. Django ORM (์ฟผ๋ฆฌ์…‹ ์ตœ์ ํ™”)โ€‹

Django ORM(Object-Relational Mapper)์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๊ฐ์ฒด ์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด ๊ฐ„์˜ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€ํ™˜ํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ORM์„ ์‚ฌ์šฉํ•˜๋ฉด SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ํŒŒ์ด์ฌ ์ฝ”๋“œ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ORM์„ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด ์„ฑ๋Šฅ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŠนํžˆ N+1 ๋ฌธ์ œ๊ฐ€ ๋Œ€ํ‘œ์ ์ž…๋‹ˆ๋‹ค.

N+1 ๋ฌธ์ œ๋ž€?โ€‹

N+1 ๋ฌธ์ œ๋Š” ํ•˜๋‚˜์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ N๊ฐœ์˜ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๋Š” ๋น„ํšจ์œจ์ ์ธ ์ƒํ™ฉ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜จ ํ›„ ๊ฐ ๊ฒŒ์‹œ๊ธ€์˜ ์ž‘์„ฑ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด ๊ฒŒ์‹œ๊ธ€ ์ˆ˜๋งŒํผ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค.

# N+1 ๋ฌธ์ œ ๋ฐœ์ƒ ์˜ˆ์‹œ
posts = Post.objects.all() # ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ (์ฟผ๋ฆฌ 1ํšŒ)
for post in posts:
print(post.author.username) # ๊ฐ ๊ฒŒ์‹œ๊ธ€์˜ ์ž‘์„ฑ์ž ์ •๋ณด ์ ‘๊ทผ ์‹œ๋งˆ๋‹ค ์ฟผ๋ฆฌ ๋ฐœ์ƒ (NํšŒ)

์ด๋Ÿฌํ•œ N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Django ORM์€ select_related์™€ prefetch_related๋ผ๋Š” ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ์ตœ์ ํ™” ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

select_related๋Š” ์ •๋ฐฉํ–ฅ ForeignKey ๋˜๋Š” OneToOneField ๊ด€๊ณ„์˜ ๊ฐ์ฒด๋“ค์„ SQL JOIN์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ์ฆ‰, ๊ด€๋ จ๋œ ๊ฐ์ฒด๋“ค์„ "์„ ํƒ์ ์œผ๋กœ" ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜์—ฌ ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋ฐœ์ƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์‹œ์ :

  • ํ•˜๋‚˜์˜ ๊ฐ์ฒด์™€ ๊ด€๋ จ๋œ ๋‹ค๋ฅธ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ (One-to-One, Foreign Key)
  • JOIN์œผ๋กœ ์ธํ•ด ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ํฌ๊ธฐ๊ฐ€ ๋„ˆ๋ฌด ์ปค์ง€์ง€ ์•Š์„ ๋•Œ

์‚ฌ์šฉ๋ฒ•:

# Post ๋ชจ๋ธ์ด author๋ผ๋Š” ForeignKey ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
posts = Post.objects.select_related('author').all()
for post in posts:
print(post.author.username) # ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋ฐœ์ƒ ์•ˆ ํ•จ

์ฃผ์˜์‚ฌํ•ญ:

  • ManyToManyField๋‚˜ ์—ญ๋ฐฉํ–ฅ ForeignKey ๊ด€๊ณ„์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (prefetch_related ์‚ฌ์šฉ)
  • ๋„ˆ๋ฌด ๋งŽ์€ JOIN์€ ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์„ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ•„์š”ํ•œ ๊ด€๊ณ„๋งŒ ์ง€์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

prefetch_related๋Š” ManyToManyField ๋˜๋Š” ์—ญ๋ฐฉํ–ฅ ForeignKey ๊ด€๊ณ„, ๊ทธ๋ฆฌ๊ณ  GenericForeignKey ๊ด€๊ณ„์˜ ๊ฐ์ฒด๋“ค์„ ๋ณ„๋„์˜ ์ฟผ๋ฆฌ๋กœ ๊ฐ€์ ธ์˜จ ํ›„ ํŒŒ์ด์ฌ ๋ ˆ๋ฒจ์—์„œ ์กฐ์ธํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๊ด€๋ จ๋œ ๊ฐ์ฒด๋“ค์„ "๋ฏธ๋ฆฌ ๊ฐ€์ ธ์™€์„œ" ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

์‚ฌ์šฉ ์‹œ์ :

  • ํ•˜๋‚˜์˜ ๊ฐ์ฒด์™€ ๊ด€๋ จ๋œ ์—ฌ๋Ÿฌ ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ (Many-to-Many, ์—ญ๋ฐฉํ–ฅ Foreign Key)
  • select_related๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ด€๊ณ„์ผ ๋•Œ

์‚ฌ์šฉ๋ฒ•:

# Post ๋ชจ๋ธ์— 'tags'๋ผ๋Š” ManyToManyField๊ฐ€ ์žˆ๊ณ , User ๋ชจ๋ธ์ด 'posts'๋ผ๋Š” ์—ญ๋ฐฉํ–ฅ ๊ด€๊ณ„๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
posts = Post.objects.prefetch_related('tags').all()
for post in posts:
print([tag.name for tag in post.tags.all()]) # ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋ฐœ์ƒ ์•ˆ ํ•จ (tags์— ๋Œ€ํ•ด)

users = User.objects.prefetch_related('post_set').all() # User์™€ ๊ด€๋ จ๋œ Post๋“ค์„ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ด
for user in users:
print([p.title for p in user.post_set.all()]) # ์ถ”๊ฐ€ ์ฟผ๋ฆฌ ๋ฐœ์ƒ ์•ˆ ํ•จ

Prefetch ๊ฐ์ฒด ํ™œ์šฉ: prefetch_related๋Š” Prefetch ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ฌ ๋ฐ์ดํ„ฐ์— ๋Œ€ํ•œ ์„ธ๋ถ€์ ์ธ ์ œ์–ด(์˜ˆ: ํŠน์ • ์กฐ๊ฑด์˜ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, ์ •๋ ฌ ์ˆœ์„œ ์ง€์ •)๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

from django.db.models import Prefetch

# ํŠน์ • ์กฐ๊ฑด์— ๋งž๋Š” ํƒœ๊ทธ๋งŒ ๋ฏธ๋ฆฌ ๊ฐ€์ ธ์˜ค๊ธฐ
posts = Post.objects.prefetch_related(
Prefetch('tags', queryset=Tag.objects.filter(is_active=True))
).all()

Best Practice:

  • ํ…œํ”Œ๋ฆฟ์ด๋‚˜ ์ฝ”๋“œ์—์„œ ๊ด€๋ จ๋œ ๊ฐ์ฒด์˜ ์†์„ฑ์— ๋ฐ˜๋ณต์ ์œผ๋กœ ์ ‘๊ทผํ•ด์•ผ ํ•œ๋‹ค๋ฉด select_related ๋˜๋Š” prefetch_related ์‚ฌ์šฉ์„ ์ ๊ทน์ ์œผ๋กœ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • Django Debug Toolbar์™€ ๊ฐ™์€ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹คํ–‰๋˜๋Š” ์ฟผ๋ฆฌ ์ˆ˜๋ฅผ ํ™•์ธํ•˜๊ณ  N+1 ๋ฌธ์ œ๋ฅผ ์ง„๋‹จํ•ฉ๋‹ˆ๋‹ค.
  • ํ•„์š”ํ•œ ํ•„๋“œ๋งŒ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•ด only() ๋˜๋Š” defer()๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค. (๋‹จ, select_related์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜. select_related๋กœ ๊ฐ€์ ธ์˜จ ํ•„๋“œ๋„ only์— ๋ช…์‹œํ•ด์•ผ ํ•จ)
  • values()๋‚˜ values_list()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋”•์…”๋„ˆ๋ฆฌ๋‚˜ ํŠœํ”Œ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ORM ๊ฐ์ฒด ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด ๊ฒฝ์šฐ select_related๋‚˜ prefetch_related์˜ ํšจ๊ณผ๋ฅผ ์ง์ ‘์ ์œผ๋กœ ๋ˆ„๋ฆฌ๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. (๋Œ€์‹  annotate ๋“ฑ์„ ํ™œ์šฉ)

๊ธฐํƒ€ ์ตœ์ ํ™” ๊ธฐ๋ฒ•โ€‹

  • only() ๋ฐ defer(): ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ฐ€์ ธ์˜ฌ ๋•Œ ํŠน์ • ํ•„๋“œ๋งŒ ์ฆ‰์‹œ ๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ํŠน์ • ํ•„๋“œ์˜ ๋กœ๋“œ๋ฅผ ์ง€์—ฐ์‹œํ‚ต๋‹ˆ๋‹ค.
# 'title'๊ณผ 'author' ํ•„๋“œ๋งŒ ์ฆ‰์‹œ ๋กœ๋“œ
posts = Post.objects.only('title', 'author').all()

# 'content' ํ•„๋“œ๋Š” ๋‚˜์ค‘์— ์ ‘๊ทผํ•  ๋•Œ ๋กœ๋“œ
posts = Post.objects.defer('content').all()
  • values() ๋ฐ values_list(): ์ฟผ๋ฆฌ์…‹ ๊ฒฐ๊ณผ๋ฅผ ๋”•์…”๋„ˆ๋ฆฌ ๋˜๋Š” ํŠœํ”Œ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ORM ๊ฐ์ฒด ์ƒ์„ฑ ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ž…๋‹ˆ๋‹ค. ํŠน์ • ํ•„๋“œ๋งŒ ์„ ํƒํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
# ์ œ๋ชฉ๋งŒ ๋”•์…”๋„ˆ๋ฆฌ ๋ฆฌ์ŠคํŠธ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
titles = Post.objects.values('title')

# ์ œ๋ชฉ๊ณผ ์ž‘์„ฑ์ž ID๋งŒ ํŠœํ”Œ ๋ฆฌ์ŠคํŠธ๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ
data = Post.objects.values_list('title', 'author_id')
  • count(): ๊ฐ์ฒด์˜ ์ˆ˜๋ฅผ ์…€ ๋•Œ len(queryset) ๋Œ€์‹  queryset.count()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. count()๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์—์„œ ํšจ์œจ์ ์œผ๋กœ ๊ฐœ์ˆ˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค.
  • exists(): ํŠน์ • ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋Š” ๊ฐ์ฒด๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•  ๋•Œ queryset.exists()๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๊ณ  ์กด์žฌ ์—ฌ๋ถ€๋งŒ ํ™•์ธํ•˜๋ฏ€๋กœ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.
  • update() ๋ฐ delete(): ์—ฌ๋Ÿฌ ๊ฐ์ฒด๋ฅผ ํ•œ ๋ฒˆ์— ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๋•Œ ์ฟผ๋ฆฌ์…‹์˜ update() ๋˜๋Š” delete() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐœ๋ณ„ ๊ฐ์ฒด๋ฅผ ๋กœ๋“œํ•˜๊ณ  ์ €์žฅํ•˜๋Š” ๊ณผ์ •์„ ์ƒ๋žตํ•˜์—ฌ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค. (๋‹จ, ๋ชจ๋ธ์˜ save(), delete() ๋ฉ”์„œ๋“œ๋‚˜ ๊ด€๋ จ ์‹œ๊ทธ๋„์ด ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ์— ์œ ์˜)
# is_published๊ฐ€ False์ธ ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์„ True๋กœ ๋ณ€๊ฒฝ
Post.objects.filter(is_published=False).update(is_published=True)
  • annotate() ๋ฐ aggregate(): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ˆ˜์ค€์—์„œ ์ง‘๊ณ„ ์—ฐ์‚ฐ(ํ‰๊ท , ํ•ฉ๊ณ„, ๊ฐœ์ˆ˜ ๋“ฑ)์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
from django.db.models import Count, Avg
# ๊ฐ ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ฒŒ์‹œ๊ธ€ ์ˆ˜ ๊ณ„์‚ฐ
Category.objects.annotate(num_posts=Count('post'))
# ๋ชจ๋“  ๊ฒŒ์‹œ๊ธ€์˜ ํ‰๊ท  ํ‰์  ๊ณ„์‚ฐ
Post.objects.aggregate(avg_rating=Avg('rating'))
  • ์ธ๋ฑ์‹ฑ (Indexing): ์ž์ฃผ ๊ฒ€์ƒ‰๋˜๋Š” ํ•„๋“œ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์กฐํšŒ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. ๋ชจ๋ธ ํ•„๋“œ ์˜ต์…˜์—์„œ db_index=True๋ฅผ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ Meta ํด๋ž˜์Šค์— indexes๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
class Post(models.Model):
title = models.CharField(max_length=200, db_index=True) # ํ•„๋“œ์— ์ง์ ‘ ์ธ๋ฑ์Šค ์„ค์ •
# ...
class Meta:
indexes = [
models.Index(fields=['created_at']), # created_at ํ•„๋“œ์— ์ธ๋ฑ์Šค ์ƒ์„ฑ
]
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ๊ด€๋ฆฌ: ์ง€์†์ ์ธ ์—ฐ๊ฒฐ(Persistent connections)์„ ์‚ฌ์šฉํ•˜์—ฌ ๋งค ์š”์ฒญ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ์„ ์ƒ์„ฑํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (Django 3.1๋ถ€ํ„ฐ CONN_MAX_AGE ๊ธฐ๋ณธ๊ฐ’์ด 0์ด ์•„๋‹Œ 300์œผ๋กœ ๋ณ€๊ฒฝ๋˜์–ด ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”)

2. ๋ฏธ๋“ค์›จ์–ด (Middleware)โ€‹

๋ฏธ๋“ค์›จ์–ด๋Š” Django์˜ ์š”์ฒญ/์‘๋‹ต ์ฒ˜๋ฆฌ ๊ณผ์ •์— ๊ฐœ์ž…ํ•˜์—ฌ ์ „์—ญ์ ์ธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” "ํ›…(hook)" ๋˜๋Š” "ํ”Œ๋Ÿฌ๊ทธ์ธ" ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. ๊ฐ ๋ฏธ๋“ค์›จ์–ด ์ปดํฌ๋„ŒํŠธ๋Š” ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ์ „์— ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฏธ๋“ค์›จ์–ด์˜ ๋™์ž‘ ์›๋ฆฌโ€‹

Django ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์„ ๋ฐ›์œผ๋ฉด, settings.py์— ์ •์˜๋œ MIDDLEWARE ์„ค์ •์— ๋”ฐ๋ผ ์ˆœ์„œ๋Œ€๋กœ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค.

  • ์š”์ฒญ ๋‹จ๊ณ„ (Request Phase):

    • ๋ทฐ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ๊ฐ ๋ฏธ๋“ค์›จ์–ด์˜ process_request(request) ๋˜๋Š” __call__(request) (์ƒˆ๋กœ์šด ์Šคํƒ€์ผ ๋ฏธ๋“ค์›จ์–ด) ๋ฉ”์„œ๋“œ๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
    • ์ด ๋‹จ๊ณ„์—์„œ ๋ฏธ๋“ค์›จ์–ด๋Š” ์š”์ฒญ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ์กฐ๊ฑด์— ๋”ฐ๋ผ ์ฆ‰์‹œ HttpResponse๋ฅผ ๋ฐ˜ํ™˜ํ•˜์—ฌ ๋ทฐ ์ฒ˜๋ฆฌ๋ฅผ ์ค‘๋‹จ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด)
  • ๋ทฐ ์ฒ˜๋ฆฌ (View Processing):

    • ๋ชจ๋“  ์š”์ฒญ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ต๊ณผํ•˜๋ฉด Django๋Š” URLconf๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ ์ ˆํ•œ ๋ทฐ๋ฅผ ์ฐพ์•„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    • ๋ทฐ๋Š” HttpResponse ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • ์‘๋‹ต ๋‹จ๊ณ„ (Response Phase):

    • ๋ทฐ๊ฐ€ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด, ๋ฏธ๋“ค์›จ์–ด๋Š” MIDDLEWARE ์„ค์ •์˜ ์—ญ์ˆœ์œผ๋กœ process_response(request, response) ๋˜๋Š” __call__ ๋‚ด๋ถ€์˜ ์‘๋‹ต ์ฒ˜๋ฆฌ ๋กœ์ง์ด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
    • ์ด ๋‹จ๊ณ„์—์„œ ๋ฏธ๋“ค์›จ์–ด๋Š” ์‘๋‹ต ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. (์˜ˆ: Content-Security-Policy ํ—ค๋” ์ถ”๊ฐ€)
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋‹จ๊ณ„ (Exception Phase):

    • ๋ทฐ ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, Django๋Š” MIDDLEWARE ์„ค์ •์˜ ์—ญ์ˆœ์œผ๋กœ ๊ฐ ๋ฏธ๋“ค์›จ์–ด์˜ process_exception(request, exception) ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
    • ์ด ๋ฉ”์„œ๋“œ๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์ƒˆ๋กœ์šด HttpResponse๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜, ์•„๋ฌด๊ฒƒ๋„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์•„ ๋‹ค์Œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋„˜๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒˆ๋กœ์šด ์Šคํƒ€์ผ ๋ฏธ๋“ค์›จ์–ด (Django 1.10+):

    • ํด๋ž˜์Šค ๊ธฐ๋ฐ˜ ๋ฏธ๋“ค์›จ์–ด๋Š” __init__๊ณผ __call__ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. __init__์€ ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜๋ฉฐ, __call__์€ ๊ฐ ์š”์ฒญ๋งˆ๋‹ค ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
class MyNewMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ์ดˆ๊ธฐํ™” ์ฝ”๋“œ (ํ•œ ๋ฒˆ ์‹คํ–‰)

def __call__(self, request):
# ์š”์ฒญ ์ฒ˜๋ฆฌ ์ „ ์ฝ”๋“œ (๋ทฐ ํ˜ธ์ถœ ์ „)
# ์˜ˆ: request.user = ...

response = self.get_response(request) # ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋‚˜ ๋ทฐ ํ˜ธ์ถœ

# ์‘๋‹ต ์ฒ˜๋ฆฌ ํ›„ ์ฝ”๋“œ (๋ทฐ ๋ฐ˜ํ™˜ ํ›„)
# ์˜ˆ: response['X-My-Header'] = 'value'

return response

์ฃผ์š” ๋‚ด์žฅ ๋ฏธ๋“ค์›จ์–ดโ€‹

  • django.middleware.security.SecurityMiddleware: ๋ณด์•ˆ ๊ด€๋ จ ์—ฌ๋Ÿฌ ์„ค์ •์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: X-Content-Type-Options, X-XSS-Protection, Strict-Transport-Security ํ—ค๋” ์„ค์ •, SECURE_SSL_REDIRECT ๋“ฑ)
  • django.contrib.sessions.middleware.SessionMiddleware: ์„ธ์…˜ ๊ด€๋ฆฌ๋ฅผ ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค.
  • django.middleware.common.CommonMiddleware: APPEND_SLASH, PREPEND_WWW ์ฒ˜๋ฆฌ, Content-Length ํ—ค๋” ์„ค์ • ๋“ฑ ์ผ๋ฐ˜์ ์ธ ์›น ์š”์ฒญ/์‘๋‹ต ํŒจํ„ด์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • django.middleware.csrf.CsrfViewMiddleware: CSRF ๊ณต๊ฒฉ ๋ฐฉ์–ด๋ฅผ ์œ„ํ•œ ํ† ํฐ์„ ๊ฒ€์ฆํ•˜๊ณ  ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • django.contrib.auth.middleware.AuthenticationMiddleware: request ๊ฐ์ฒด์— user ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • django.contrib.messages.middleware.MessageMiddleware: ์ผํšŒ์„ฑ ๋ฉ”์‹œ์ง€(์˜ˆ: ์„ฑ๊ณต, ์˜ค๋ฅ˜ ์•Œ๋ฆผ)๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด ์ž‘์„ฑโ€‹

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๊ฑธ์ณ ์ ์šฉํ•˜๊ณ  ์‹ถ์€ ๋กœ์ง์ด ์žˆ๋‹ค๋ฉด ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ: ์š”์ฒญ ์ฒ˜๋ฆฌ ์‹œ๊ฐ„ ์ธก์ • ๋ฏธ๋“ค์›จ์–ด

# myapp/middleware.py
import time

class TimingMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
start_time = time.time()
response = self.get_response(request)
duration = time.time() - start_time
response['X-Page-Generation-Duration-ms'] = str(int(duration * 1000))
print(f"Request to {request.path} took {duration:.4f} seconds")
return response

# settings.py
MIDDLEWARE = [
# ... ๊ธฐ์กด ๋ฏธ๋“ค์›จ์–ด ...
'myapp.middleware.TimingMiddleware', # ์ถ”๊ฐ€
# ... ๊ธฐ์กด ๋ฏธ๋“ค์›จ์–ด ...
]

๋ฏธ๋“ค์›จ์–ด ํ›… (์„ ํƒ์  ๋ฉ”์„œ๋“œ - ๊ตฌํ˜• ์Šคํƒ€์ผ):

  • process_view(request, view_func, view_args, view_kwargs): ๋ทฐ๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ง์ „์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. None์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๋ทฐ ์ฒ˜๋ฆฌ๊ฐ€ ๊ณ„์†๋˜๊ณ , HttpResponse๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ํ•ด๋‹น ์‘๋‹ต์ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • process_template_response(request, response): ๋ทฐ๊ฐ€ ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง์„ ํฌํ•จํ•˜๋Š” TemplateResponse (๋˜๋Š” ์œ ์‚ฌ ๊ฐ์ฒด)๋ฅผ ๋ฐ˜ํ™˜ํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. response.render()๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ์ „์— ์‘๋‹ต์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฏธ๋“ค์›จ์–ด Best Practiceโ€‹

  • ์ˆœ์„œ์˜ ์ค‘์š”์„ฑ: ๋ฏธ๋“ค์›จ์–ด๋Š” MIDDLEWARE ์„ค์ •์— ์ •์˜๋œ ์ˆœ์„œ๋Œ€๋กœ, ๊ทธ๋ฆฌ๊ณ  ์‘๋‹ต ์‹œ์—๋Š” ์—ญ์ˆœ์œผ๋กœ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ๊ธฐ๋Šฅ ๊ฐ„์˜ ์˜์กด์„ฑ์„ ๊ณ ๋ คํ•˜์—ฌ ์ˆœ์„œ๋ฅผ ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    • ์˜ˆ: SessionMiddleware๋Š” AuthenticationMiddleware๋ณด๋‹ค ๋จผ์ € ์™€์•ผ ํ•ฉ๋‹ˆ๋‹ค. (AuthenticationMiddleware๊ฐ€ ์„ธ์…˜์— ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ)
    • ์˜ˆ: SecurityMiddleware๋Š” ๊ฐ€๋Šฅํ•œ ํ•œ ๋ชฉ๋ก์˜ ์•ž์ชฝ์— ๋‘์–ด ๋‹ค๋ฅธ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ „์— ๋ณด์•ˆ ํ—ค๋”๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
  • ์ตœ์†Œํ™”: ๋ถˆํ•„์š”ํ•œ ๋ฏธ๋“ค์›จ์–ด๋Š” ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ๊ผญ ํ•„์š”ํ•œ ๋ฏธ๋“ค์›จ์–ด๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐ€๋ณ๊ฒŒ ์œ ์ง€: ๋ฏธ๋“ค์›จ์–ด๋Š” ๋ชจ๋“  ์š”์ฒญ/์‘๋‹ต์— ๋Œ€ํ•ด ์‹คํ–‰๋˜๋ฏ€๋กœ, ๊ฐ ๋ฏธ๋“ค์›จ์–ด์˜ ๋กœ์ง์€ ์ตœ๋Œ€ํ•œ ๊ฐ€๋ณ๊ณ  ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฌด๊ฑฐ์šด ์ž‘์—…์€ ๋น„๋™๊ธฐ ์ž‘์—…์œผ๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ฉ๋‹ˆ๋‹ค.
  • ํŠน์ • ๊ฒฝ๋กœ์—๋งŒ ์ ์šฉ: ๋ชจ๋“  ๊ฒฝ๋กœ์— ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ ์šฉํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค๋ฉด, ๋ฏธ๋“ค์›จ์–ด ๋‚ด๋ถ€์—์„œ request.path๋ฅผ ํ™•์ธํ•˜์—ฌ ํŠน์ • ๊ฒฝ๋กœ์—๋งŒ ๋กœ์ง์ด ์‹คํ–‰๋˜๋„๋ก ์กฐ๊ฑด๋ถ€ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๋ฌธ์„œํ™”: ์ปค์Šคํ…€ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด, ๊ทธ ๊ธฐ๋Šฅ๊ณผ ์„ค์ • ๋ฐฉ๋ฒ•์„ ๋ช…ํ™•ํžˆ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค.

3. ์‹œ๊ทธ๋„ (Signals)โ€‹

์‹œ๊ทธ๋„์€ Django ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํŠน์ • ์ง€์ ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์•ก์…˜์— ๋Œ€ํ•ด ๋‹ค๋ฅธ ๊ณณ์—์„œ ์•Œ๋ฆผ์„ ๋ฐ›๊ณ  ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋””์ปคํ”Œ๋ง(decoupling) ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ์ฆ‰, ํŠน์ • ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ(sender) ๋“ฑ๋ก๋œ ์ˆ˜์‹ ๊ธฐ(receiver) ํ•จ์ˆ˜๋“ค์ด ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.

์‹œ๊ทธ๋„์˜ ๋™์ž‘ ์›๋ฆฌโ€‹

  1. ์‹œ๊ทธ๋„ ์ •์˜ (Signal Definition):

    • Django๋Š” ์—ฌ๋Ÿฌ ๋‚ด์žฅ ์‹œ๊ทธ๋„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: ๋ชจ๋ธ ์ €์žฅ ์ „/ํ›„, ์š”์ฒญ ์‹œ์ž‘/์ข…๋ฃŒ ๋“ฑ)
    • ํ•„์š”์— ๋”ฐ๋ผ ์ปค์Šคํ…€ ์‹œ๊ทธ๋„์„ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
    from django.dispatch import Signal
    my_signal = Signal() # ์ปค์Šคํ…€ ์‹œ๊ทธ๋„ ์ •์˜
  2. ์ˆ˜์‹ ๊ธฐ ํ•จ์ˆ˜ ์ž‘์„ฑ (Receiver Function):

    • ์‹œ๊ทธ๋„์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์‹คํ–‰๋  ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” sender์™€ ์‹œ๊ทธ๋„๋ณ„ ์ธ์ž๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    def my_receiver_function(sender, **kwargs):
    print(f"Signal received from {sender} with arguments: {kwargs}")
    # ... ์‹œ๊ทธ๋„ ์ฒ˜๋ฆฌ ๋กœ์ง ...
  3. ์‹œ๊ทธ๋„์— ์ˆ˜์‹ ๊ธฐ ์—ฐ๊ฒฐ (Connecting Receiver to Signal):

    • Signal.connect() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ @receiver ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜์‹ ๊ธฐ ํ•จ์ˆ˜๋ฅผ ํŠน์ • ์‹œ๊ทธ๋„์— ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.contrib.auth.models import User

    # ๋ฐฉ๋ฒ• 1: connect() ๋ฉ”์„œ๋“œ ์‚ฌ์šฉ
    def user_saved_receiver(sender, instance, created, **kwargs):
    if created:
    print(f"New user created: {instance.username}")
    else:
    print(f"User updated: {instance.username}")

    post_save.connect(user_saved_receiver, sender=User)

    # ๋ฐฉ๋ฒ• 2: @receiver ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์‚ฌ์šฉ
    @receiver(post_save, sender=User)
    def another_user_saved_receiver(sender, instance, created, **kwargs):
    if created:
    print(f"Another receiver: New user {instance.username} joined!")
  4. ์‹œ๊ทธ๋„ ๋ฐœ์ƒ (Sending Signal):

    • ๋‚ด์žฅ ์‹œ๊ทธ๋„์€ Django ํ”„๋ ˆ์ž„์›Œํฌ ๋‚ด๋ถ€์—์„œ ์ ์ ˆํ•œ ์‹œ์ ์— ์ž๋™์œผ๋กœ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค๊ฐ€ ์ €์žฅ๋  ๋•Œ pre_save, post_save ์‹œ๊ทธ๋„ ๋ฐœ์ƒ)
    • ์ปค์Šคํ…€ ์‹œ๊ทธ๋„์€ Signal.send() ๋˜๋Š” Signal.send_robust() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    # my_signal.send(sender=self.__class__, arg1='hello', arg2='world')
    • send_robust(): ์ˆ˜์‹ ๊ธฐ ํ•จ์ˆ˜์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  ๋ชจ๋“  ์ˆ˜์‹ ๊ธฐ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

๋‚ด์žฅ ์‹œ๊ทธ๋„โ€‹

Django๋Š” ๋‹ค์–‘ํ•œ ๋‚ด์žฅ ์‹œ๊ทธ๋„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ฃผ์š” ์‹œ๊ทธ๋„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋ชจ๋ธ ์‹œ๊ทธ๋„ (django.db.models.signals):
    • pre_save / post_save: ๋ชจ๋ธ์˜ save() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „/ํ›„์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • pre_delete / post_delete: ๋ชจ๋ธ์˜ delete() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „/ํ›„์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • pre_init / post_init: ๋ชจ๋ธ์˜ __init__() ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „/ํ›„์— ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • m2m_changed: ManyToManyField๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ์„ ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ์š”์ฒญ/์‘๋‹ต ์‹œ๊ทธ๋„ (django.core.signals):
    • request_started / request_finished: HTTP ์š”์ฒญ ์ฒ˜๋ฆฌ ์‹œ์ž‘/์ข…๋ฃŒ ์‹œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
    • got_request_exception: ์š”์ฒญ ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ํ…œํ”Œ๋ฆฟ ๋ Œ๋”๋ง ์‹œ๊ทธ๋„ (django.test.signals):
    • template_rendered: ํ…Œ์ŠคํŠธ ์ค‘ ํ…œํ”Œ๋ฆฟ์ด ๋ Œ๋”๋ง๋  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹œ๊ทธ๋„ (django.db.backends.signals):
    • connection_created: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์ด ์ฒ˜์Œ ์ƒ์„ฑ๋  ๋•Œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ปค์Šคํ…€ ์‹œ๊ทธ๋„ ์ •์˜ ๋ฐ ์‚ฌ์šฉโ€‹

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ณ ์œ ์˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ปค์Šคํ…€ ์‹œ๊ทธ๋„์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

# myapp/signals.py
from django.dispatch import Signal

# ์ฃผ๋ฌธ ์™„๋ฃŒ ์‹œ๊ทธ๋„ ์ •์˜
# Django 4.0 ๋ถ€ํ„ฐ Signal ์ƒ์„ฑ์ž์—์„œ providing_args๊ฐ€ ์ œ๊ฑฐ๋จ.
# ์ด์ œ๋Š” send ๋ฉ”์„œ๋“œ์—์„œ ์ธ์ž๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์ „๋‹ฌ.
order_completed = Signal()

# myapp/models.py (๋˜๋Š” ๋กœ์ง์ด ์žˆ๋Š” ๊ณณ)
# from .signals import order_completed
#
# class Order(models.Model):
# # ... ํ•„๋“œ๋“ค ...
# def complete_order(self):
# # ... ์ฃผ๋ฌธ ์™„๋ฃŒ ๋กœ์ง ...
# print(f"Order {self.id} completed. Sending signal...")
# order_completed.send(sender=self.__class__, order_id=self.id, user=self.user)

# myapp/receivers.py (๋˜๋Š” apps.py์˜ ready ๋ฉ”์„œ๋“œ)
# from django.dispatch import receiver
# from .signals import order_completed
#
# @receiver(order_completed)
# def send_order_confirmation_email(sender, order_id, user, **kwargs):
# print(f"Receiver: Sending confirmation email for order {order_id} to {user.email}")
# # ... ์ด๋ฉ”์ผ ๋ฐœ์†ก ๋กœ์ง ...

# myapp/apps.py
from django.apps import AppConfig

class MyappConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'

def ready(self):
import myapp.receivers # ์‹œ๊ทธ๋„ ์ˆ˜์‹ ๊ธฐ๋ฅผ ์ž„ํฌํŠธํ•˜์—ฌ ์—ฐ๊ฒฐ

์ค‘์š”: ์‹œ๊ทธ๋„ ์ˆ˜์‹ ๊ธฐ๋Š” Django๊ฐ€ ์‹œ์ž‘๋  ๋•Œ ์ž„ํฌํŠธ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์•ฑ์˜ apps.py ํŒŒ์ผ ๋‚ด AppConfig ํด๋ž˜์Šค์˜ ready() ๋ฉ”์„œ๋“œ ์•ˆ์—์„œ ์ˆ˜์‹ ๊ธฐ ๋ชจ๋“ˆ์„ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค.

์‹œ๊ทธ๋„ Best Practiceโ€‹

  • ๊ผญ ํ•„์š”ํ•  ๋•Œ๋งŒ ์‚ฌ์šฉ: ์‹œ๊ทธ๋„์€ ์ฝ”๋“œ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด์ง€๋งŒ, ๊ณผ๋„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ ํ๋ฆ„์„ ์ถ”์ ํ•˜๊ธฐ ์–ด๋ ต๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ช…ํ™•ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ๋ชจ๋ธ ๋ฉ”์„œ๋“œ๋‚˜ ์„œ๋น„์Šค ๊ณ„์ธต์—์„œ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ˆ˜์‹ ๊ธฐ ํ•จ์ˆ˜๋Š” ๊ฐ€๋ณ๊ฒŒ: ์‹œ๊ทธ๋„ ์ˆ˜์‹ ๊ธฐ๋Š” ๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ, ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์€ ๋น„๋™๊ธฐ ์ž‘์—…(์˜ˆ: Celery)์œผ๋กœ ์œ„์ž„ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • sender ์ง€์ •: ๊ฐ€๋Šฅํ•˜๋‹ค๋ฉด connect() ๋ฉ”์„œ๋“œ๋‚˜ @receiver ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์—์„œ sender ์ธ์ž๋ฅผ ์ง€์ •ํ•˜์—ฌ ํŠน์ • ๋ชจ๋ธ์ด๋‚˜ ํด๋ž˜์Šค์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์‹œ๊ทธ๋„์—๋งŒ ๋ฐ˜์‘ํ•˜๋„๋ก ์ œํ•œํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋ถˆํ•„์š”ํ•œ ์ˆ˜์‹ ๊ธฐ ํ˜ธ์ถœ์„ ์ค„์—ฌ ์„ฑ๋Šฅ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.
  • ๋ฉฑ๋“ฑ์„ฑ(Idempotency): ์ˆ˜์‹ ๊ธฐ ํ•จ์ˆ˜๋Š” ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœ๋˜์–ด๋„ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋‚ด๋„๋ก ๋ฉฑ๋“ฑ์„ฑ์„ ๊ฐ€์ง€๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. (์˜ˆ: ๊ฐ์ฒด๊ฐ€ ์ด๋ฏธ ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ)
  • ํŠธ๋žœ์žญ์…˜ ์ฃผ์˜: post_save ์‹œ๊ทธ๋„ ํ•ธ๋“ค๋Ÿฌ ๋‚ด์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”ํ•œ ์ž‘์—…์€ transaction.on_commit()์„ ์‚ฌ์šฉํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ํ›„์— ์‹คํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
from django.db import transaction

@receiver(post_save, sender=MyModel)
def my_handler(sender, instance, **kwargs):
def do_something_on_commit():
# ์ด ์ž‘์—…์€ ํŠธ๋žœ์žญ์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ํ›„์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
print(f"Transaction committed for {instance.id}")
transaction.on_commit(do_something_on_commit)
  • ์‹œ๊ทธ๋„ ์—ฐ๊ฒฐ ์œ„์น˜: ์•ฑ์˜ apps.py ๋‚ด ready() ๋ฉ”์„œ๋“œ์—์„œ ์‹œ๊ทธ๋„ ์ˆ˜์‹ ๊ธฐ๋ฅผ ์ž„ํฌํŠธํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ฑ์ด ๋กœ๋“œ๋  ๋•Œ ์‹œ๊ทธ๋„์ด ์•ˆ์ •์ ์œผ๋กœ ์—ฐ๊ฒฐ๋ฉ๋‹ˆ๋‹ค.
  • ๋ฌธ์„œํ™”: ์–ด๋–ค ์‹œ๊ทธ๋„์ด ์–ธ์ œ, ์™œ ์‚ฌ์šฉ๋˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ๋ฌธ์„œํ™”ํ•ฉ๋‹ˆ๋‹ค.

4. ์บ์‹ฑ ์ „๋žต (Caching Strategies)โ€‹

์บ์‹ฑ์€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๊ฑฐ๋‚˜ ์ƒ์„ฑ ๋น„์šฉ์ด ๋น„์‹ผ ๋ฐ์ดํ„ฐ๋ฅผ ์ž„์‹œ ์ €์žฅ์†Œ์— ์ €์žฅํ•ด๋‘๊ณ , ๋‹ค์Œ ์š”์ฒญ ์‹œ ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ๊ณผ ์‘๋‹ต ์†๋„๋ฅผ ํ–ฅ์ƒ์‹œํ‚ค๋Š” ์ค‘์š”ํ•œ ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

์บ์‹ฑ์˜ ์ค‘์š”์„ฑโ€‹

  • ์‘๋‹ต ์‹œ๊ฐ„ ๋‹จ์ถ•: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๋‚˜ ๋ณต์žกํ•œ ์—ฐ์‚ฐ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹œ์—์„œ ๋ฐ”๋กœ ๊ฐ€์ ธ์˜ค๋ฏ€๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋น ๋ฅธ ์‘๋‹ต์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋‚˜ ๋‹ค๋ฅธ ๋ฐฑ์—”๋“œ ์‹œ์Šคํ…œ์œผ๋กœ์˜ ์š”์ฒญ ์ˆ˜๋ฅผ ์ค„์—ฌ ์„œ๋ฒ„ ์ž์› ์‚ฌ์šฉ๋ฅ ์„ ๋‚ฎ์ถฅ๋‹ˆ๋‹ค.
  • ํ™•์žฅ์„ฑ ํ–ฅ์ƒ: ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ์ค„์–ด๋“ค๋ฉด ๋” ๋งŽ์€ ๋™์‹œ ์‚ฌ์šฉ์ž๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

Django ์บ์‹œ ํ”„๋ ˆ์ž„์›Œํฌโ€‹

Django๋Š” ๋‹ค์–‘ํ•œ ์ˆ˜์ค€์—์„œ ์บ์‹ฑ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์œ ์—ฐํ•œ ์บ์‹œ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. settings.py์˜ CACHES ์„ค์ •์„ ํ†ตํ•ด ์บ์‹œ ๋ฐฑ์—”๋“œ์™€ ์˜ต์…˜์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', # ์˜ˆ: Memcached ์‚ฌ์šฉ
'LOCATION': '127.0.0.1:11211',
},
'another_cache': { # ์—ฌ๋Ÿฌ ์บ์‹œ ์„ค์ • ๊ฐ€๋Šฅ
'BACKEND': 'django.core.cache.backends.redis.RedisCache', # ์˜ˆ: Redis ์‚ฌ์šฉ
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}

์บ์‹ฑ ์ˆ˜์ค€โ€‹

Django์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์ˆ˜์ค€์—์„œ ์บ์‹ฑ์„ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ํ…œํ”Œ๋ฆฟ ์กฐ๊ฐ ์บ์‹ฑ (Template Fragment Caching):

    • ํ…œํ”Œ๋ฆฟ์˜ ํŠน์ • ๋ถ€๋ถ„์„ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค. {% load cache %} ํ›„ {% cache <timeout_seconds> <cache_key_name> [optional_args...] %} ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • ๋™์ ์œผ๋กœ ๋ณ€ํ•˜๋Š” ๋ถ€๋ถ„์ด ์ ์€ ํ…œํ”Œ๋ฆฟ ์กฐ๊ฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    {% load cache %}
    ...
    {% cache 500 sidebar request.user.username %}
    <p>์•ˆ๋…•ํ•˜์„ธ์š”, {{ user.username }}!</p>
    <p>์ž์ฃผ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์‚ฌ์ด๋“œ๋ฐ” ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.</p>
    {% endcache %}
    ...
  • ๋ทฐ ์บ์‹ฑ (View Caching):

    • ๊ฐœ๋ณ„ ๋ทฐ์˜ ์ถœ๋ ฅ์„ ์บ์‹ฑํ•ฉ๋‹ˆ๋‹ค. @cache_page(timeout_seconds) ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    • URL๋ณ„๋กœ ์บ์‹ฑ๋˜๋ฉฐ, ๋™์ผํ•œ URL ์š”์ฒญ์— ๋Œ€ํ•ด ์บ์‹œ๋œ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    from django.views.decorators.cache import cache_page

    @cache_page(60 * 15) # 15๋ถ„ ๋™์•ˆ ์บ์‹œ
    def my_view(request):
    # ... ๋น„์šฉ์ด ๋งŽ์ด ๋“œ๋Š” ์—ฐ์‚ฐ ...
    return HttpResponse(...)
    • URLconf์—์„œ cache_page๋ฅผ ์ ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‚ฌ์ดํŠธ ์ „์ฒด ์บ์‹ฑ (Site-wide Caching):

    • WorkspaceFromCacheMiddleware์™€ UpdateCacheMiddleware๋ฅผ MIDDLEWARE ์„ค์ •์˜ ์ฒ˜์Œ๊ณผ ๋์— ์ถ”๊ฐ€ํ•˜์—ฌ ์‚ฌ์ดํŠธ ์ „์ฒด ํŽ˜์ด์ง€๋ฅผ ์บ์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ์ •์ ์ธ ์‚ฌ์ดํŠธ๋‚˜ ๊ฑฐ์˜ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๋Š” ์ฝ˜ํ…์ธ ์— ์ ํ•ฉํ•˜์ง€๋งŒ, ์‚ฌ์šฉ์ž๋ณ„ ์ฝ˜ํ…์ธ ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ฃผ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ์ €์ˆ˜์ค€ ์บ์‹œ API (Low-level Cache API):

    • ๊ฐ€์žฅ ์œ ์—ฐํ•œ ์บ์‹ฑ ๋ฐฉ์‹์œผ๋กœ, ์ฝ”๋“œ ๋‚ด์—์„œ ์ง์ ‘ ์บ์‹œ ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ณ  ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • django.core.cache.cache (๋˜๋Š” caches['cache_name'])๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    from django.core.cache import cache

    def get_expensive_data():
    cache_key = 'my_expensive_data'
    data = cache.get(cache_key)
    if data is None:
    # ๋ฐ์ดํ„ฐ๊ฐ€ ์บ์‹œ์— ์—†์œผ๋ฉด ์ƒ์„ฑ (์˜ˆ: DB ์กฐํšŒ, API ํ˜ธ์ถœ)
    data = # ... ๋น„์‹ผ ์—ฐ์‚ฐ ...
    cache.set(cache_key, data, timeout=3600) # 1์‹œ๊ฐ„ ๋™์•ˆ ์บ์‹œ
    return data
    • ์ฃผ์š” ๋ฉ”์„œ๋“œ: set(key, value, timeout), get(key, default=None), add(key, value, timeout) (ํ‚ค๊ฐ€ ์—†์„ ๋•Œ๋งŒ ์ถ”๊ฐ€), delete(key), clear() ๋“ฑ.

์บ์‹œ ๋ฐฑ์—”๋“œโ€‹

Django๋Š” ๋‹ค์–‘ํ•œ ์บ์‹œ ๋ฐฑ์—”๋“œ๋ฅผ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

  • Memcached (django.core.cache.backends.memcached): ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๊ณ ์„ฑ๋Šฅ ๋ถ„์‚ฐ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ์‹œ์Šคํ…œ์ž…๋‹ˆ๋‹ค. PyMemcacheCache ๋˜๋Š” PyLibMCCache ์‚ฌ์šฉ.
  • Redis (django.core.cache.backends.redis): ์ธ๋ฉ”๋ชจ๋ฆฌ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ €์žฅ์†Œ๋กœ, ์บ์‹œ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค, ์„ธ์…˜ ์ €์žฅ์†Œ ๋“ฑ ๋‹ค์–‘ํ•˜๊ฒŒ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค. django-redis ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•„์š”.
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์บ์‹ฑ (django.core.cache.backends.db.DatabaseCache): ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ ์บ์‹œ ์ €์žฅ์†Œ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„ค์ •์€ ๊ฐ„ํŽธํ•˜์ง€๋งŒ ์„ฑ๋Šฅ์€ ๋‹ค๋ฅธ ๋ฐฑ์—”๋“œ์— ๋น„ํ•ด ๋–จ์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŒŒ์ผ ์‹œ์Šคํ…œ ์บ์‹ฑ (django.core.cache.backends.filebased.FileBasedCache): ํŒŒ์ผ ์‹œ์Šคํ…œ์— ์บ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ์ž‘์€ ์‚ฌ์ดํŠธ์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.
  • ๋กœ์ปฌ ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ (django.core.cache.backends.locmem.LocMemCache): ํŒŒ์ด์ฌ ํ”„๋กœ์„ธ์Šค ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์— ์บ์‹œํ•ฉ๋‹ˆ๋‹ค.