fos-blog/study
01 / 홈02 / 카테고리
01 / 홈02 / 카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 커머스/F&B 도메인 설계 면접 대비 — 슬롯 경험을 주문·결제·쿠폰·매장 상태 설계로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis [Cache-Aside](../database/redis/cache-aside.md) × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • [초안] F&B / e-Commerce 운영 장애 대응과 모니터링 — 백엔드 관점 정리
    • Graceful Shutdown
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • investing 페이지로 이동
    • stock-notes 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] CJ푸드빌 디지털 채널 Back-end 개발자 직무 분석
    • [초안] CJ푸드빌 디지털 채널 Back-end 면접 답변집 — 슬롯 도메인 경험을 커머스/F&B 설계로 번역하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/database/인덱스 - DB 성능 최적화의 핵심
db

인덱스 - DB 성능 최적화의 핵심

- 인덱스와 실행 계획은 DB 성능의 80%를 결정짓는 핵심 분야 - 인덱스의 가장 기본이 되는 B+Tree 구조부터 시작해보자. - B+Tree는 이진 트리(Binary Tree)를 확장하여 하다의 노드가 가질 수 있는 자식 노드의 개수를 늘린 B-Tree의 변형 구조이다 - 핵심 특징: - 모든 키 값은 Leaf Node에만 존재: - Root와 Int...

2026.01.30·6 min read·198 views
  • 인덱스와 실행 계획은 DB 성능의 80%를 결정짓는 핵심 분야
  • 인덱스의 가장 기본이 되는 B+Tree 구조부터 시작해보자.

B+Tree란 무엇인가?

  • B+Tree는 이진 트리(Binary Tree)를 확장하여 하다의 노드가 가질 수 있는 자식 노드의 개수를 늘린 B-Tree의 변형 구조이다
  • 핵심 특징:
    • 모든 키 값은 Leaf Node에만 존재:
      • Root와 Internal 노드는 데이터의 위치를 안내하는 이정표 역할만 하며, 실제 데이터는 가장 하위인 Leaf 노드에만 저장된다.
    • Leaf Node 간의 Linked List:
      • 모든 Leaf 노드는 서로 연결 리스트(Linked List)로 이어져있어 순차 검색(Full Scan)이나 범위 검색(Range Scan)에 최적화되어 있다
    • 데이터 정렬:
      • 모든 노드 내의 데이터는 정렬된 상태를 유지한다.

왜 B-Tree가 아니라 B+Tree인가?

  • 일반적인 B-Tree도 훌륭한 자료구조지만, DB 인덱스 관점에서는 B+Tree가 가지는 압도적인 장점들이 있다.

  • 효율적인 범위 검색(Range Scan)

    • B-Tree : 특정 범위(예: WHERE age > 20)를 찾으려면 트리 전체를 중위 순회(In-order-traversal)하며 위아래로 계속 이동해야 한다.
    • B+Tree : 시작점인 Leaf 노드 하나만 찾으면, 그 이후부터는 연결 리스트를 따라 옆으로 쭉 읽기만 하면 된다.
      • 인덱스 풀 스캔이나 범위 검색 속도가 훨씬 빠르다.
  • 더 많은 키 저장 가능(I/O 효율성)

    • B-Tree : 각 노드가 데이터까지 직접 들고 있다.
    • B+Tree : 중간 노드에는 데이터 없이 인덱스 키만 담긴다.
    • 결과 :
      • 하나의 페이지 (보통 16KB)에 더 많은 키를 담을 수 있다.
      • 이는 트리의 높이를 낮게 유지해주며, 디스크 I/O 횟수를 획기적으로 줄여준다.
      • (보통 3~4레벨이면 수천만 건의 데이터를 커버한다.)
  • 캐시 히트율 상승

    • 중간 노드의 크기가 작기 때문에 메모리(InnoDB Buffer Pool)에 더 많은 인덱스 노드를 캐싱할 수 있어, 실제 디스크에 접근하는 빈도가 낮아진다.

그렇다면 B+Tree에 어떻게 저장되는지 살펴보자.

  • 이해를 돕기 위해, 간단한 사용자(User) 테이블을 예시로 들어 B+Tree가 인덱스 키를 어떻게 배치하고 데이터를 저장하는지 시각화 해보자.

예시 테이블 (MySQL InnoDB 기준)

sql
CREATE TABLE users (
    user_id INT PRIMARY KEY, -- 클러스터형 인덱스 (B+Tree)
    name VARCHAR(20),
    age INT
);
  • 샘플 데이터 (PK 기준 정렬): 10, 15, 20, 25, 30, 35, 40 (총 7개의 레코드)

B+Tree 인덱스 시각화 (Clustered Index)

  • InnoDB에서 PK 인덱스는 데이터 그 자체를 들고 있는 클러스터형 인덱스이다.
text
[ Root Node (Level 0) ]
       [ 25 |  ]  <-- 25를 기준으로 길을 나눔
      /        \
     /          \
[ Internal Node (Level 1) ]             [ Internal Node (Level 1) ]
    [ 15 | 20 ]                             [ 35 | 40 ]
   /     |     \                           /     |     \
  /      |      \                         /      |      \
[Leaf] [Leaf] [Leaf] <--- 연결 리스트 ---> [Leaf] [Leaf] [Leaf] (Level 2)
  |      |      |                         |      |      |
 [10]   [15]   [20]                      [25]   [30]   [35]   [40]
Record Record Record                   Record Record Record Record

상세 분석

  • Root & Internal Nodes (이정표):
    • 여기에는 실제 사용자 이름이나 나이 데이터가 없다.
    • 오직 PK(user_id)와 자식 노드의 주소만 기록된다.
    • 예: user_id = 22를 찾는다면,
      • Root에서 "25보다 작네?" -> 왼쪽 Internal 노드로 이동
      • "15, 20보다 크네?" -> 세 번째 Leaf 노드로 이동 순으로 탐색한다.
  • Leaf Nodes (실제 데이터):
    • B+Tree의 핵심
    • 여기에는 user_id뿐만 아니라 해당 행의 모든 데이터(name, age등)가 함꼐 저장된다.
  • Horizontal Linked List(옆으로 그어진 화살표)
    • 모든 Leaf 노드는 이전 / 다음 노드의 주소를 알고 있다
    • WHERE user_id BETWEEN 15 AND 35라는 쿼리가 들어오면, 트리 타고 15를 찾은 뒤
    • 그때부터는 트리를 다시 올라가지 않고 옆으로만 쭉 읽어서 35까지 가져온다. (매우 빠름!)

왜 이렇게 저장할까요?

  • 포인터의 최소화:
    • 만약 위 그림이 B-Tree였다면, Root 노드인 25에도 name, age 데이터가 붙어 있었을 것이다.
    • 그러면 노드 하나(페이지)에 담을 수 있는 PK 개수가 줄어들어 트리가 위로 길어진다. (높이가 높아짐)
  • Disk I/O의 법칙:
    • DB 성능의 병목은 보통 디스크 읽기이다.
    • B+Tree는 중간에 데이터가 없어서 한 페이지에 수백 개의 PK를 담을 수 있고
    • 덕분에 아무리 데이터가 많아도 보통 3~4번의 점프(I/O)면 원하는 데이터에 도달한다.

그렇다면 데이터 삽입/삭제될 때 어떤 일이 벌어질까?

데이터 삽입 시 : 노드 분할 (Node Split)

B+Tree는 정렬 상태를 유지해야 하며, 각 노드(Page)의 용량은 제한적이다.
(MySQL은 기본 16KB)

  • 빈 공간이 있을 때:
    • 정렬 순서에 맞춰 ㄹ해당 Leaf 노드에 데이터를 넣고 끝난다.
  • 노드가 가득 찼을 때(Split):
    • 해당 Leaf 노드를 두 개로 쪼갠다.
    • 중간값을 부모 노드로 올린다.
    • 만약 부모 노드도 가득 찼다면, Root 노드까지 이 현상이 전이될 수 있다.
    • 이 과정에서 새로운 페이지를 할당받고 데이터를 재배치하므로 비용이 많이 든다

데이터 삭제 시 : 노드 병합 (Node Merge / Rebalance)

삭제는 삽입의 반대 과정

  • 데이터 삭제:
    • 해당 키를 삭제 한다.
    • B+Tree는 실제 데이터가 Leaf에만 있으므로 항상 Leaf에서 삭제 발생
  • 언더플로우(Underflow):
    • 삭제 후 노드에 남은 데이터가 너무 적으면, (보통 50% 미만) 인덱스 구조가 비효율적으로 변한다
  • 병합 또는 재배치:
    • 옆 노드에서 데이터를 빌려오거나
    • 옆 노드와 하나로 합친다(Merge)
    • 이 과정에서도 부모 노드의 키가 수정되거나 삭제될 수 있다.

페이지(Page)의 의미

  • MySQL InnoDB 엔진에서 페이지(Page)는 디스크와 메모리(Buffer Pool) 사이에서 데이터를 주고받는 최소 작업 단위이며, 기본 설정값이 바로 16KB이다.
  • 이 페이지의 크기가 인덱스 구조와 성능에 미치는 영향은 결정적이다.

노드(Node) = 페이지(Page)

  • B+Tree 시각화 자료에서 보았던 '네모 박스' 하나가 곧 16KB짜리 페이지 하나라고 생각하면 된다.
    • Internal 노드 한 페이지:
      • 자식 노드들을 가리키는 이정표 역할
    • Leaf 노드 한 페이지:
      • 실제 데이터(Record)들이 담겨 있다.

페이지 크기가 인덱스에 미치는 영향

  • 트리의 높이

    • 페이지 크기가 고정되어 있기 떄문에, 인덱스 키(Column)의 사이즈가 작을수록 한 페이지에 더 많은 이정표를 담을 수 있다.
      • 만약 PK가 BIGINT(8바이트)라면, 페이지 주소값 등을 포함해도 한 페이지에 약 1,000개 이상의 자식 노드 주소를 저장할 수 있다.
      • 이 경우, 단 3개 층(Root - Internal - Leaf)만으로도 1,000 _ 1,000 _ 1000 = 10억 개의 레코드를 관리할 수 있다.
      • 결론: 페이지 크기 내에 키를 많이 채울수록 트리가 낮아지고, 데이터를 찾기 위한 디스크 I/O 횟수가 줄어들어 성능이 좋아진다.
  • 페이지 분할(Page Split)의 기준

    • 데이터를 INSERT하다가 해당 페이지의 여유 공간이 16KB를 넘어서려고 하면, 데이터베이스는 페이지를 두 개(8KB, 8KB)로 나눈다
      • 문제점: 페이지 분할이 일어나면 새로운 페이지를 할당받아야 하고, 상위 노드의 정보도 갱신해야 한다.
      • 이 작업 동안 해당 인덱스 범위에 락(Lock)이 걸릴 수 있어 동시성이 떨어진다.
  • 단편화(Fragmentation)

    • 데이터를 무작위로 삭제하거나 삽입하면 페이지 내부에 16KB를 다 채우지 못한 빈 공간이 많이 생긴다
    • 공간은 차지하는데 실제 데이터는 적은 상태가 되어, 풀 스캔시 읽어야할 페이지 수가 많아져 성능이 저하된다.
    • 이 때 OPTIMIZE TABLE로 페이지를 재정렬할 수 있다.
on this page
  • 01B+Tree란 무엇인가?
  • 02왜 B-Tree가 아니라 B+Tree인가?
  • 03그렇다면 B+Tree에 어떻게 저장되는지 살펴보자.
  • 예시 테이블 (MySQL InnoDB 기준)
  • B+Tree 인덱스 시각화 (Clustered Index)
  • 04그렇다면 데이터 삽입/삭제될 때 어떤 일이 벌어질까?
  • 데이터 삽입 시 : 노드 분할 (Node Split)
  • 데이터 삭제 시 : 노드 병합 (Node Merge / Rebalance)
  • 05페이지(Page)의 의미
  • 노드(Node) = 페이지(Page)
  • 페이지 크기가 인덱스에 미치는 영향

댓글 (0)