Skip to main content

PostgreSQL n+1 문제 해결하기 – psycopg와 Pydantic으로 효율적인 한 번의 조회 구현

Published 3 min readMarkdown

# 📌 서론 – “왜 n+1 문제가 우리를 괴롭히는가?”

데이터베이스와 연동한 웹 애플리케이션을 개발하면 “n+1 문제” 라는 성능 병목을 자주 마주합니다.

예를 들어, 게시글 목록을 10개 가져오고 각 게시글마다 작성자를 다시 조회한다면

총 11개의 쿼리가 실행됩니다. 데이터가 늘어날수록 쿼리 수는 기하급수적으로 증가해

응답 시간이 급격히 느려지고 서버 부하도 커집니다.

이 글에서는 PostgreSQL, psycopg, Pydantic이라는 내가 즐겨 쓰는 세 가지 도구를 활용해

한 번의 SQL만으로 중첩된 객체를 효율적으로 가져오는 방법을 단계별로 설명합니다.

코드 예제와 레포지토리 링크를 통해 바로 실습해볼 수 있으니, 초보자도 안심하고 따라오세요!

핵심 키워드: PostgreSQL, psycopg, Pydantic


# 🛠️ 본론

# 1️⃣ 환경 설정 – 필요한 패키지와 기본 테이블 만들기

bash

# 가상환경 생성 및 활성화

python -m venv venv

source venv/bin/activate

# 패키지 설치

pip install psycopg2-binary pydantic

PostgreSQL에 다음과 같은 간단한 스키마를 만든다고 가정합니다.

sql

CREATE TABLE authors (

    id   SERIAL PRIMARY KEY,

    name TEXT NOT NULL

);

CREATE TABLE posts (

    id        SERIAL PRIMARY KEY,

    title     TEXT NOT NULL,

    content   TEXT,

    author_id INT REFERENCES authors(id)

);

# 2️⃣ Pydantic 모델 정의 – 데이터 검증과 직렬화

python

from pydantic import BaseModel

from typing import List, Optional

class Author(BaseModel):

    id: int

    name: str

class Post(BaseModel):

    id: int

    title: str

    content: Optional[str]

    author: Author          # 중첩된 객체

Pydantic은 타입 검증과 JSON 직렬화를 자동으로 처리해 주어, API 응답을 만들 때 매우 편리합니다.

# 3️⃣ psycopg로 한 번에 데이터 가져오기 – JOIN + JSON_AGG 활용

n+1 문제를 방지하려면 JOIN과 PostgreSQL의 JSON 함수를 조합해 한 번의 쿼리로

게시글과 작성자 정보를 모두 받아와야 합니다.

sql

SELECT

    p.id,

    p.title,

    p.content,

    jsonb_build_object('id', a.id, 'name', a.name) AS author

FROM posts p

JOIN authors a ON a.id = p.author_id;

위 쿼리는 각 행에 author 라는 JSON 객체를 포함시켜 반환합니다. 이제 psycopg로 실행해 보겠습니다.

python

import psycopg2

import json

from pydantic import parse_obj_as

conn = psycopg2.connect(

    dbname='mydb',

    user='myuser',

    password='secret',

    host='localhost'

)

with conn.cursor() as cur:

    cur.execute('''

        SELECT

            p.id,

            p.title,

            p.content,

            jsonb_build_object('id', a.id, 'name', a.name) AS author

        FROM posts p

        JOIN authors a ON a.id = p.author_id;

    ''')

    rows = cur.fetchall()

# psycopg는 JSON을 문자열로 반환하므로 파이썬 dict 로 변환

posts_data = [

    {

        'id': row[0],

        'title': row[1],

        'content': row[2],

        'author': json.loads(row[3])

    }

    for row in rows

]

# Pydantic 모델에 바로 매핑

posts = parse_obj_as(List[Post], posts_data)

for post in posts:

    print(post.json())
  • 한 번의 네트워크 라운드 트립만으로 모든 데이터를 가져옵니다.

  • jsonb_build_object 덕분에 중첩된 객체가 그대로 파이썬 딕셔너리 형태로 전달됩니다.

  • parse_obj_as와 Pydantic 모델을 사용해 타입 안전하게 데이터를 다룰 수 있습니다.

# 4️⃣ 실전 적용 팁 – 대용량 데이터와 페이징

한 번에 수천 건을 가져오면 메모리 사용량이 급증할 수 있습니다. 이를 방지하려면 OFFSET/LIMIT 혹은 키셋 페이징을 적용합니다.

sql

SELECT

    p.id,

    p.title,

    p.content,

    jsonb_build_object('id', a.id, 'name', a.name) AS author

FROM posts p

JOIN authors a ON a.id = p.author_id

ORDER BY p.id

LIMIT 20 OFFSET %s;

또는 WHERE p.id > %s 로 마지막 조회 ID를 기준으로 키셋 페이징을 구현하면

인덱스 활용도가 높아져 성능이 더욱 향상됩니다.

# 5️⃣ 트랜잭션과 오류 처리 – 안전하게 코드 마무리하기

python

try:

    with conn:

        with conn.cursor() as cur:

            cur.execute(sql, (offset,))

            # 위와 동일한 변환 로직

except psycopg2.Error as e:

    print('Database error:', e)

finally:

    conn.close()

with conn: 블록은 자동 커밋/롤백을 보장해 주므로, 예외 발생 시 데이터 무결성을 유지할 수 있습니다.


# 🎯 결론 – 지금 바로 적용해 보세요!

  • n+1 문제는 단순히 쿼리 수를 줄이는 것이 아니라, 네트워크 비용과 서버 부하를 크게 낮춥니다.

  • psycopg와 PostgreSQL의 JSON 기능을 활용하면 복잡한 객체 구조도 한 번에 받아올 수 있습니다.

  • Pydantic은 받은 데이터를 검증·직렬화하는 과정을 자동화해 코드 가독성을 크게 향상시킵니다.

행동 유도: 이번 포스트에 소개된 코드를 직접 실행해 보시고, 여러분의 프로젝트에 맞게 커스터마이징해 보세요. 구현 과정에서 궁금한 점이 있으면 댓글이나 GitHub 이슈를 남겨 주세요. 여러분의 피드백이 더 나은 튜토리얼을 만드는 원동력이 됩니다!


# 📚 참고 자료

  • 공식 레포지토리: https://github.com/yourname/n1-solution-psycopg-pydantic

  • PostgreSQL JSON 함수 문서: https://www.postgresql.org/docs/current/functions-json.html

  • Pydantic 사용 가이드: https://docs.pydantic.dev/


이 글은 PostgreSQL, psycopg, Pydantic을 키워드로 SEO 최적화되었습니다. 검색 엔진에서 “PostgreSQL n+1 문제 해결”, “psycopg JSON 조회”, “Pydantic 데이터 모델링” 등으로 찾아보시면 도움이 될 것입니다.

No comments yet. Be the first to comment.

More from this blog