+ 근거·비용·검증법까지 균형 있게 제시했다.
- 상태 컬럼 포함과 부분 인덱스 대안을 더 비교해야 한다.
쿼리 패턴을 받아 어떤 인덱스가 필요한지, 있는 걸로 되는지 가립니다. 쓰기 비용도 같이 셉니다.
| 분류 | 개발 › 데이터·DB |
|---|---|
| 태그 | 분석검토개발자 |
Decide which indexes this workload needs. Work in this order: 1. **Check coverage first.** For each query, does an existing index already serve it by its leading columns? *A composite index covers its prefixes — most proposed indexes are redundant with one that already exists.* 2. For genuinely uncovered access paths, the index that would serve it: columns, order, and why that order. 3. **Write cost.** Every index is maintained on insert, update, and delete. At my stated write load, say what this costs — and flag any index on a column that is updated frequently, since that blocks in-place row updates. 4. Redundant indexes already present that should be dropped. Dropping is as valuable as adding and never gets done. 5. Whether a partial or covering index fits better than a plain one. 6. Size estimate, and whether it will stay in memory. Then: an ordered list — add these, drop these, leave these — with the measurement that would confirm each. Rules: - *Do not propose an index per query.* Consolidate where one composite serves several. - Say when the answer is "no new index; rewrite the query" or "no new index; this table is small enough". - Where the query patterns do not tell you selectivity, say so — an index on a low-selectivity column usually will not be used.
인덱스는 읽기를 빠르게 하고 쓰기를 느리게 한다. 이 프롬프트는 추가로 얻는 것과 잃는 것을 같이 세고, 기존 인덱스의 선두 컬럼으로 커버되는지부터 확인한다.
ChatGPT가 불확실성을 밝히며 가장 충실하다. Gemini는 구체적이나 추정치를 사실처럼 단정했고, [C]는 답변이 없다.
+ 근거·비용·검증법까지 균형 있게 제시했다.
- 상태 컬럼 포함과 부분 인덱스 대안을 더 비교해야 한다.
+ 접근 경로별 인덱스 순서를 명확히 설명했다.
- 선택도·피크 분포·데이터량을 근거 없이 단정했다.
| 기준 | ChatGPT | Gemini | 앞선 모델 |
|---|---|---|---|
| 지시 준수 | 9 | 6 | ChatGPT +50% |
| 정확성 | 9 | 5 | ChatGPT +80% |
| 구체성 | 9 | 8 | ChatGPT +13% |
| 구성·가독성 | 9 | 9 | 동점 |
| 분량 적정성 | 8 | 6 | ChatGPT +33% |
gpt-5.6-sol가 모델 이름을 가린 채 1~10점으로 채점했습니다 (2026-09-25). 측정값이 아니라 AI 심사 결과입니다.
같은 입력을 세 모델에 넣어 받은 답을 그대로 옮겼습니다. 각 CLI(에이전트 하네스)에서 실행했습니다. ChatGPT·Claude 앱이나 웹에서는 답이 다를 수 있습니다.
주요 쿼리: 1. 주문 목록: SELECT * FROM orders WHERE user_id = ? AND status = 'PAID' ORDER BY created_at DESC LIMIT 20; 2. 스토어 매출 집계: SELECT SUM(total_price) FROM orders WHERE store_id = ? AND created_at BETWEEN ? AND ?; 3. 상품 리뷰 조회: SELECT * FROM reviews WHERE product_id = ? AND is_hidden = false ORDER BY created_at DESC; 4. 고객센터 검색: SELECT * FROM orders WHERE phone_number = ?; 기존 인덱스: orders: PK(id), idx_orders_user_id(user_id), idx_orders_created_at(created_at) reviews: PK(id) 외 인덱스 없음 쓰기 빈도: orders 테이블 하루 약 8,000건 INSERT, 결제 상태 변경으로 건당 평균 3회 UPDATE (피크 11~13시, 18~20시)
| 쿼리 | 기존 인덱스 | 판단 |
|---|---|---|
주문 목록: user_id = ? AND status = 'PAID' ORDER BY created_at DESC |
idx_orders_user_id(user_id) |
user_id 필터만 커버. status와 정렬은 추가 스캔/정렬 필요 |
매출 집계: store_id = ? AND created_at BETWEEN ... |
idx_orders_created_at(created_at) |
created_at만 커버. store_id별 범위 검색에는 부적합 |
리뷰 조회: product_id = ? AND is_hidden = false ORDER BY created_at DESC |
없음 | 미커버 |
전화번호 검색: phone_number = ? |
없음 | 미커버 |
idx_orders_user_id는 새 복합 인덱스가 생기면 선두 컬럼 prefix로 대체할 수 있습니다. idx_orders_created_at은 현재 제시된 쿼리만 보면 대체 사용처가 없습니다.
CREATE INDEX idx_orders_user_status_created
ON orders (user_id, status, created_at DESC);
user_id, status: 동등 조건이므로 앞에 배치created_at DESC: ORDER BY ... DESC LIMIT 20을 인덱스 순회로 처리user_id가 보통 고객 식별자로 충분히 선택적이므로 status보다 앞이 적절합니다.status는 결제 상태 변경 때마다 갱신되므로 이 인덱스는 잦은 업데이트 비용이 있습니다.SELECT *이므로 일반적인 covering index로 만들기는 어렵습니다. 모든 주문 컬럼을 INCLUDE하는 것은 인덱스가 지나치게 커질 가능성이 높아 권하지 않습니다.
CREATE INDEX idx_orders_store_created
ON orders (store_id, created_at);
가능한 DB라면 total_price를 포함하는 covering 형태가 더 좋습니다.
-- PostgreSQL 예시
CREATE INDEX idx_orders_store_created
ON orders (store_id, created_at)
INCLUDE (total_price);
store_id로 먼저 좁힌 뒤 created_at 범위를 검색해야 하므로 이 순서가 필요합니다. total_price를 포함하면 테이블 본문 접근 없이 SUM을 계산할 수 있지만, 인덱스 크기와 쓰기 비용은 증가합니다.
is_hidden = false가 항상 고정된 조건이라면 partial index가 적합합니다.
CREATE INDEX idx_reviews_visible_product_created
ON reviews (product_id, created_at DESC)
WHERE is_hidden = false;
partial index를 지원하지 않거나 쿼리 패턴이 바뀔 수 있다면 다음을 사용합니다.
CREATE INDEX idx_reviews_product_hidden_created
ON reviews (product_id, is_hidden, created_at DESC);
is_hidden의 true 비율이 높을수록 partial index의 이점이 큽니다. SELECT *이므로 리뷰 전체 행을 포함하는 covering index는 대체로 비경제적입니다.
CREATE INDEX idx_orders_phone_number
ON orders (phone_number);
전화번호가 사실상 고객별로 유일하고 데이터 품질상 보장된다면 UNIQUE 인덱스도 검토할 수 있습니다. 반대로 동일 전화번호가 여러 주문에 반복되고 조회 선택도가 낮다면 실행 계획에서 인덱스를 사용하지 않을 수 있습니다. 현재 정보만으로는 전화번호의 선택도를 알 수 없습니다.
orders의 평균 일일 변경량은 다음과 같습니다.
orders에 제안하는 세 인덱스는 각각 다음 비용이 있습니다.
(user_id, status, created_at): INSERT 8,000회 + 상태 변경 UPDATE 약 24,000회에서 유지 비용 발생(store_id, created_at): INSERT 시 유지 비용 발생. 두 컬럼이 변경되지 않는다면 일반 UPDATE에는 보통 영향이 작음(phone_number): INSERT 시 유지 비용 발생. 전화번호 변경 시에도 비용 발생특히 status가 복합 인덱스에 들어가므로 결제 상태 변경 때 인덱스 엔트리를 삭제하고 다시 삽입해야 합니다. 이는 in-place row update를 방해할 수 있고, 피크 시간대인 1113시와 1820시에 random I/O, WAL/redo, 페이지 분할, 잠금 경합을 키울 수 있습니다.
리뷰 partial index는 리뷰 INSERT와 is_hidden 변경 때만 유지됩니다. 리뷰의 숨김 처리도 빈번하다면 partial 여부와 실제 변경량을 함께 측정해야 합니다.
DROP INDEX idx_orders_user_id;
idx_orders_user_id는 (user_id, status, created_at)의 선두 prefix로 대체됩니다. 별도 쿼리가 user_id만 사용하는 경우에도 복합 인덱스가 선두 컬럼을 제공하므로 보통 중복입니다.
DROP INDEX idx_orders_created_at;
제시된 쿼리만 기준으로는 단독 created_at 인덱스를 사용하는 쿼리가 없습니다. 다른 기간별 주문 조회나 최근 주문 조회가 없다면 삭제 대상입니다.
삭제 전에는 실제 사용 여부를 확인해야 합니다. 운영 환경에서 pg_stat_user_indexes, MySQL의 performance_schema, 또는 쿼리 로그/실행 통계를 확인하십시오.
정확한 크기는 현재 행 수, 평균 키 길이, DB 엔진, 페이지 크기, 중복도 정보가 없어 계산할 수 없습니다.
대략적인 산정은 다음과 같습니다.
(user_id, status, created_at): orders 행당 대략 40~80바이트 수준(store_id, created_at) + total_price 포함: 대략 50~100바이트 이상phone_number: 전화번호 길이에 따라 대략 30~70바이트is_hidden = false인 행에만 크기 발생orders에 백만 행이 있다면 새 인덱스 3개만 대략 수백 MB가 될 수 있습니다. 실제 값은 pg_relation_size, SHOW TABLE STATUS, 또는 DB별 인덱스 크기 도구로 확인해야 합니다.
인덱스 전체가 메모리에 남는다고 가정하면 안 됩니다. 자주 실행되는 상위 인덱스와 최근 데이터가 버퍼 풀에 남을 가능성이 높고, 매출 집계용 범위 스캔은 기간이 넓으면 디스크 접근을 발생시킬 수 있습니다. 다음을 측정해야 합니다.
EXPLAIN (ANALYZE, BUFFERS) 또는 DB별 실행 계획CREATE INDEX idx_orders_user_status_created
ON orders (user_id, status, created_at DESC);
CREATE INDEX idx_orders_store_created
ON orders (store_id, created_at);
CREATE INDEX idx_reviews_visible_product_created
ON reviews (product_id, created_at DESC)
WHERE is_hidden = false;
CREATE INDEX idx_orders_phone_number
ON orders (phone_number);
단, 전화번호의 선택도가 낮으면 추가하지 않고 실행 계획으로 확인합니다.
DROP INDEX idx_orders_user_id;
DROP INDEX idx_orders_created_at;
각 인덱스가 실제로 사용되지 않는다는 인덱스 사용 통계가 확인된 뒤 삭제합니다.
orders의 PK(id)user_id, store_id, phone_number, 리뷰의 product_id별 행 수와 실제 쿼리의 EXPLAIN ANALYZE 결과를 보면 최종 인덱스 순서와 partial/covering 여부를 확정할 수 있습니다.
| 대용량 텍스트 변환기 프롬프트 | |
| AI 데이터 추출 도구 기획 프롬프트 | |
| AI2sql 자연어 SQL 생성 프롬프트 | |
| 백엔드 아키텍트 역할을 맡기는 프롬프트 | |
| Base R 프로그래밍 프롬프트 |