Insights·2026-10-01

N+1·트랜잭션·인덱스·마이그레이션이란 — AI가 짠 데이터베이스에서 사고 나는 네 가지

AI에게 데이터베이스를 맡겼을 때 사고가 나는 자리는 N+1 문제, 트랜잭션, 인덱스, 마이그레이션 네 곳이고, 코드를 읽지 못해도 증상으로 알아볼 수 있다. 목록이 느리면 N+1 문제와 인덱스를 함께 의심하고, 가입은 됐는데 포인트가 안 들어왔으면 트랜잭션을 의심한다. 기존 데이터를 옮기는 마이그레이션은 AI에게만 맡기지 말고 단계마다 직접 확인한다. 이름을 알면 AI에게 무엇을 확인해 달라고 할지 한 문장으로 말할 수 있다.

N+1·트랜잭션·인덱스·마이그레이션, 증상으로 알아본다 — 글의 요약 도식

증상 네 개, 이름 네 개

이 연재는 모바일 청첩장 서비스를 예로 ERD를 읽어 왔다. ERD는 데이터베이스의 표와 표 사이 관계를 한 장에 그린 설계도다. 01편에서 관리 대상인 엔티티와 칸인 컬럼을, 02편에서 하나에 여러 개가 딸리는 1:N 관계를, 03편에서 여러 개끼리 서로 얽히는 N:M 관계와 조인을 봤다.

강의는 마지막에 고급 주제 네 가지를 소개만 하고 끝난다. N+1 문제, 트랜잭션, 인덱스, 마이그레이션이다. 이름만 들으면 개발자의 영역 같지만 강의가 설명하는 방식은 코드가 아니라 증상이다. 어떤 일이 벌어지면 무엇을 의심하라는 식이다.

바이브코더에게는 이 방식이 맞다. AI에게 코드를 맡긴 사람이 직접 볼 수 있는 것은 화면과 동작이다. 코드 속 원인은 못 찾아도 「목록이 느리다」「포인트가 안 들어왔다」는 알아챌 수 있다. 그 증상에 이름을 붙일 줄 알면 AI에게 무엇을 확인해 달라고 할지가 정해진다.

아래 표가 이 글 전체의 요약이다. 왼쪽 열의 증상을 만나면 가운데 열의 이름을 넣어 오른쪽 열처럼 AI에게 확인을 요청한다.

증상의심할 것AI에게 할 요청
목록 화면이 너무 느리다N+1 문제, 인덱스이 목록에 N+1 문제가 있는지, 인덱스가 빠졌는지 둘 다 확인해 줘
검색이 너무 느리다인덱스이 검색에 쓰는 칸에 인덱스가 설정돼 있는지 확인해 줘
가입은 됐는데 300포인트가 안 들어왔다트랜잭션가입과 포인트 지급이 한 트랜잭션으로 묶여 있는지 확인해 줘
칸을 별도 표로 바꾸는 등 기존 데이터를 옮겨야 한다마이그레이션실행하기 전에 단계별 계획을 먼저 보여 줘

목록이 느리다 — N+1 문제, 그리고 인덱스

청첩장 20개짜리 목록을 그릴 때 N+1 방식은 21번, 조인은 1번 조회한다는 것을 막대 길이로 비교한 그림

청첩장 목록 화면을 떠올려 보자. 청첩장마다 어떤 템플릿을 쓰는지 썸네일과 템플릿 주소(URL)를 같이 보여 준다. 그런데 청첩장 표에는 템플릿 정보가 없고 템플릿의 id(template_id)만 있다. 03편에서 본 대로 템플릿 표에서 그 id의 줄을 찾아 주소를 끌어와 붙인다. 이것이 조인이다.

목록 화면은 이 데이터를 API로 받아 온다. API는 화면이 데이터를 달라고 부르는 창구다. 제대로 짠 목록 API는 청첩장과 템플릿을 조인해 한 번에 가져온다. N+1 문제는 조인하지 않고 청첩장 목록을 한 번 가져온 다음, 한 줄마다 템플릿을 다시 조회하는 것이다. 목록 조회 한 번(1)에 줄 수만큼(N)의 조회가 붙어서 N+1이라고 부른다.

청첩장이 몇 개 없을 때는 차이가 안 보이고, 줄이 늘수록 조회 횟수가 같이 는다. 그래서 강의는 증상으로 기억하라고 한다. 목록 API를 불렀는데 너무 느리면 한번 의심해 보라는 것이다. 그때 AI에게 「이 목록 API에 N+1 문제가 있는지 확인해 줘」라고 요청하면 된다.

인덱스도 증상이 비슷하다. 인덱스는 데이터베이스를 위해 목차를 미리 만들어 두는 것이다. 목차 없는 책에서 원하는 대목을 찾으려면 처음부터 넘겨야 하듯, 인덱스가 없는 칸으로 검색하면 표 전체를 훑는다. 검색이 너무 느려졌다면 인덱스를 의심한다.

강의가 강조하는 대목은 둘을 함께 의심하라는 것이다. 목록 같은 API를 호출했는데 너무 느리다면 N+1 문제인지, 인덱스가 제대로 설정되지 않았는지 두 가지를 다 물어봐야 한다. 증상이 같아서 한쪽만 확인하면 원인을 놓칠 수 있다.

같은 목록, 조회 횟수의 차이
청첩장 20개짜리 목록을 그릴 때

N+1 (느린 쪽)
① 청첩장 20개를 조회                  → 1번
② 청첩장마다 템플릿을 따로 조회        → 20번
   합계 21번. 청첩장이 늘면 같이 는다

조인 (빠른 쪽)
① 청첩장과 템플릿을 template_id 로 붙여 한 번에 조회 → 1번

가입은 됐는데 포인트가 없다 — 트랜잭션

강의의 예시는 회원 가입이다. 신규 가입자에게 300포인트를 주는 기능이 있다고 하자. 데이터베이스에는 유저 표와 포인트 표가 따로 있다. 가입 한 번에 두 표 모두에 기록이 생겨야 한다.

직접 가입해 보면 유저는 생성됐는데 300포인트는 추가되지 않은 경우가 생길 수 있다. 같이 일어나야 할 일인데 표가 나뉘어 있다는 이유로 한쪽만 되는 것이다. 포인트 없는 반쪽짜리 계정이 남는다.

트랜잭션은 여러 동작을 한 묶음으로 처리하는 것이다. 묶인 동작은 같이 성공하거나 같이 실패한다. 은행 이체에서 내 계좌에서 돈이 빠지는 일과 상대 계좌에 돈이 들어가는 일이 따로 놀면 안 되는 것과 같다. 포인트 지급이 실패하면 가입도 없던 일로 돌린다.

증상은 「같이 일어나야 할 일 중 하나만 일어나 있다」이다. 확인도 강의 예시처럼 직접 가입해 보고 포인트가 들어왔는지 보면 된다. 안 들어왔으면 AI에게 「회원 가입과 300포인트 지급이 한 트랜잭션으로 묶여 있는지 확인해 줘. 둘 중 하나가 실패하면 둘 다 취소돼야 해」라고 요청한다.

가입포인트 지급트랜잭션이 없으면트랜잭션이 있으면
성공성공정상정상
성공실패포인트 없는 유저만 남는다둘 다 취소된다

템플릿을 표로 옮길 때 — 마이그레이션

청첩장의 템플릿 칸을 templates 표로 옮기는 마이그레이션 세 단계와, 옛 칸을 지우기 전에 확인할 두 가지를 보여 주는 그림

마이그레이션은 데이터베이스의 엔티티를 추가하거나 고치는 일이다. 강의에 따르면 표를 새로 하나 더하는 정도는 큰 상관이 없다. 조심할 것은 이미 쌓인 데이터를 옮겨야 하는 변경이다.

대표적인 경우가 02편에서 본 템플릿의 승격이다. 처음에는 청첩장 표의 템플릿 칸에 1·2·3 중 하나만 넣게 정해 두었다. 정해 둔 값만 들어가게 하는 이 방식을 enum이라고 한다. 템플릿을 관리해야 할 때가 오자 템플릿을 별도 표로 뺐다. 설계도에서는 선 하나 바꾸는 일이지만, 이미 만들어진 청첩장들이 있다면 이야기가 달라진다.

순서는 이렇다. 먼저 템플릿 표를 만들고 1·2·3을 거기에 넣는다. 각 줄에 그 줄을 가리키는 id가 생긴다. 다음으로 기존 청첩장의 템플릿 값 1·2·3을 새 id에 하나씩 짝지어 template_id에 채운다. 두 표가 이미 관계를 맺고 있으니 이 짝이 맞아야 에러가 나지 않는다. 마지막으로 더는 필요 없어진 옛 템플릿 칸을 지운다.

이런 대규모 변경을 AI에게 맡기고 아무것도 보지 않으면 사고가 굉장히 많이 난다는 것이 강의의 경고다. 데이터베이스를 잘못 조작해 데이터가 싹 날아갔다는 이야기는 대체로 마이그레이션을 신경 쓰지 않고 AI에게 맡긴 경우라고 한다. 그래서 이런 작업은 AI에게만 맡기지 말고 직접 한 번씩 확인하는 습관을 들이라고 권한다.

확인이 코드를 읽는 일일 필요는 없다. 실행 전에 단계별 계획을 먼저 보여 달라고 하고, 옮기기 전과 후의 청첩장 수가 같은지, 모든 청첩장에 template_id가 채워졌는지 확인한 다음에 옛 칸을 지우게 한다. 지우는 단계가 되돌리기 가장 어렵기 때문이다.

템플릿 칸을 표로 옮기는 세 단계 (id는 줄여 표기)
전: invitations
id  title       template
1   청첩장 A    1
2   청첩장 B    3

① templates 표를 만들고 1·2·3 을 넣는다
id    url  thumbnail
t-01  ...  ...
t-02  ...  ...
t-03  ...  ...

② invitations.template_id 를 채운다
   template 1 → templates.id t-01
   template 3 → templates.id t-03

③ 옛 invitations.template 칸을 지운다   ← 되돌리기 가장 어렵다
마이그레이션 전에 AI에게 보낼 요청문
템플릿 칸을 templates 표로 옮기는 마이그레이션을 하려고 해.
실행하기 전에 단계별 계획을 먼저 보여 줘.
옮기기 전과 후의 청첩장 수가 같은지,
모든 청첩장에 template_id 가 채워졌는지 확인하는 방법도 같이 알려 줘.
옛 template 칸은 내가 확인한 다음에 지울게.

완성된 ERD가 보여 주는 것

강의자가 가장 중요하게 꼽은 것은 네 가지 사고가 아니라 완성된 ERD 자체다. 완성된 ERD를 보면 무엇을 구현했고 무엇을 구현하지 않았는지가 눈에 보인다.

추가 기능을 개발해야 할 때 어느 부분에 무엇을 더하면 되는지가 보이고, 지금의 연관 관계로 어떤 기능은 가능하고 어떤 기능은 불가능한지를 스스로 빠르게 파악할 수 있다. 01편에서 말한, 몇 달 뒤 「내가 뭘 만들었나」를 복원하는 지도가 이것이다.

네 가지 사고도 그 지도 위에 자리가 있다. N+1 문제는 표와 표를 잇는 선을 따라 데이터를 가져오는 방식의 문제이고, 인덱스는 표 안에서 자주 찾는 칸에 붙는 목차다. 트랜잭션은 한 동작이 건드리는 두 표 사이의 문제이고, 마이그레이션은 선을 바꾸는 순간의 문제다. ERD를 읽을 줄 알면 증상이 났을 때 AI에게 어디를 보라고 할지도 보인다.

여기까지가 ERD 강의를 바탕으로 한 네 편이다. 다음 편부터는 웹 페이지의 뼈대를 짜는 HTML 강의로 넘어가, 상담 신청 폼을 예로 화면 구성(UI)이 왜 정답이 있는 문제인지 본다.