문맥
이 질문은 SQL 및 NoSQL 데이터베이스 시스템 모두에서 인덱스의 저수준 구현 세부 사항에 관한 것입니다. 인덱스의 실제 구조 (B + 트리, 해시, SSTable 등)는 특정 구현의 단일 노드 내에 저장된 키 와 관련이 있기 때문에 관련이 없습니다 .
배경
SQL (예 : MySQL) 및 NoSQL (CouchDB, MongoDB 등) 데이터베이스에서 열 또는 JSON 문서 데이터 필드에 색인을 작성할 때 실제로 데이터베이스에서 수행하는 작업은 본질적 으로 모든 항목의 정렬 된 목록을 작성 하는 것입니다. 파일과 함께 해당 값은 해당 값과 관련된 레코드가있는 기본 데이터 파일로 오프셋됩니다.
(간단하게하기 위해 특정 impls의 다른 난해한 세부 사항을 손으로 흔드는 것일 수 있음)
간단한 클래식 SQL 예
인덱스를 생성하는 간단한 32 비트 int 기본 키가있는 표준 SQL 테이블을 고려하면 데이터 파일에 대한 64 비트 오프셋과 정렬 된 정수 키의 디스크상의 인덱스가 생성됩니다. 레코드 수명, 예 :
id | offset
--------------
1 | 1375
2 | 1413
3 | 1786
인덱스에서 키의 디스크 상 표현은 다음과 같습니다.
[4-bytes][8-bytes] --> 12 bytes for each indexed value
파일 시스템 및 데이터베이스 시스템으로 디스크 I / O를 최적화하는 표준 규칙을 준수하여 디스크에 4KB 블록으로 키를 저장한다고 가정 해 봅시다.
4096 bytes / 12 bytes per key = 341 keys per block
인덱스의 전체 구조 (B + 트리, 해시, 정렬 된 목록 등)를 무시하면 한 번에 341 개의 키 블록을 읽고 메모리에 쓰고 필요에 따라 디스크에 다시 씁니다.
쿼리 예
이전 섹션의 정보를 사용하여 "id = 2"에 대한 쿼리가 발생한다고 가정하면 클래식 DB 인덱스 조회는 다음과 같습니다.
- 인덱스의 루트를 읽습니다 (이 경우 1 블록).
- 키를 찾기 위해 정렬 된 블록을 이진 검색
- 값에서 데이터 파일 오프셋을 가져옵니다.
- 오프셋을 사용하여 데이터 파일에서 레코드를 찾습니다
- 발신자에게 데이터를 반환
질문 설정 ...
자, 여기 질문이 모이는 곳이 있습니다 ...
2 단계는 이러한 쿼리를 O (로그온) 시간에 실행할 수있게하는 가장 중요한 부분입니다. 정보를 정렬 해야 하지만 , 빠른 정렬 방식으로 목록을 순회 할 수 있어야합니다. 특히, 해당 위치의 인덱스 키 값을 읽을 때 잘 정의 된 오프셋으로 이동할 수 있어야합니다.
블록을 읽은 후 즉시 170 번째 위치로 건너 뛰고 키 값을 읽고 찾고있는 것이 GT 또는 LT인지 그 위치 (및 기타 등등)인지 확인해야합니다.
이와 같이 블록의 데이터를 뛰어 넘을 수있는 유일한 방법은 위의 예와 같이 키 값 크기가 모두 잘 정의 된 경우 (4 바이트, 키 당 8 바이트)입니다.
질문
자, 여기 효율적인 인덱스 디자인이 필요합니다. SQL 데이터베이스의 varchar 열 또는 CouchDB 또는 NoSQL과 같은 문서 데이터베이스의 완전 자유 형식 필드에 대해 색인을 생성하려는 필드가 length 인덱스 구조의 블록 안에있는 키 값을 어떻게 구현합니까?
예를 들어, CouchDB에서 ID에 순차적 카운터를 사용하고 트윗을 인덱싱한다고 가정합니다. 몇 달 후 "1"에서 "100,000,000,000"까지의 값을 갖게됩니다.
데이터베이스에 트윗이 4 개 밖에없는 1 일차에 데이터베이스에 인덱스를 작성한다고 가정하면 CouchDB는 인덱스 블록 내부의 키 값에 다음 구성을 사용하려고 할 수 있습니다.
[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block
어느 시점에서 이것은 깨지고 키 값을 인덱스에 저장하기 위해 가변 바이트 수가 필요합니다.
"tweet_message"와 같은 가변 길이 필드를 인덱싱하기로 결정하면 요점이 훨씬 더 눈에 is니다.
키 자체가 완전히 가변 길이이고 데이터베이스가 인덱스를 생성 및 업데이트 할 때 "최대 키 크기"를 지능적으로 추측 할 수있는 방법이 없기 때문에 이러한 키가 실제로 이러한 데이터베이스의 인덱스 세그먼트를 나타내는 블록 안에 어떻게 저장됩니까? ?
분명히 키의 크기가 가변적이고 키 블록을 읽는다면 실제로 블록에 몇 개의 키가 있는지 모를뿐만 아니라 이진을 수행하기 위해 목록의 중간으로 이동하는 방법을 모릅니다 그들을 검색하십시오.
이것은 내가 모두 넘어지고있는 곳이다.
bool, int, char 등과 같은 고전적인 SQL 데이터베이스의 정적 유형 필드를 사용하면 인덱스가 키 길이를 미리 정의하고 고정시킬 수 있음을 이해합니다 ...하지만이 문서 데이터 저장소 세계에서는 디스크에서이 데이터를 효율적으로 모델링하여 O (로그온) 시간 내에 스캔 할 수있는 방법을 이해하고 여기에 대한 설명을 부탁합니다.
설명이 필요한 경우 알려주십시오!
업데이트 (Greg의 답변)
Greg의 답변에 첨부 된 내 의견을 참조하십시오. 일주일 동안 더 많은 연구를 한 후에 그는 실습이 구현하고 사용하기가 쉽지만 훌륭하게 간단하고 성능이 뛰어나다는 제안에 크게 부딪쳤다 고 생각하지만 중요하지 않은 핵심 가치의 역 직렬화를 피하는 데 큰 성과를 제공합니다.
I 3 별도의 DBMS 구현 (CouchDB를, kivaloo 및 이노) 내로 보았다 모든 자신의 실행 환경 (얼랑 / C) 내부의 값을 검색하기 전에 내부 데이터 구조로 전체 블록을 역 직렬화하여이 문제를 처리하는 그들.
이것이 내가 Greg의 제안에 대해 너무 훌륭하다고 생각하는 것입니다. 2048의 일반 블록 크기는 보통 50 개 이하의 오프셋을 가지므로 매우 작은 수의 블록을 읽어야합니다.
업데이트 (Greg의 제안에 대한 잠재적 단점)
이 대화를 나 자신과 함께 계속하기 위해 다음과 같은 단점을 깨달았습니다.
모든 "블록"이 오프셋 데이터로 향하는 경우 헤더에서 올바르게 시작하지 않은 데이터 또는 블록으로 시작하는 데이터를 읽을 수 있으므로 나중에 길에서 구성에서 블록 크기를 조정할 수 없습니다. 여러 헤더를 포함했습니다.
큰 키 값을 인덱싱하는 경우 (누군가가 char (8192) 또는 blob (8192) 열을 인덱싱하려고 시도하는 경우) 키가 단일 블록에 맞지 않고 두 블록에 나란히 오버플로해야 할 수 있습니다 . 즉, 첫 번째 블록에는 오프셋 헤더가 있고 두 번째 블록은 바로 키 데이터로 시작합니다.
이 모든 것에 대한 해결책은 조정 가능 하지 않은 고정 된 데이터베이스 블록 크기를 가지며 주변에서 헤더 블록 데이터 구조를 개발하는 것입니다. 처음에 "블록 유형"을 포함하는 블록 헤더. 정상 블록 인 경우 블록 헤더 바로 다음에 오프셋 헤더가 있어야합니다. "오버 플로우"유형 인 경우 블록 헤더 바로 다음은 원시 키 데이터입니다.
업데이트 (잠재적 인 멋진 업사이드)
블록이 일련의 바이트로 판독되고 오프셋이 디코딩 된 후; 기술적으로 검색하는 키를 원시 바이트로 인코딩 한 다음 바이트 스트림을 직접 비교할 수 있습니다.
찾고있는 키를 찾으면 포인터를 해독하고 따라갈 수 있습니다.
Greg의 아이디어의 또 다른 멋진 부작용! 여기에서 CPU 시간 최적화의 가능성은 고정 된 블록 크기를 설정하는 것만으로도이 모든 것을 얻을 가치가 있습니다.