데이터베이스를 변경하기 전에 비용이 큰 요청을 찾아라
카테고리 로드, 기사 검색, 장바구니 추가, 주문 제출 같은 사용자 행동에서 시작하세요. 해당 경로, 쿼리 수, 데이터베이스 소요 시간, 반환된 행 수, 요청 전체 시간을 캡처합니다. 느린 페이지는 애플리케이션 코드나 다른 종속성에서 대기하고 있을 수도 있으니 페이지 로드 시간만으로 진단하지 마세요.
쿼리 계획은 테이블, 인덱스, 조인, 정렬, 집계를 통한 데이터베이스의 경로입니다. PostgreSQL에서는 EXPLAIN이 그 경로를 보여줍니다. EXPLAIN ANALYZE는 문장을 실행해 실제 행 수와 시간을 보고하므로 안전한 대표 쿼리를 사용하세요. 추정과 실제 행 수의 큰 차이는 통계나 조건을 조사할 이유이지 자동 인덱스 추가의 근거는 아닙니다.
후보 쿼리를 경로와 결과에 연결해 대시보드만의 보고가 느린 결제 시나리오를 능가하지 않게 하세요.
변경 전 타이밍과 비교 가능한 트래픽을 기록하되 고객 식별자, 토큰, 카드 관련 값은 로깅하지 마세요.
실제로 사용하는 접근 경로에 맞춰 인덱스를 설계하라
인덱스는 행을 찾거나 정렬된 결과를 반환하는 데 도움이 되는 유지되는 구조로, 만능 속도 스위치가 아닙니다. 중요한 쿼리의 필터, 조인 키, 정렬 순서부터 시작한 다음 제안한 인덱스를 계획에서 확인하세요. PostgreSQL 문서는 일치하는 B-tree 인덱스가 특히 작은 LIMIT와 함께 별도 정렬 없이 ORDER BY를 만족할 수 있음을 설명합니다.
인덱스는 삽입, 업데이트, 삭제 시 유지 작업이 필요하므로 저장공간과 쓰기 비용을 추가합니다. 쿼리가 테이블의 큰 비율을 필요로 하면 순차 스캔과 명시적 정렬이 더 빠를 수 있습니다. 운영에 근사한 데이터로 테스트하고 이득이 비용을 정당화할 때만 인덱스를 유지하세요.
인터페이스가 안정적 순서를 필요로 하면 결정적 타이브레이커를 포함해 쿼리의 실제 필터와 ORDER BY를 사용하세요.
인덱스가 실제로 사용되는지와 쓰기 시 비용을 검토하고 데이터와 트래픽이 변할 때 재검토하세요.
페이지네이션을 안정적이고 합리적으로 만들어라
페이지 번호 방식의 페이지네이션은 일반적으로 LIMIT와 OFFSET을 사용합니다. 항상 예측 가능한 정렬을 강제하세요. PostgreSQL은 ORDER BY 없이 LIMIT를 사용하면 반환되는 하위 집합이 예측 불가능할 수 있으며, 다른 LIMIT 또는 OFFSET 값에서 옵티마이저가 다른 계획을 선택할 수 있다고 경고합니다.
큰 OFFSET은 건너뛰는 행도 계산해야 하므로 주의가 필요합니다. 깊은 카탈로그, 주문 기록, 아카이브 브라우징에는 커서나 키셋(keyset) 페이지네이션을 고려하세요: 마지막으로 본 안정적 정렬 값과 고유 타이브레이커 이후의 행을 요청합니다. 인터페이스를 변경하기 전에 신규 또는 삭제된 레코드가 어떻게 나타날지 정의하세요.
무제한 결과 대신 가장 작은 유용한 페이지를 반환하세요.
인터페이스가 매 요청마다 정확한 총계를 정말로 필요로 하는지 결정하세요.
잘못된 응답을 제공하지 않고 읽기 작업을 캐시하라
캐싱은 이전 결과를 재사용해 데이터베이스에서 다시 구성하는 대신 이용합니다. 공개적이고 반복 가능한 페이지 및 버전된 자산에 대해 우선 HTTP 레이어에서 시작하세요. 캐시 적중(hit)은 애플리케이션과 데이터베이스 작업을 피할 수 있습니다. web.dev는 버전된 URL에 대해 장기 캐싱을 권장하며, URL을 변경하는 것이 실무적인 무효화 메커니즘이라고 설명합니다.
캐시 정책은 정합성(정확성)에 관한 결정입니다. 공유 캐시는 한 응답을 여러 사용자에게 재사용할 수 있으므로 계정, 장바구니, 가격, 인증별 출력을 설계된 범위로 공유하지 마세요. Cache-Control의 private은 브라우저 캐시로의 재사용만 허용하고, no-store는 캐시가 유지하지 않도록 지시합니다.
공개 카탈로그나 기사 출력과 세션별, 위치별, 권한 민감 출력을 분리하세요.
누가 얼마나 오랫동안 오래된 값을 볼 수 있는지, 그리고 무효화 또는 재검증 경로를 정의하세요.
단순히 상한을 올리지 말고 연결 용량을 보호하라
연결은 데이터베이스로의 라이브 클라이언트 세션입니다. 워커, 크론 작업, 미리보기, 재시도는 개별 쿼리가 허용 범위에 있어도 슬롯을 고갈시킬 수 있습니다. PostgreSQL의 max_connections는 동시 연결을 제어하며 서버 시작 시 설정되고 높은 값은 공유 메모리를 포함해 더 많은 리소스를 할당합니다. 워크로드 모델 없이 값을 올리면 실패 지점이 이동할 수 있습니다.
웹 워커, 백그라운드 작업, 관리, 레플리카에 걸쳐 하나의 예산을 설정하세요. 적합한 곳에는 바운디드 풀링(bounded pooling)을 사용하고 복구를 위한 여유(headroom)를 보존하며 방치된 작업이 무한정 기다리지 않도록 타임아웃을 적용하세요. 안전한 수치는 엔진, 메모리, 쿼리 혼합, 제공자 한도에 따라 다르므로 운영자와 확인하세요.
웹 프로세스, 큐, 예약 작업, 마이그레이션, 보고 도구, 배포 작업을 포함하세요.
동시성을 제한하고 백오프를 사용해 장애가 연결 수요를 증폭시키지 않도록 하세요.
문맥과 프라이버시 제어로 쿼리 비용을 관찰하라
단일 느린 샘플이 아니라 총 영향으로 데이터베이스 작업을 순위 매기세요. 적당히 느린 쿼리가 수천 번 호출되면 드물게 발생하는 하나의 느린 리포트보다 더 중요할 수 있습니다. PostgreSQL의 pg_stat_statements는 호출 수, 총/평균 실행 시간, 행 수, 버퍼 활동 등 계획 및 실행 데이터를 집계합니다. 이를 추가하거나 제거하려면 서버 구성과 재시작이 필요하며, 계획 추적은 오버헤드를 추가할 수 있습니다.
쿼리 그룹을 경로, 릴리스 버전, 시간 창과 연결하세요. OpenTelemetry는 리터럴을 대체해 쿼리 텍스트를 기본적으로 수집할 때는 먼저 정규화(sanitization)하고 그룹화를 위한 저카디날리티 요약을 사용하라고 권장합니다. 이는 소유자가 고객 데이터를 외부로 내보내거나 각 값에 대한 라벨을 생성하지 않고 추세를 볼 수 있게 합니다.
지연 시간, 총 시간, 호출 수, 오류, 연결, 행 수를 검토하세요; 각 항목은 다른 실패 모드를 드러냅니다.
파라미터화된 쿼리를 사용하고 추적이나 로그가 애플리케이션을 떠나기 전에 리터럴이 제거되었는지 확인하세요.
데이터베이스 변경은 신뢰성 작업으로 되돌릴 수 있게 배포하라
인덱스, 재작성, 캐시 정책, 풀 설정, 스키마 변경을 프로덕션 변경으로 취급해 소유자, 성공 측정치, 롤백 기준을 지정하세요. 대표 환경에서 검증하고 제한된 범위에 릴리스한 뒤 영향받는 여정을 관찰하세요. 빠르다고 성공한 것이 아니며 변경이 잘못된, 누락된, 중복된, 혹은 오래된 데이터를 반환하면 실패입니다.
스키마 변경은 애플리케이션 버전이 중첩될 수 있으므로 주의가 필요합니다. Google Cloud 지침은 호환성 순서를 설명합니다: 구 버전과 새 스키마를 처리하는 애플리케이션을 먼저 릴리스하고, 스키마를 변경한 뒤 새 스키마를 사용하는 기능을 릴리스하세요. 이렇게 하면 애플리케이션 롤백 경로를 보존합니다. 락과 온라인 변경 동작은 엔진과 관리형 서비스마다 다르므로 고부하 또는 결제 관련 시스템은 자격을 갖춘 검토를 받으세요.
경로, 성능 및 정합성 신호, 배포를 중지할 문턱값을 명명하세요.
확대 전에 통제된 범위에서 성능과 정합성을 검증하고, 사고 외의 상황에서도 되돌리는 연습을 하세요.
