2026년 2월 19일
[커넥트 지누] 인덱스를 활용해 학식 조회 성능을 개선해보기
인덱스 조회
SELECT * FROM pg_indexes WHERE tablename = 'cafeteria_diet';
실행 결과
indexname | indexdef |
cafeteria-diet_pkey | CREATE UNIQUE INDEX "cafeteria-diet_pkey" ON public.cafeteria_diet USING btree (diet_id) |
인사이트
- PostgreSQL은 MySQL과 다르게 FK 제약조건 생성 시 자동으로 인덱스를 만들어주지 않는다.
최적화 대상 쿼리
SELECT dish_category, dish_type, dish_name FROM cafeteria_diet WHERE cafeteria_id = 4 AND date = '2025-05-13' AND time = '점심';
실행 결과
dish_category | dish_type | dish_name |
한식 | 주식 | 기장밥 |
한식 | 국류 | 해물보탕국 |
한식 | 찬류 | 치즈불닭 |
3. 실험 결과
3.1. 인덱스 없이 실행했을 때
EXPLAIN ANALYZE SELECT dish_category, dish_type, dish_name FROM cafeteria_diet WHERE cafeteria_id = 4 AND date = '2025-05-13' AND time = '점심';
QUERY PLAN |
Seq Scan on cafeteria_diet (cost=0.00..1007.70 rows=3 width=39) (actual time=1.263..3.327 rows=9 loops=1) |
Filter: ((cafeteria_id = 4) AND (date = '2025-05-13'::date) AND (("time")::text = '점심'::text)) |
Rows Removed by Filter: 32831 |
Planning Time: 0.068 ms |
Execution Time: 3.350 ms |
2. FK 단일 인덱스 적용
인덱스 생성
CREATE INDEX idx_cafeteria_diet_cafeteria_id ON cafeteria_diet (cafeteria_id);
실행 결과
QUERY PLAN |
Bitmap Heap Scan on cafeteria_diet (cost=20.86..494.14 rows=3 width=39) (actual time=0.260..0.425 rows=9 loops=1) |
Recheck Cond: (cafeteria_id = 4) |
Filter: ((date = '2025-05-13'::date) AND (("time")::text = '점심'::text)) |
Rows Removed by Filter: 2279 |
Heap Blocks: exact=84 |
-> Bitmap Index Scan on idx_cafeteria_diet_cafeteria_id (cost=0.00..20.86 rows=2302 width=0) (actual time=0.089..0.089 rows=2288 loops=1) |
Index Cond: (cafeteria_id = 4) |
Planning Time: 0.083 ms |
Execution Time: 0.447 ms |
3. 복합 인덱스 적용
인덱스 생성
CREATE INDEX idx_cafeteria_diet_date_cafeteria_time ON cafeteria_diet (date, cafeteria_id, time);
실행 결과
QUERY PLAN |
Index Scan using idx_cafeteria_diet_date_cafeteria_time on cafeteria_diet (cost=0.29..4.74 rows=5 width=39) (actual time=0.015..0.019 rows=9 loops=1) |
Index Cond: ((date = '2025-05-13'::date) AND (cafeteria_id = 4) AND (("time")::text = '점심'::text)) |
Planning Time: 0.080 ms |
Execution Time: 0.043 ms |
4. 커버링 인덱스 적용
커버링 인덱스 추가
CREATE INDEX idx_cafeteria_diet_date_cafeteria_time_cover ON cafeteria_diet (date, cafeteria_id, time) INCLUDE (dish_category, dish_type, dish_name);
실행 결과
QUERY PLAN |
Index Only Scan using idx_cafeteria_diet_date_cafeteria_time_cover on cafeteria_diet (cost=0.41..2.68 rows=3 width=39) (actual time=0.019..0.021 rows=9 loops=1) |
Index Cond: ((date = '2025-05-13'::date) AND (cafeteria_id = 4) AND ("time" = '점심'::text)) |
Heap Fetches: 0 |
Planning Time: 0.083 ms |
Execution Time: 0.041 ms |
실험 결과 비교
인덱스 구성 | 실행 계획 | 실행 시간 | 인덱스 크기 |
1️⃣ 인덱스 없음 | Seq Scan | 3.350 ms | - |
2️⃣ FK 단일 인덱스 | Bitmap Heap Scan | 0.447 ms | 240 kB |
3️⃣ 복합 인덱스 | Index Scan | 0.043 ms | 432 kB |
4️⃣ 커버링 인덱스 | Index Only Scan | 0.041 ms | 2656 kB |
결론
- 실험 결과, 본 쿼리 패턴에서는 복합 인덱스가 성능과 저장 공간 측면에서 가장 합리적인 선택임을 확인하였다.
