전주곡: 데이터베이스 인덱싱이란 무엇인가요?
비동기 인덱싱: 예를 들어 Couchbase와 같은 데이터베이스의 글로벌 보조 인덱스는 데이터 노드의 JSON 문서에 대한 읽기 및 쓰기에 영향을 주지 않고 생성, 업데이트 및 삭제할 수 있습니다. 즉, 인덱스별 삽입/업데이트/삭제가 비동기적으로 이루어지며 워크로드가 시스템의 나머지 부분과 격리됩니다.
쿼리 SLA를 이해하고 적절하게 인덱스를 생성하세요: 인덱스는 실행되는 N1QL 쿼리와 직접적으로 관련이 있습니다. N1QL과 GSI는 함께 탱고를 춥니다. 인덱스는 대기 시간 비용을 줄이고 처리량을 증가시키는 N1QL 쿼리의 스테로이드 역할을 합니다. 인덱스는 또한 자체 스토리지를 필요로 하지만, 느린(또는 조잡한) 고객 참여 경험으로 인해 비즈니스를 잃을 위험이 관련 비용보다 높습니다. 또한 대부분의 경우 인덱스는 애플리케이션 외부에 존재하므로 수명 주기를 적절하게 관리하는 데 도움이 됩니다.
최고의 고객 경험을 제공하기 위한 몇 가지 데이터베이스 인덱스 모범 사례를 살펴보겠습니다.
1. 자체 노드 집합에서 인덱스 서비스 실행
데이터, 쿼리, 인덱스, 검색 등 Couchbase의 모든 서비스를 모든 노드에서 실행할 수 있지만, 개별 워크로드는 자체 노드 세트에서 실행하는 것을 권장합니다. 이렇게 하면 워크로드를 격리하고 독립적으로 확장할 수 있습니다. 또한 워크로드의 특성에 따라 하드웨어를 적절히 할당할 수 있습니다. 예를 들어, 인덱스는 일반적으로 메모리 집약적이고 쿼리는 CPU 집약적입니다. 이렇게 서로 다른 서비스를 위해 서로 다른 하드웨어를 사용할 수 있습니다. 서비스별로 최상의 컴퓨팅 용량을 위한 독립적인 확장성은 다차원 확장(MDS)이 제공하는 아키텍처의 장점으로 달성할 수 있습니다.

모든 서비스가 사용 가능한 모든 노드에서 실행되는 경우

개별 서비스가 자체 전용 노드에서 실행되는 경우
2. MOI와 표준 GSI에 대한 이해:
카우치베이스 5.0은 플라즈마를 GSI용 새로운 스토리지 엔진. 설정 시 '표준 글로벌 보조'를 선택하면 밑줄이 그어진 스토리지 엔진이 플라즈마입니다. 두 스토리지 유형은 서로 다른 특성을 가지고 있습니다. 사용 사례에서 전체 인덱스가 메모리에 상주해야 하는 경우(더 엄격한 SLA, 더 낮은 레이턴시 + 더 높은 처리량), MOI를 선택합니다. 표준 GSI(플라즈마)는 전체 인덱스가 메모리에 상주할 수 없을 때 매우 유용하며, 이를 DGM(Data-Greater-Than-Memory) 시나리오라고 합니다. 또한 메모리 비용이 인덱스 유형을 결정하는 데 중요한 요소일 때 유용합니다. MOI는 메모리가 완전히 사용되면 일시 중지 모드에 들어갈 수 있지만(즉, 쿼리는 서비스되지만 인덱스 업데이트는 중지됨), Plasma는 적절하게 디스크로 넘어가서 쉽게 작동(그리고 데이터베이스의 인덱스는 업데이트됨)합니다. Couchbase 5.0에서 Plasma는 20% DGM 시나리오(즉, 인덱스 데이터의 20%가 메모리에 있는 경우)까지 잘 작동하며, 쿼리가 메모리와 디스크 모두에서 키에 액세스하는 경우 쿼리 중에 디스크 액세스가 분명하기 때문에 쿼리 성능에 적절한 영향이 있습니다.
완전히 메모리에 상주하는 특성으로 인해 MOI는 일반적으로 표준 GSI(특히 이전의 ForestDB)보다 훨씬 빠릅니다. 현재로서는 데이터베이스의 두 가지 유형의 인덱싱이 동일한 클러스터에 상주하는 것은 불가능합니다.
다음 도식에서는 사용 가능한 다양한 인덱스 저장소 엔진과 그 상위 기능에 대해 설명합니다.
3. 인덱스 복제본 사용 : GSI의 복제본은 활성 복제본으로, N1QL 쿼리의 부하를 분산하고 다른 인덱스 복제본이 실패할 경우 트래픽을 수용하는 두 가지 용도로 사용됩니다.
1 |
create 색인 idx on 버킷(field1) 와 함께 {"숫자_replica": 2} |
(또는)
1 |
create 색인 idx on 버킷(field1) 와 함께 {"노드":["1.2.3.1:8091", "1.2.3.2:8091", "1.2.3.3:8091"]} |
데이터 노드에서 문서 업데이트가 발생하면 두 인덱스 복사본이 비동기식으로 자동 업데이트됩니다. 항상 복제본이 하나 이상 있어야 하며, 이는 N1QL 쿼리를 서비스하기 위해 최소한 두 개의 인덱스 노드가 있어야 함을 의미합니다. 5.0에서는 스왑 리밸런싱이 지원되므로 인덱스 노드가 다운되고 새 노드가 다시 추가되면 토폴로지가 유지됩니다. 이는 쿼리의 계절성으로 인한 스케일업/다운 작업, 더 큰 노드와 더 작은 노드 사이를 이동하려는 경우에 매우 유용합니다.
이미 동등한 인덱스를 사용하고 있다면 복제본으로 전환하세요. 이 프로세스에 대해 자세히 알아보세요. 여기.
4. 인덱스 변형
GSI는 다양한 사용 사례에 따라 다양한 변형이 있습니다. 이러한 다양한 변형은 쿼리의 특성에 맞게 구축되었으므로 쿼리의 동작을 이해하고 이러한 인덱스 변형을 적절히 활용하는 것이 매우 중요합니다.
기본 색인 | 기능 색인 |
명명된 기본 색인 | 배열 색인 |
종합 지수 | 커버된 색인 |
부분 색인 | 적응형 인덱스 |
확인해 보세요 이 DZone 기사 그리고 이 문서 를 참조하세요.
데이터베이스 인덱스의 예로 커버된 인덱스를 살펴보겠습니다. 이 변형에는 술어와 정의에서 인덱싱된 모든 속성이 포함되어 있으므로 데이터 노드로의 추가 홉이 방지됩니다. 쿼리 지연 시간이 크게 줄어듭니다.
예를 들어 다음과 같은 경우가 있습니다:
1 |
만들기 INDEX `IDX_TS_TYPE_IATA` 켜기 `여행-샘플`(`유형`,`iATA`); |
그리고 쿼리를 사용합니다:
1 |
선택 iATA 에서 `여행-샘플` 어디 유형="항공사" 그리고 iATA = "TQ" |
그러면 설명 계획에서 쿼리가 인덱스에 '포함'되고 있음을 알 수 있습니다:
1 2 3 4 5 6 7 8 9 |
"~어린이": [ { "#operator": "IndexScan2", "커버": [ "커버 ((`여행 샘플`.`유형`))", "커버 ((`여행-샘플`.`IATA`))", "커버 ((메타(`여행 샘플`).`id`))" ], "index": "IDX_TS_TYPE_IATA", |
그리고 '모두'('select *' 사용) 속성을 선택하려고 합니다:
1 |
<span 스타일="글꼴 무게: 400">선택 * 에서 `여행-샘플` 어디 유형="항공사" 그리고 iATA = "TQ";</span> |
그런 다음 설명 계획은 쿼리 서비스가 모든 속성을 가져오기 위해 데이터 서비스로 이동해야 하므로 쿼리가 커버되지 않는('커버' 필드 누락) 것으로 나타났습니다:
1 2 3 4 5 6 7 8 |
"~어린이": [ { "#operator": "IndexScan2", "index": "IDX_TS_TYPE_IATA", "index_id": "240cf64d8c6ddce3", "index_projection": { "primary_key": true }, |
마찬가지로 배열 인덱스는 주로 고객이 JSON 데이터를 쿼리하는 데 도움을 주기 위해 만들어졌으며, 배열은 매우 일반적입니다. 이에 대한 자세한 포스팅이 곧 올라오겠습니다!
5. 프로덕션 환경에서 기본 키 사용 방지
예기치 않은 전체 기본 인덱스 스캔이 발생할 수 있으며, 프로덕션에서는 기본 인덱스를 아예 사용하지 않음으로써 이러한 발생 가능성을 제거해야 합니다. N1QL 인덱스 선택은 현재 규칙 기반 시스템으로 쿼리를 충족할 수 있는 인덱스가 있는지 확인하고, 인덱스가 없는 경우 기본 인덱스를 사용합니다. 기본 인덱스에는 문서의 모든 키가 있으므로 쿼리는 기본 인덱스에서 모든 키를 가져온 다음 데이터 서비스로 이동하여 문서를 가져온 다음 필터를 적용합니다. 보시다시피 이 작업은 매우 비용이 많이 드는 작업이므로 어떤 대가를 치르더라도 피해야 합니다.
생성된 기본 인덱스가 없고 쿼리를 처리할 일치하는 인덱스를 찾을 수 없는 경우 쿼리 서비스에서 다음 메시지와 함께 오류를 표시하므로 필요한 보조 인덱스를 만드는 데 도움이 됩니다:
"쿼리와 일치하는 키 스페이스 여행 샘플에 사용할 수 있는 인덱스가 없습니다. 인덱스 만들기 또는 기본 인덱스 만들기를 사용하여 인덱스를 만들거나 예상 인덱스가 온라인 상태인지 확인하세요.."
또한 고유한 인덱싱 모범 사례로서, 기본 인덱스의 파티셔닝은 Couchbase에서 지원되지 않습니다. 많은 RDBMS와 달리, 기본 키는 Couchbase에서 선택 사항입니다.
6. 설명 요금제 사용
N1QL 쿼리가 실제로 생성된 인덱스를 사용하고 있는지 확인하려면 다음을 확인합니다. 계획 설명 결과를 확인할 수 있습니다. Couchbase 관리 콘솔에서 코드 편집기에 쿼리를 붙여넣고 '설명' 버튼을 클릭하면 쉽게 확인할 수 있습니다. "1TP5운영자" 및 "색인" 속성을 확인하여 인덱스 사용을 확인합니다. 문서 링크.
7. 술어별 색인
쿼리에서 WHERE 절을 술어라고 하고 SELECT 절에서 선택한 필드/속성을 투영이라고 합니다. 인덱스는 항상 Predicate 절을 염두에 두고 생성해야 합니다. 인덱스 선택은 Predicate에 있는 인덱스의 선행 키를 기반으로 이루어지기 때문입니다.
예를 들어 4개의 속성에 대해 다음과 같은 인덱스가 있다고 가정합니다:
1 |
만들기 INDEX `IDX_TS_TYPE_IATA_NAME_ICAO` 켜기 `여행-샘플`(`유형`,`iATA`, `이름`,`icao`); |
그리고 쿼리하는 동안 실제로 icao 속성을 건너뛰는 다음 쿼리를 실행하면 쿼리 엔진은 최상의 쿼리 성능을 위해 위의 인덱스를 사용해야 한다는 것을 충분히 똑똑하게 알고 있습니다.
1 |
선택 이름 에서 `여행-샘플` 어디 icao="MLA" 그리고 유형="항공사"; |
선택한 인덱스는 아래 설명 계획에서 확인할 수 있습니다. 술어에 없는 'name'이 Projection에 있으므로 쿼리는 커버링 쿼리가 되며, 따라서 데이터 서비스로의 홉은 피할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 |
"~어린이": [ { "#operator": "IndexScan2", "커버": [ "커버 ((`여행 샘플`.`유형`))", "커버 ((`여행-샘플`.`IATA`))", "커버 ((`여행 샘플`.`이름`))", "커버 ((`여행 샘플`.`아이카오`))", "커버 ((메타(`여행 샘플`).`id`))" ], "index": "IDX_TS_TYPE_IATA_NAME_ICAO", |
8. 선행 키를 사용하여 인덱스 강제 선택
쿼리에 사용된 술어가 인덱스의 선행 키와 일치하지 않으면 인덱스가 쿼리에 대해 자동으로 선택되지 않습니다. 설명 계획에서 인덱스가 강제로 선택되지 않는 경우 'IS NOT MISSING' 또는 'IS NOT NULL' 절을 사용하여 인덱스를 강제로 선택하도록 합니다.
Fo예를 들어, 다음 쿼리 중 하나를 입력합니다:
1 2 |
선택 카운트(1) 에서 `여행-샘플` 어디 유형 IS NOT NULL; 선택 카운트(1) 에서 `여행-샘플` 어디 유형 IS NOT 누락; |
은 인덱스의 선행 키가 'type'이므로 다음 인덱스를 사용합니다:
1 |
만들기 INDEX `IDX_TS_TYPE_IATA` 켜기 `여행-샘플`(`유형`,`iATA`); |
이미 생성된 인덱스만 선택하려면 사용 색인 지시어를 N1QL 쿼리의 일부로 사용할 수 있습니다. 이는 USE INDEX에 언급된 인덱스가 N1QL 규칙 기반 최적화 도구에서 선택한 인덱스보다 더 나은 선택성을 가지고 있다는 것을 알고 있는 경우에 유용합니다:
1 |
선택 카운트(1) 에서 `여행-샘플` 사용 INDEX (IDX_TS_TYPE_IATA) 어디 유형="항공사"; |
9. 부분 인덱스 사용
때때로 인덱싱할 술어가 크기 제한으로 인해 하나의 노드에 맞지 않을 수 있습니다. Couchbase의 GSI는 현재로서는 자동으로 분할되지 않습니다. 따라서 관리자가 부분 인덱스를 생성해야 하며, N1QL 쿼리는 부분 인덱스가 있는 경우 쿼리에 사용된 술어의 종류에 따라 적절한 인덱스를 선택할 수 있을 만큼 똑똑합니다.
예를 들어, 이름이 두 개의 서로 다른 범위에 있는 것을 기준으로 다음 두 개의 인덱스를 생성합니다:
1 2 |
만들기 INDEX `IDX_TS_NAME_AK` 켜기 `여행-샘플`(`이름`) 어디 이름 사이 "A" AND "K"; 만들기 INDEX `IDX_TS_NAME_KZ` 켜기 `여행-샘플`(`이름`) 어디 이름 사이 "K" AND "Z"; |
이제 다음 쿼리는 각 설명 계획에 명시된 대로 적합한 인덱스를 자동으로 선택합니다:
1 2 3 4 5 6 7 |
선택 * 에서 `여행-샘플` 어디 이름="아스트라에우스"; 설명 플랜 : "~어린이": [ { "#operator": "IndexScan2", "index": "IDX_TS_NAME_AK", |
1 2 3 4 5 6 7 |
선택 * 에서 `여행-샘플` 어디 이름="텍사스 윙"; 설명 플랜 : "~어린이": [ { "#operator": "IndexScan2", "index": "IDX_TS_NAME_KZ", |
술어에 LIKE 절을 사용할 때 인덱스가 적절하게 선택됩니다. 예를 들어 프랑스어 이름('L'로 시작)처럼 들리는 모든 이름을 가져오고 싶다고 가정해 보겠습니다:
1 2 3 4 5 6 |
선택 * 에서 `여행-샘플` 어디 이름 같은 "L'%"; 설명 플랜 : "~어린이": [ { "#operator": "IndexScan2", "index": "IDX_TS_NAME_KZ", |
10. 일관성 옵션
비동기적 특성으로 인해 Couchbase의 GSI는 기본적으로 일관성을 유지하며 이미 언급했듯이 비동기적으로 업데이트됩니다. 변경 피드(DCP)를 사용하여 가능한 한 빨리 인덱스를 업데이트하지만, 특정 문서 변경이 인덱스에서 업데이트되지 않았을 가능성이 매우 높습니다. 쿼리 의미론에서 더 엄격한 데이터 일관성이 요구되는 경우, Couchbase는 쿼리 시점에 조정 가능한 일관성 모델을 제공합니다.
Couchbase에서 사용할 수 있는 일관성 옵션은 세 가지입니다:
- 스캔 일관성=not_bounded
- 스캔 일관성=at_plus
- 스캔 일관성 = 요청_플러스
자세히 알아보기: 문서 링크
요청_플러스 의미론은 데이터 무결성을 보장하지만 쿼리 대기 시간이 증가함에 따라 성능에 영향을 미치며, 쿼리는 데이터가 반환되기 전에 관련 인덱스가 최신 변형을 따라잡을 때까지 기다립니다. 'not_bounded'(기본 일관성 옵션)는 3가지 일관성 옵션 중 가장 빠른 옵션입니다.
11. 모니터 인덱스 따라잡기
일반적으로 인덱스 서비스는 문서 변경을 매우 빠르게 따라잡아 사용자에게 거의 영향을 주지 않습니다. 하지만 관리자로서 (색인에서 업데이트될) 문서 변경이 가능한 한 최소화되고 계속 증가하지 않도록 하려면 색인 이름 아래의 '남은 항목' 메트릭을 살펴보세요.
12. 빌드 지연 사용
디퍼 빌드는 인덱스를 생성하는 2단계 프로세스를 제공합니다. 노드에서 인덱스를 생성하는 데 동일한 변경 피드가 사용되므로 항상 디퍼 빌드를 최적으로 사용하는 것이 좋습니다. 디퍼 빌드를 사용하지 않으면 데이터 노드의 변경 피드에 여러 번 액세스해야 하므로 네트워크를 통해 더 많은 데이터가 전송되고 데이터 노드의 부하가 약간 증가합니다.
예시:
1 2 |
만들기 INDEX `IDX_TS_TYPE_IATA` 켜기 `여행-샘플`(`유형`,`iATA`) WITH { "defer_build":true }; 빌드 INDEX 켜기 `여행-샘플`(`IDX_TS_TYPE_IATA`); |
인덱스 생성 구문에 대한 자세한 내용은 다음을 참조하세요. 문서.
13. 인덱싱할 큰 키는 피하세요.
5.0 이전에는 인덱스의 키 크기에 제한이 있었습니다(최대 4k). 이 제한은 5.0에서 제거되었습니다. 인덱스는 데이터 액세스 경로를 위한 것이므로, 데이터 모델과 쿼리(인덱스 포함)는 필요한 정보를 최단 시간에 얻을 수 있도록 구조화되어야 합니다. 고객은 복합 인덱스에 필드를 얼마든지 포함할 수 있지만, 인덱스 키 크기도 그에 비례하여 커집니다. 키 크기가 너무 크면 성능에 영향을 미칠 수 있습니다. 일반적으로 복합 인덱스의 모든 필드를 합친 크기가 1kB가 되도록 하고, 이것이 불가능하다면 쿼리를 적절히 리팩터링하세요.
14. 키 사용, 인덱스 피하기
모든 N1QL 쿼리에 인덱스가 필요한 것은 아닙니다. 키를 사용하여 문서를 직접 쿼리함으로써 N1QL 쿼리가 인덱스와 독립적으로 작동할 수 있는 경우 USE KEYS 지시문이 유용합니다.
예를 들어
1 |
선택 * FROM `여행-샘플` 사용 키 ["landmark_37588"]; |
결과 설명 계획에는 (인덱스 스캔에 대한 언급 없이) 키 스캔이 수행되는 것이 표시됩니다:
1 2 3 4 5 |
"~어린이": [ { "#operator": "KeyScan", "keys": "[\"landmark_37588\"]" } |
USE KEYS는 쿼리 서비스에서 결과를 반환하는 데 인덱스를 사용하지 않으므로 이는 모범 사례라기보다는 알아두어야 할 사항입니다. 고객이 항상 USE KEYS를 사용하는 쿼리만 가질 가능성은 거의 없지만, 이러한 동작이 필수인 엣지 케이스에서는 유용할 수 있습니다.
긴 글입니다!! 하지만 데이터베이스와 인덱스를 이해하는 데 도움이 되었기를 바라며, DBMS 모범 사례에서 인덱싱이 어떻게 우수한 고객 경험을 제공하는 데 도움이 될 수 있는지 :)
추신: GSI 개요 및 Couchbase Server 5.0의 새로운 기능: https://www.youtube.com/watch?v=OrC2gkm2OFA