콘텐츠로 이동

Ch.8 사례: 같은 문제, 다른 카테고리의 키워드

< 환경 세팅 | OS와 네트워크 키워드 >


Ch.7에서 "키워드가 프롬프트의 방향을 결정한다"고 했다. 이번에는 한 걸음 더 나간다. 같은 문제라도 어느 카테고리의 키워드를 쓰느냐에 따라 AI의 해법이 완전히 달라진다.

증상은 보통 한 줄이다. "API가 느려요", "서버가 죽어요", "DB가 멈춰요". 그런데 진짜 원인은 OS일 수도, DB일 수도, 네트워크일 수도, 자료구조일 수도, 아키텍처일 수도 있다. 어느 카테고리에서 출발하느냐가 진단의 절반이다.

8-1. 사례: "사용자 조회 API가 느리다"

상황은 단순하다. GET /users/{id} API가 평균 800ms, p99 2초다. 사용자 한 건 조회하는 데 너무 오래 걸린다. 이 한 줄짜리 증상을 5명의 개발자가 각자 다른 카테고리의 키워드로 진단해본다.

개발자 A: OS 키워드로 접근

[프롬프트]
GET /users/{id} API가 느리다. CPU는 여유 있는데 응답이 800ms다.
strace로 따라가니 write() system call이 요청당 수백 번 호출되고 있다.
컨트롤러에서 print/log를 한 줄씩 쓰고 있어서 stdout이 병목인 것 같다.
1. 로그 레벨을 조정해줘 (DEBUG 제거)
2. 남길 로그는 QueueHandler 기반 비동기 로깅으로 바꿔줘

AI 응답 방향:

import logging
from logging.handlers import QueueHandler, QueueListener
from queue import Queue

log_queue = Queue(-1)
handler = QueueHandler(log_queue)
listener = QueueListener(log_queue, logging.FileHandler("app.log"))
listener.start()

logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(handler)

Ch.2의 "System Call이 비싸다"가 진단의 출발점이었다. 키워드가 write(), stdout, QueueHandler였기 때문에 AI는 바로 비동기 로깅 코드를 줬다.

개발자 B: 네트워크 키워드로 접근

[프롬프트]
GET /users/{id} API가 느리다. 응답이 800ms인데 그 중 700ms가 외부 호출이다.
프로필 이미지 서버, 권한 서버, 활동 로그 서버를 순차 호출한다.
각각 RTT 200ms 정도. 직렬이라 700ms 가까이 걸린다.
asyncio + aiohttp로 3개를 병렬 호출해서 max(RTT) 수준으로 줄이고 싶다.

AI 응답 방향:

import asyncio
import aiohttp

async def get_user_full(user_id: int):
    async with aiohttp.ClientSession() as session:
        profile, perm, activity = await asyncio.gather(
            session.get(f"/profile/{user_id}"),
            session.get(f"/permission/{user_id}"),
            session.get(f"/activity/{user_id}"),
        )
        return await combine(profile, perm, activity)

Ch.6의 "Connection 비용"과 Ch.3의 "I/O Bound는 async가 답"이 진단의 출발점이었다.

개발자 C: DB 키워드로 접근

[프롬프트]
GET /users/{id} API가 느리다. SQLAlchemy 로그를 켰더니
유저 1건 조회하는데 같은 SELECT가 N번 반복된다.
users → orders → items 순으로 lazy loading이 걸려서 N+1이 되고 있다.
EXPLAIN으로 보니 Full Table Scan도 한 번 있다.
1. joinedload/selectinload로 eager loading 적용
2. orders.user_id에 인덱스 걸기

AI 응답 방향:

from sqlalchemy.orm import selectinload

def get_user(session, user_id: int):
    return session.query(User).options(
        selectinload(User.orders).selectinload(Order.items),
    ).filter(User.id == user_id).one()
CREATE INDEX idx_orders_user_id ON orders (user_id);

Ch.13에서 다룰 "N+1 쿼리"와 Ch.14의 "인덱스"가 진단의 출발점이었다.

개발자 D: 자료구조 키워드로 접근

[프롬프트]
GET /users/{id} API가 느리다. DB는 빠른데(10ms) 애플리케이션에서 500ms가 더 걸린다.
프로파일링 해보니 권한 체크 로직이 user.permissions 리스트에서
required_perms = ["read", "write", ...] 약 50개를 in 연산으로 확인하고 있다.
user.permissions 자체도 수백 개라 매 요청마다 O(n*m).
1. user.permissions를 set으로 바꿔서 O(1) 조회로
2. 가능하면 required_perms도 frozenset으로

AI 응답 방향:

class User:
    @property
    def permission_set(self) -> frozenset[str]:
        return frozenset(self.permissions)

REQUIRED_PERMS = frozenset({"read", "write", "delete"})

def has_required(user: User) -> bool:
    return REQUIRED_PERMS.issubset(user.permission_set)

Ch.10의 "List vs Set 시간 복잡도"가 진단의 출발점이었다.

개발자 E: 아키텍처 키워드로 접근

[프롬프트]
GET /users/{id} API가 느리다. 단일 모놀리스에서
유저 정보 + 결제 이력 + 추천 상품을 한 번에 내려주고 있다.
유저 모듈은 빠른데, 결제와 추천이 무거워서 전체 응답이 따라 느려진다.
1. 추천은 Read 비중이 압도적이라 CQRS로 Read Replica에서 조회
2. 결제 이력 알림은 Event-Driven으로 분리, 메인 응답에서 제외
3. 외부 호출은 Circuit Breaker로 묶어서 부분 장애가 전체를 죽이지 않게

AI 응답 방향: 추천 서비스 Read Replica 분리 설계, 결제 이벤트 발행 코드 스켈레톤(OrderCreated → Message Queue), Circuit Breaker 라이브러리(pybreaker, tenacity) 적용 예시.

Ch.22의 "관심사 분리"와 Ch.19의 "Read/Write 분리"가 진단의 출발점이었다.

8-2. 다섯 접근의 비교

접근 진단 도구 핵심 키워드 해법 관련 챕터
OS strace, htop System Call, write(), stdout, QueueHandler 로그 레벨 조정, 비동기 로깅 Ch.2
Network 외부 호출 시간 측정, RTT RTT, Connection Pool, asyncio.gather 병렬 호출 Ch.6
DB ORM 쿼리 로그, EXPLAIN N+1, eager loading, Index joinedload/selectinload, CREATE INDEX Ch.13~14
자료구조 프로파일러 (cProfile) List vs Set, Time Complexity, O(n) List → Set/frozenset Ch.10
아키텍처 모듈별 응답 분해 Monolith, CQRS, Event-Driven, Circuit Breaker 읽기 분리, 비동기 이벤트 Ch.19~22

다섯 명 다 "GET /users/{id}가 느리다"는 동일한 한 줄에서 출발했다. 그런데 진단 결과가 다르고, 쓴 키워드가 다르고, AI의 해법이 다르다. 어느 게 정답인가? 전부 정답이다. 원인이 다르니까.

(현실에서는 한 명이 다 했을 가능성이 높다. "처음엔 DB부터 보고, 그게 아니면 외부 호출 보고…" 하는 순서로. 그게 가능하려면 5개 카테고리의 키워드를 다 알아야 한다는 뜻이다.)

중요한 건 두 가지다.

  1. 어느 카테고리의 키워드를 꺼낼 수 있느냐가 진단 능력이다
  2. 정확한 키워드 없이 AI에게 던지면, AI는 "엉뚱한 카테고리"의 답을 줄 가능성이 높다

8-3. 키워드 없이 같은 문제를 물어보면

비교를 위해 똑같은 문제를 키워드 없이 물어본 케이스를 보자.

[프롬프트]
사용자 조회 API가 느려요. 어떻게 해야 하나요?

AI 응답 방향(요약):

1. 캐시(Redis)를 붙이세요
2. 인덱스를 거세요
3. DB Connection Pool 사이즈를 늘리세요
4. 비동기 처리를 도입하세요
5. 모니터링을 추가하세요

문제는 두 가지다.

  • 다섯 가지 일반론을 늘어놓는다. 그 중 진짜 원인에 해당하는 건 운이 좋아야 한 개다
  • "왜 그렇게 해야 하는가"의 진단이 빠져 있다. 인덱스가 문제인지, 외부 호출이 문제인지, 권한 체크 O(n*m)이 문제인지 모른 채로 다 시도하게 된다

"느린데 어떻게 해야 하나"라고 물으면 AI는 "느릴 때 흔히 시도하는 것"을 늘어놓는다. Ch.7에서 본 "키워드 없는 프롬프트 → 평균적 답변"의 전형적인 모습이다.

반면 앞에서 본 다섯 개발자의 프롬프트에는 전부 진단 결과가 들어 있다. "write() 호출이 많다", "RTT 200ms 외부 호출 3개", "N+1과 Full Table Scan", "권한 체크 O(n*m)", "결제/추천 모듈이 무겁다". AI는 그 진단에 맞는 정확한 코드를 생성한다.

8-4. 다음 챕터에서 다루는 것

다음부터 카테고리별로 "AI에게 줄 수 있는 핵심 키워드"를 정리한다. 각 키워드가 어떤 상황에서, 어떤 프롬프트에 쓰이는지를 보여주는 게 이 챕터의 목적이다.

graph LR
    SYM["증상 한 줄<br/>(느리다/죽는다/꼬인다)"] --> CAT{"어느 카테고리?"}
    CAT --> OS["OS"]
    CAT --> NET["네트워크"]
    CAT --> DB["DB"]
    CAT --> DS["자료구조"]
    CAT --> ARCH["아키텍처"]
    OS --> P["카테고리 키워드<br/>+ 진단 결과<br/>= 좋은 프롬프트"]
    NET --> P
    DB --> P
    DS --> P
    ARCH --> P

전부 외울 필요는 없다. "이런 키워드가 있다"는 걸 알아두면, 문제가 생겼을 때 꺼내 쓸 수 있다. 카테고리 선택이 진단의 출발점이다.


< 환경 세팅 | OS와 네트워크 키워드 >