숨 막히는 N+1 쿼리 문제 진단하기
AI가 생성한 ORM 코드에서 흔하게 발생하는 치명적 성능 저하 원인인 N+1 쿼리 문제를 진단하고, 데이터베이스 단에서 최적화하는 개념을 학습합니다.
대망의 마지막 시간입니다. 바이브코딩 시대의 비개발자들은 SQL을 직접 타이핑하기보다, AI에게 "게시글과 작성자 정보를 가져오는 파이썬(또는 자바스크립트) 코드 짜줘"라고 부탁합니다. 이때 AI는 보통 개발자들이 쓰는 편리한 번역기인 ORM(Object-Relational Mapping)이라는 도구를 사용하여 코드를 생성합니다. 하지만 이 편리함 이면에는 앱 서버와 데이터베이스 간의 통신을 마비시켜 버리는 악명 높은 숨은 살인마, 'N+1 쿼리 문제'가 도사리고 있습니다. 이 문제의 정체를 폭로하고 해결하는 법을 배워봅시다.
편리한 번역기, ORM의 배신
백엔드 코드(Python, JavaScript 등)에서 길고 복잡한 SQL 텍스트를 직접 치는 것은 오타도 잘 나고 유지보수하기 힘듭니다. 그래서 현대 개발에서는 앱 코드로 `Post.objects.all()` 처럼 아름답게 명령하면, ORM이라는 녀석이 이 코드를 찰떡같이 알아듣고 `SELECT * FROM posts;`라는 무식한 SQL로 자동 번역해서 데이터베이스에 날려주는 기술을 사용합니다. AI 역시 이 ORM을 활용한 코드를 기가 막히게 잘 짜줍니다.
문제는 프론트엔드 화면에 '게시글 10개'와 그 옆에 '각 게시글의 작성자 이름'을 함께 띄워주고 싶을 때 발생합니다.
악몽의 시작: N+1 쿼리 발동
AI가 짜준 겉보기엔 깔끔한 ORM 코드가 뒤편에서 어떻게 동작하는지 훔쳐봅시다.
- 최초 1번의 질문: 앱 서버가 DB에게 "게시글 최신순으로 10개만 먼저 가져와 줘" 라고 요청합니다. (`SELECT * FROM posts LIMIT 10;`)
- DB가 10개의 게시글 목록을 앱 서버로 보내줍니다.
- 앱 서버가 1번 게시글을 화면에 그리려다 보니 작성자 이름이 필요합니다. 다시 DB에게 "야, 이 1번 글 쓴 유저 번호가 5번이네? 5번 유저 이름 좀 가져와 줘."라고 개별 요청을 보냅니다. (`SELECT name FROM users WHERE id = 5;`)
- 앱 서버가 2번 게시글을 그리려다 또 DB에게 "2번 글 쓴 17번 유저 이름 좀 줘." 라고 두 번째 추가 요청을 보냅니다.
- 이 멍청한 짓거리를 10번째 게시글까지 총 10번 반복합니다.
결과적으로, 화면 하나를 띄우기 위해 최초 게시글 조회 1번 + 딸려 있는 유저 정보 조회 10번(N번) = 총 11번(N+1번)의 네트워크 통신이 발생해 버립니다. 만약 게시판 한 페이지에 글이 100개가 뜬다면? 화면 한 번 로딩할 때마다 앱 서버와 DB가 101번이나 네트워크 핑퐁을 쳐야 합니다. 페이지가 숨넘어가듯 느리게 뜨는 최악의 병목 현상입니다.
N+1 쿼리와 최적화 조인의 비교
| 처리 방식 | 네트워크 통신 횟수 | 비유하자면? |
|---|---|---|
| N+1 문제 발생 (AI의 엉성한 코드) | 1 + N 번 (게시글 100개면 총 101번의 통신) | 사장님이 비서에게 '직원 100명 서류 가져와' 한 뒤, 서류를 한 장씩 볼 때마다 '얘 이름이 뭐야?'라고 100번 전화해서 물어보는 짓 |
| JOIN 최적화 적용 (Eager Loading) | 단 1번의 통신 (DB 안에서 미리 싹 다 합쳐서 던져줌) | 사장님이 처음부터 '직원 100명 서류에 이름까지 싹 다 호치키스로 찍어서 하나로 묶어서 한 번에 가져와!'라고 똑똑하게 지시하는 것 |
AI를 참교육하는 마법의 단어: Eager Loading
데이터베이스는 애초에 이런 상황을 대비해 여러 테이블을 한 번에 싹 다 묶어주는 4회차에서 배운 `JOIN` 기능을 가지고 있습니다. N+1 문제를 해결하는 방법은, 무식하게 101번 질문하지 말고 애초에 첫 질문을 던질 때 "게시글 10개 가져올 건데, 가져올 때 유저 정보도 JOIN으로 묶어서(Eager Loading) 단 한 번의 쿼리로 가져와!"라고 ORM에게 정확히 지시하는 것입니다.
바이브코딩 중에 뭔가 화면 로딩 속도가 미세하게 느리다고 느껴지면, 주저하지 말고 터미널의 서버 로그 창을 쳐다보세요. 화면을 한 번 새로고침했는데 로그 창에 `SELECT ...` 구문이 수십, 수백 줄이 다다다닥 찍혀서 올라간다면 100% N+1 쿼리 병에 걸린 것입니다.
AI에게 지시하는 완벽한 프롬프트
나쁜 지시: "게시글 목록이랑 작성자 이름 띄우는 백엔드 코드 짜줘." (AI가 N+1 문제를 일으키는 편한 코드를 짜줄 확률 99%)
고수의 지시: "게시글 목록 띄우는 API 만들 건데, 게시글과 연관된 User 테이블 정보를 가져올 때 절대 N+1 쿼리 문제가 발생하지 않도록 JOIN(또는 Eager Loading, select_related 등 프레임워크 기능)을 사용해서 쿼리 단 1번만 발생하게 최적화된 코드로 짜줘."
개념 퀴즈
퀴즈 답을 맞춰야 학습 완료가 됩니다.바이브코딩으로 만든 게시판 화면을 새로고침 했더니, 백엔드 서버 로그에 '게시글 목록 1번 조회' 직후 '각 작성자의 정보를 가져오는 조회 쿼리'가 게시글 개수만큼 연속해서 수십 번 실행되는 끔찍한 현상을 발견했습니다. 이처럼 ORM을 쓸 때 연관된 데이터를 무식하게 1건씩 따로 가져오면서 성능을 갉아먹는 이 현상의 이름은 무엇인가요?
3줄 요약
- 1앱 코드에서 SQL을 편하게 쓰게 해주는 ORM 기술은, 연관된 데이터(예: 글 작성자)를 가져올 때 치명적인 N+1 쿼리 문제를 자주 발생시킵니다.
- 2N+1 문제가 발생하면 네트워크 통신이 불필요하게 수십~수백 번 발생하여 데이터베이스와 서버의 성능을 바닥으로 끌어내립니다.
- 3바이브코딩으로 코드를 요청할 때, AI에게 'N+1 문제가 발생하지 않도록 JOIN(Eager Loading)을 써서 쿼리 1번으로 한방에 가져와'라고 명확히 지시하는 것이 최적화의 핵심입니다.