Ch.8 DB와 자료구조 키워드¶
< OS와 네트워크 키워드 | 유사 사례와 키워드 정리 >
앞에서 OS와 네트워크 키워드를 정리했다. 이번에는 DB, 자료구조, 아키텍처 키워드를 정리한다. 이 세 카테고리는 백엔드 개발자가 가장 자주 마주치는 영역이고, AI 프롬프트에서도 가장 효과 차이가 큰 영역이다.
DB 키워드¶
DB 키워드는 "데이터를 어떻게 저장하고 조회하는가"를 다룬다. 쿼리 성능, 데이터 정합성, Connection 관리 문제를 진단할 때 필요하다.
인덱스와 쿼리¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Index | 조회 속도를 높이기 위한 자료구조 (보통 B-Tree) | 쿼리가 느릴 때 가장 먼저 확인 | "EXPLAIN 결과 Full Table Scan이다. email 컬럼에 인덱스 걸어줘" |
| B-Tree Index | 정렬된 트리로 범위/정렬 검색에 강한 인덱스 | 범위 검색이나 ORDER BY 최적화가 필요할 때 | "created_at으로 정렬 + 범위 조회가 잦다. B-Tree 인덱스 정의해줘" |
| Hash Index | 해시 기반, 정확 일치 검색에만 강한 인덱스 | 단순 동등 조건 검색이 압도적일 때 | "session_id로 정확 일치 조회만 한다. PostgreSQL Hash Index 적용해줘" |
| Covering Index | 인덱스 자체가 필요한 컬럼을 다 가지고 있어 테이블 접근이 불필요한 인덱스 | 자주 쓰는 SELECT 컬럼 조합을 빠르게 하고 싶을 때 | "이 쿼리는 user_id, created_at만 읽는다. Covering Index로 묶어줘" |
| Full Table Scan | 인덱스 없이 테이블 전체를 훑는 것 | EXPLAIN 결과 type이 ALL일 때 | "EXPLAIN에서 type=ALL이다. Full Table Scan을 피하도록 WHERE 컬럼에 인덱스 걸어줘" |
| Query Execution Plan (QEP) | DB가 쿼리를 실행하는 계획 | 쿼리가 왜 느린지 원인을 찾을 때 | "EXPLAIN ANALYZE 결과를 보고 어디서 시간이 가장 많이 가는지 분석해줘" |
| Slow Query | 임계값 이상 걸리는 쿼리, 보통 로그로 별도 수집 | "느린 쿼리부터 잡자" 류 작업에 | "Slow Query Log 분석해서 평균 응답 시간 상위 10개 쿼리 뽑는 스크립트 줘" |
ORM과 패턴¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| ORM | 객체와 관계형 테이블을 매핑해주는 라이브러리 | ORM이 만드는 SQL을 다룰 때 | "SQLAlchemy ORM이 어떤 SQL을 발행하는지 echo=True로 켜는 법 알려줘" |
| N+1 Problem | 1번 쿼리 후 N번의 추가 쿼리가 발생하는 패턴 | ORM에서 연관 데이터를 반복 조회할 때 | "N+1 쿼리가 발생한다. joinedload로 eager loading 적용해줘" |
| Lazy / Eager Loading | 연관 데이터를 접근 시 / 처음에 불러오는 전략 | N+1 해결 방향을 잡을 때 | "기본 lazy를 그대로 두고, 이 엔드포인트만 selectinload로 eager 적용해줘" |
| Batch Insert | 여러 INSERT를 한 번에 묶어 보내는 방식 | 대량 입력이 너무 느릴 때 | "1만 건 INSERT가 30초 걸린다. executemany 또는 bulk_save_objects로 batch 처리해줘" |
| Pagination (Offset/Cursor) | 큰 결과를 페이지 단위로 나눠 조회 | 큰 테이블에서 페이지가 뒤로 갈수록 느려질 때 | "OFFSET 100000이 너무 느리다. created_at 기준 Cursor Pagination으로 바꿔줘" |
트랜잭션과 동시성¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Transaction | 하나의 작업 단위, 전부 성공하거나 전부 실패 | 데이터 정합성이 깨질 때 | "주문 생성과 재고 차감을 하나의 Transaction으로 묶어줘" |
| ACID | Atomicity, Consistency, Isolation, Durability | 트랜잭션 보장 수준을 논의할 때 | "이 결제 흐름이 ACID를 어디서 깨고 있는지 한 단계씩 짚어줘" |
| Isolation Level | 동시 트랜잭션 간 데이터 격리 수준 | 동시 요청에서 데이터가 꼬일 때 | "Phantom Read가 의심된다. Isolation Level을 SERIALIZABLE로 올리는 비용 알려줘" |
| Dirty / Non-repeatable / Phantom Read | Isolation 단계별로 나타나는 이상 현상 | Isolation Level 선택 근거를 댈 때 | "지금 READ COMMITTED인데 Non-repeatable Read가 발생한다. 어떤 케이스인지 진단해줘" |
| Optimistic Lock / Pessimistic Lock | 충돌 가능성을 낮게 보거나(낙관) 미리 잠그는(비관) 전략 | 동시 업데이트 충돌이 잦을 때 | "재고 차감에 version 컬럼 기반 Optimistic Lock 적용해줘. 충돌 시 재시도 로직도 함께" |
| Deadlock (DB) | 두 트랜잭션이 서로의 Lock을 기다리는 상태 | "Deadlock detected" 에러가 날 때 | "DB Deadlock이 발생한다. Lock 획득 순서를 통일하거나 재시도 백오프 넣어줘" |
| Connection Pool | DB Connection을 미리 만들어두고 재활용 | Connection 관련 에러가 날 때 | "pool_size=5인데 동시 요청이 50이다. pool_size와 max_overflow 조정 가이드 줘" |
확장과 운영¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Read Replica | 읽기 전용 복제본 DB | 읽기 부하가 압도적일 때 | "Read 비중 90%다. Read Replica로 라우팅하는 SQLAlchemy 설정 줘" |
| Partitioning | 한 테이블을 작은 조각으로 나누는 기법 | 큰 테이블의 조회/관리가 무거워질 때 | "주문 테이블이 1억 건이다. created_at 기준 월 단위 Range Partitioning 적용해줘" |
| Sharding | 데이터를 여러 DB 인스턴스로 분산 | 단일 DB 용량/성능 한계에 닿을 때 | "user_id 해시 기반 Sharding 전략 짜줘. cross-shard JOIN 회피 방법도" |
| Materialized View | 쿼리 결과를 미리 계산해 저장하는 뷰 | 무거운 집계 쿼리를 자주 호출할 때 | "이 집계 쿼리가 30초 걸린다. Materialized View로 빼고 야간에 REFRESH 하게 해줘" |
(Ch.6에서 Connection Pool을 배웠고, Ch.13~16에서 나머지를 자세히 다룬다.)
DB 키워드 활용 사례¶
사례 1: N+1과 인덱스를 한 번에 잡기¶
[프롬프트]
주문 목록 API가 100건 응답에 5초 걸린다.
1. ORM 로그를 보니 Order 1번 + 각 Order의 User 100번 = 101번 쿼리 발생 (N+1)
2. EXPLAIN으로 보니 User 조회가 Full Table Scan
1) selectinload(Order.user)로 eager loading
2) users.id가 PK가 아니라 일반 컬럼이라면 인덱스 추가
3) 적용 후 쿼리 수와 응답시간을 기록할 수 있게 미들웨어로 카운트 코드도 줘
N+1, eager loading, selectinload, Full Table Scan, Index 다섯 키워드를 묶었다. Ch.13~14 내용이 그대로 적용된다.
사례 2: Slow Query → Connection Pool 고갈¶
[프롬프트]
간헐적으로 "QueuePool limit of size 5 overflow 10 reached" 에러가 난다.
원인을 추적해보니 슬로우 쿼리 1건이 30초씩 Connection을 점유한다.
1. Slow Query를 EXPLAIN ANALYZE로 분석해서 인덱스 또는 쿼리 재작성 후보 뽑아줘
2. Connection Pool 사이즈는 그대로 두고, 쿼리 timeout(statement_timeout) 설정으로 한계 시간 강제
3. SQLAlchemy 옵션 코드 + 모니터링용 pool 상태 출력 코드 같이
Slow Query, Connection Pool, statement_timeout 키워드 조합으로 "원인 + 응급 처치 + 근본 대응"을 한 프롬프트에 담았다. Ch.16에서 다룰 패턴이다.
자료구조 키워드¶
자료구조 키워드는 "데이터를 어떤 형태로 저장하고 접근하는가"를 다룬다. "왜 이 코드가 느린가"를 데이터 구조 관점에서 진단할 때 필요하다.
기본 컨테이너¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| List (Array) | 순서가 있는 가변 시퀀스, in 연산은 O(n) | "이 리스트에서 자주 찾는다"가 보일 때 | "List에서 in 연산을 1만 번 한다. Set으로 바꿔서 O(1) 조회로 만들어줘" |
| Set | 중복 없는 집합, 평균 O(1) in 연산 | 멤버십 검사가 많은 코드에 | "권한 체크를 List 대신 frozenset으로 바꾸고 issubset으로 한 번에 검사하게 해줘" |
| Dict (Hash Map) | 키→값 매핑, 평균 O(1) 조회 | 반복 조회를 ID/키로 인덱싱하고 싶을 때 | "users 리스트를 매번 순회한다. user_id를 키로 한 dict로 인덱싱하게 바꿔줘" |
| Tuple | 불변 시퀀스, 해시 가능 | dict 키로 복합 키를 쓰고 싶을 때 | "캐시 키로 (user_id, date)를 쓰고 싶다. Tuple로 dict 키 만드는 패턴 보여줘" |
| Deque | 양쪽 끝 O(1) 삽입/삭제 | List.pop(0)이 자주 등장할 때 | "queue 패턴에 List.pop(0)을 쓰고 있다. collections.deque로 바꿔줘" |
트리/그래프/특수 구조¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| B-Tree | 디스크 기반 정렬 트리, DB 인덱스의 기본 구조 | DB 인덱스 동작 설명을 AI에게 시킬 때 | "B-Tree 인덱스라 BETWEEN 같은 범위 검색이 효율적이다. 그 이유 단계별로 설명해줘" |
| Trie | 문자열 prefix 검색에 강한 트리 | 자동완성, prefix 매칭 검색에 | "검색어 자동완성을 메모리에서 Trie로 처리하는 코드 줘" |
| Heap (Priority Queue) | 최솟값/최댓값을 O(log n)으로 꺼낼 수 있는 트리 | top-K, 우선순위 작업 처리에 | "top-100 추천 점수만 유지하고 싶다. heapq로 size 100 heap 유지하는 코드 줘" |
| Bloom Filter | 원소가 집합에 있는지 확률적으로 판별 (false positive 있음) | 대량 데이터에서 빠른 사전 차단이 필요할 때 | "100만 사용자 중복 이메일 체크를 DB 없이 우선 거르고 싶다. Bloom Filter로 설계해줘" |
| LRU Cache | 가장 오래 안 쓴 항목을 제거하는 캐시 | 캐시 크기를 제한하고 싶을 때 | "functools.lru_cache로 maxsize=1000인 캐시 적용. 효과 측정 코드도" |
| DAG (Directed Acyclic Graph) | 방향이 있고 사이클이 없는 그래프 | 작업 의존성, 파이프라인 설계에 | "작업 간 의존성을 DAG로 모델링하고 topological sort로 실행 순서 만들어줘" |
알고리즘과 복잡도¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Time Complexity | 입력 크기 대비 실행 시간 증가율 | "왜 이 코드가 느린지" 근본 원인을 설명할 때 | "이 로직이 O(n^2)이다. O(n log n) 또는 O(n)으로 줄이는 방법 정리해줘" |
| Space Complexity | 입력 크기 대비 메모리 사용량 증가율 | 메모리 효율 vs 속도 트레이드오프를 다룰 때 | "이 함수가 O(n) 추가 메모리를 쓴다. in-place로 바꿀 수 있는가?" |
| BFS / DFS | 그래프 너비/깊이 우선 탐색 | 트리/그래프 순회가 필요할 때 | "카테고리 트리를 BFS로 순회하면서 depth별로 그룹화하는 코드 줘" |
| Binary Search | 정렬된 데이터에서 O(log n) 검색 | 정렬된 큰 리스트 검색에 | "bisect 모듈로 정렬된 timestamp 리스트에서 범위 검색하는 패턴 보여줘" |
| Topological Sort | DAG의 노드를 의존성 순서로 정렬 | 작업 실행 순서 결정에 | "task 의존성 그래프에서 실행 순서 만들어줘. 사이클 검출도 함께" |
(Ch.10~12에서 자세히 다룬다.)
자료구조 키워드 활용 사례¶
사례 1: List → Set 한 줄 교체¶
[프롬프트]
이 함수가 느리다. 입력 N이 1만일 때 5초 걸린다.
원인은 in 연산을 List 대상으로 N번 호출하는 부분이다 (O(n^2)).
1. allowed_ids를 set으로 바꿔서 in 연산을 O(1)로
2. 시간 측정 코드도 함께 (time.perf_counter)
List vs Set, Time Complexity, O(n^2) → O(n) 키워드를 묶었다. Ch.10의 가장 단순하지만 강력한 패턴이다.
사례 2: 트리 자료구조로 한방에¶
[프롬프트]
카테고리 트리를 매 요청마다 재귀로 DB에서 끌어와 만든다 (요청당 수십 쿼리).
1. 카테고리 트리를 메모리에 한 번만 만들고 캐시한다 (변경이 거의 없음)
2. 트리 구조는 dict[parent_id, list[Category]]로 인덱싱, 조회 시 BFS로 하위 전체
3. 변경 시점에만 invalidate 하는 함수도 같이
Tree, BFS, dict 인덱싱 키워드 조합으로 "재귀 N+1을 자료구조로 푸는" 사고를 한 프롬프트에 담았다.
아키텍처 키워드¶
아키텍처 키워드는 "시스템을 어떻게 구성하는가"를 다룬다. 설계, 확장, 배포, 장애 격리 문제를 논의할 때 필요하다.
구성과 분리¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Monolith | 하나의 코드베이스에 모든 기능이 들어있는 구조 | 배포 단위가 너무 무거워졌을 때 | "Monolith에서 주문 서비스만 분리하고 싶다. 점진적 분리 단계 짜줘" |
| Microservice | 기능별로 독립된 서비스로 분리한 구조 | 팀과 도메인이 분리되면서 배포가 부딪힐 때 | "주문과 결제를 Microservice로 분리. 통신 방식과 트랜잭션 경계 설계해줘" |
| Modular Monolith | 한 배포 단위 안에서 모듈만 강하게 분리 | 마이크로서비스가 아직 부담스러울 때 | "Microservice는 부담스럽다. Modular Monolith로 모듈 경계를 잡는 기준 알려줘" |
| Service Boundary | 서비스 간 책임 경계 | 분리/통합 의사결정에 | "이 두 모듈 사이의 Service Boundary가 모호하다. 책임 분리 기준 제안해줘" |
비동기 통신과 이벤트¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| Event-Driven | 이벤트를 발행하고 구독하는 비동기 아키텍처 | 결합도를 낮추고 싶을 때 | "주문 생성 시 알림/재고/정산을 Event-Driven으로 처리하는 설계 보여줘" |
| Message Queue | 서비스 간 메시지를 비동기로 전달하는 중간 계층 | 직접 호출 대신 비동기 통신이 필요할 때 | "외부 API 호출을 Celery + RabbitMQ로 비동기 처리하는 코드 줘" |
| Pub/Sub | 발행/구독 모델, 다대다 비동기 통신 | 한 이벤트를 여러 컨슈머가 처리해야 할 때 | "주문 이벤트를 알림/정산/분석 3개 컨슈머가 각각 구독하는 구조 짜줘" |
| Idempotency | 같은 요청을 여러 번 처리해도 결과가 같은 성질 | 메시지 중복 처리 보호에 | "메시지가 중복 전달될 수 있다. consumer를 Idempotent하게 만드는 패턴 알려줘" |
안정성 패턴¶
| 키워드 | 한 줄 설명 | 이런 상황에 쓴다 | 프롬프트 예시 |
|---|---|---|---|
| CQRS | 읽기와 쓰기를 분리하는 패턴 | 읽기/쓰기 부하가 크게 다를 때 | "읽기가 쓰기의 100배다. CQRS로 Read Model 분리 + Read Replica 라우팅" |
| Circuit Breaker | 장애가 난 외부 서비스 호출을 차단하는 패턴 | 외부 장애가 내 서비스에 전파될 때 | "외부 API 장애 시 Circuit Breaker로 차단 + fallback 응답 주는 코드 줘" |
| Retry / Backoff | 실패한 요청을 재시도, 간격을 점진적으로 늘림 | 일시적 장애에 자동 복구가 필요할 때 | "외부 호출에 Exponential Backoff + Jitter로 retry 적용해줘 (최대 3회)" |
| Bulkhead | 한 부분의 장애가 전체를 침몰시키지 않게 분리 | 한 의존성 폭발이 전체 자원을 잡아먹을 때 | "외부 호출별 ThreadPool을 분리해 Bulkhead 구성. 한쪽 hang이 전체에 영향 안 가게" |
| Service Discovery | 서비스 인스턴스를 동적으로 찾는 메커니즘 | 컨테이너 환경에서 서비스 위치를 추적할 때 | "K8s 환경에서 내부 서비스 호출에 ClusterIP + DNS 기반 Service Discovery 어떻게?" |
(Ch.19~22에서 자세히 다룬다.)
아키텍처 키워드 활용 사례¶
사례: 외부 결제 장애 → 우리 서비스도 장애¶
[프롬프트]
어제 결제사 장애가 났는데, 우리 주문 서비스도 같이 죽었다.
원인은 결제 호출이 timeout 없이 무한 대기 → 모든 worker thread 점유.
1. 결제 호출에 5초 timeout과 Circuit Breaker 적용 (Resilience)
2. 결제 실패 시 fallback으로 "pending" 상태로 저장 후 비동기 재처리 (Event-Driven)
3. 결제 호출 풀을 별도 ThreadPool로 분리 (Bulkhead)
timeout, Circuit Breaker, fallback, Event-Driven, Bulkhead 키워드 조합으로 장애 격리 설계가 한 번에 나온다.
카테고리를 넘나드는 진단¶
실무 문제는 한 카테고리에 깔끔하게 떨어지지 않는다. "API가 느리다"라는 증상 하나에도 여러 카테고리의 키워드가 엮인다. 빠른 진단을 위한 "키워드 카테고리 결정 트리"를 그려보면 아래와 같다.
graph TD
START["증상: API가 느리다 / 죽는다"] --> Q1{"CPU 사용률이<br/>높은가?"}
Q1 -->|"높다"| CPU["CPU Bound<br/>→ OS 키워드<br/>(GIL/Process/Thread)"]
Q1 -->|"낮다"| Q2{"I/O Wait가<br/>높은가?"}
Q2 -->|"높다"| Q3{"어떤 I/O?"}
Q2 -->|"낮다"| Q4{"외부 호출이<br/>있는가?"}
Q3 -->|"DB"| DB["DB 키워드<br/>(Index, QEP, N+1, Slow Query)"]
Q3 -->|"파일/로그"| OS2["OS 키워드<br/>(System Call, Buffer)"]
Q4 -->|"있다"| NET["네트워크 키워드<br/>(RTT, Connection Pool, Keep-Alive)"]
Q4 -->|"없다"| Q5{"메모리 사용량이<br/>증가하는가?"}
Q5 -->|"증가"| MEM["OS 키워드<br/>(RSS, OOM, tracemalloc)"]
Q5 -->|"일정"| DS["자료구조 키워드<br/>(Time Complexity, List vs Set)"]
DB --> ARCH{"전체 구조<br/>문제인가?"}
NET --> ARCH
ARCH -->|"예"| A["아키텍처 키워드<br/>(CQRS, Event-Driven, Circuit Breaker)"]
이 트리를 머리에 넣어두면, 문제 상황에서 어느 카테고리의 키워드를 꺼낼지 빠르게 결정할 수 있다. AI에게 물어볼 때도 "어느 방향에서 봐야 하는가"를 먼저 정하고 프롬프트를 쓰는 거다.
(주의: 결정 트리가 항상 정답을 주는 건 아니다. 여러 카테고리가 동시에 원인인 경우도 흔하다. 트리는 "어디부터 의심할지"를 정하는 시작점일 뿐이다.)