# PostgreSQL n+1 문제 해결하기 – psycopg와 Pydantic으로 한 번에 중첩 객체 가져오기

> **SEO 키워드**: `PostgreSQL`, `psycopg`, `Pydantic`

---

# # 📌 서론 – n+1 문제가 왜 문제인가?

웹·API 서버에서 **관계형 데이터베이스**를 사용할 때 가장 흔히 마주치는 성능 병목 중 하나가 **n+1 문제**입니다.

예를 들어, 게시글 10개를 조회하고 각각의 작성자를 다시 조회한다면 총 **11개의 SQL**이 실행됩니다.

데이터 양이 늘어나면 쿼리 수는 기하급수적으로 증가해 **응답 시간이 급격히 늘어나고** 서버 부하도 크게 올라갑니다.

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

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

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

---

# # 🛠️ 본문

## # 1️⃣ 프로젝트 환경 설정

```
bash

# 가상환경 생성·활성화

python -m venv venv

source venv/bin/activate

# 필수 패키지 설치

pip install psycopg2-binary pydantic

```

위 명령어만으로 `psycopg`와 `Pydantic`을 사용할 준비가 끝납니다.

## # 2️⃣ 데이터베이스 스키마 예시

```
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)

);

```

`posts`와 `authors`는 1:N 관계이며, **게시글과 작성자 정보를 동시에** 가져와야 하는 상황을 가정합니다.

## # 3️⃣ Pydantic 모델 정의 – 타입 검증과 직렬화

```
python

from pydantic import BaseModel

from typing import Optional

class Author(BaseModel):

    id: int

    name: str

class Post(BaseModel):

    id: int

    title: str

    content: Optional[str]

    author: Author          # 중첩된 객체

```

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

## # 4️⃣ 한 번에 데이터를 가져오는 SQL – 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;

```

- `jsonb_build_object`는 각 행에 `author` 라는 JSON 객체를 포함시켜 반환합니다.

- 이렇게 하면 **네트워크 라운드 트립**이 1회만 발생합니다.

## # 5️⃣ psycopg로 쿼리 실행하고 Pydantic에 매핑하기

```
python

import psycopg2

import json

from pydantic import parse_obj_as

from typing import List

# DB 연결

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: List[Post] = parse_obj_as(List[Post], posts_data)

# 결과 출력 (JSON 형태)

for post in posts:

    print(post.json())

```

- **한 번의 네트워크 호출**만으로 모든 데이터를 받아옵니다.

- `json.loads` 로 JSON 문자열을 딕셔너리로 변환하고, `parse_obj_as` 로 **타입 안전**하게 `Post` 객체 리스트를 생성합니다.

## # 6️⃣ 대용량 데이터와 페이징 처리

한 번에 수천 건을 가져오면 메모리 사용량이 급증할 수 있습니다. 이를 방지하려면 **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를 기준으로 **키셋 페이징**을 구현하면 인덱스 활용도가 높아져 성능이 더욱 향상됩니다.

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

```
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 이슈로 알려 주세요. 여러분의 피드백이 더 나은 튜토리얼을 만드는 원동력이 됩니다!

---

## # 📚 참고 자료

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

- **psycopg2 사용 가이드**: https://www.psycopg.org/docs/

- **Pydantic 문서**: https://docs.pydantic.dev/

*이 글은 **PostgreSQL**, **psycopg**, **Pydantic**을 핵심 키워드로 SEO 최적화되었습니다. “PostgreSQL n+1 문제 해결”, “psycopg JSON 조회”, “Pydantic 데이터 모델링” 등으로 검색하면 바로 찾아볼 수 있습니다.*
