데이터베이스는 가변 길이 필드에 대한 인덱스 키 값 (디스크 상)을 어떻게 저장합니까?


16

문맥

이 질문은 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. 인덱스의 루트를 읽습니다 (이 경우 1 블록).
  2. 키를 찾기 위해 정렬 된 블록을 이진 검색
  3. 값에서 데이터 파일 오프셋을 가져옵니다.
  4. 오프셋을 사용하여 데이터 파일에서 레코드를 찾습니다
  5. 발신자에게 데이터를 반환

질문 설정 ...

자, 여기 질문이 모이는 곳이 있습니다 ...

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의 제안에 대한 잠재적 단점)

이 대화를 나 자신과 함께 계속하기 위해 다음과 같은 단점을 깨달았습니다.

  1. 모든 "블록"이 오프셋 데이터로 향하는 경우 헤더에서 올바르게 시작하지 않은 데이터 또는 블록으로 시작하는 데이터를 읽을 수 있으므로 나중에 길에서 구성에서 블록 크기를 조정할 수 없습니다. 여러 헤더를 포함했습니다.

  2. 큰 키 값을 인덱싱하는 경우 (누군가가 char (8192) 또는 blob (8192) 열을 인덱싱하려고 시도하는 경우) 키가 단일 블록에 맞지 않고 두 블록에 나란히 오버플로해야 할 수 있습니다 . 즉, 첫 번째 블록에는 오프셋 헤더가 있고 두 번째 블록은 바로 키 데이터로 시작합니다.

이 모든 것에 대한 해결책은 조정 가능 하지 않은 고정 된 데이터베이스 블록 크기를 가지며 주변에서 헤더 블록 데이터 구조를 개발하는 것입니다. 처음에 "블록 유형"을 포함하는 블록 헤더. 정상 블록 인 경우 블록 헤더 바로 다음에 오프셋 헤더가 있어야합니다. "오버 플로우"유형 인 경우 블록 헤더 바로 다음은 원시 키 데이터입니다.

업데이트 (잠재적 인 멋진 업사이드)

블록이 일련의 바이트로 판독되고 오프셋이 디코딩 된 후; 기술적으로 검색하는 키를 원시 바이트로 인코딩 한 다음 바이트 스트림을 직접 비교할 수 있습니다.

찾고있는 키를 찾으면 포인터를 해독하고 따라갈 수 있습니다.

Greg의 아이디어의 또 다른 멋진 부작용! 여기에서 CPU 시간 최적화의 가능성은 고정 된 블록 크기를 설정하는 것만으로도이 모든 것을 얻을 가치가 있습니다.


이 주제에 관심이있는 다른 사람들을 위해 Redis의 수석 개발자는 Redis를 위해 없어진 "디스크 저장소"구성 요소를 구현하려고 시도하면서이 정확한 문제를 겪고있었습니다. 그는 원래 32 바이트의 "충분히 큰"정적 키 크기를 선택했지만 문제의 가능성을 깨닫고 키의 해시 (sha1 또는 md5)를 일관된 크기로 저장하기로 결정했습니다. 이것은 원거리 쿼리를 수행하는 능력을 없애지 만 FWIW와 균형을 이룹니다. 자세한 내용은 여기 redis.hackyhack.net/2011-01-12.html
Riyad Kalla

내가 찾은 정보가 더 있습니다. SQLite는 키가 얼마나 큰지에 대한 제한이 있거나 실제로 상한에서 키 값을 자르고 나머지는 디스크의 "오버플로 페이지"에 넣는 것처럼 보입니다. 이는 임의의 I / O가 두 배로 증가함에 따라 거대한 키에 대한 쿼리를 끔찍하게 만들 수 있습니다. "B- 트리 페이지"섹션으로 스크롤하십시오. sqlite.org/fileformat2.html
Riyad Kalla

답변:


7

인덱스를 고정 크기 오프셋 목록으로 키 데이터가 포함 된 블록에 저장할 수 있습니다. 예를 들면 다음과 같습니다.

+--------------+
| 3            | number of entries
+--------------+
| 16           | offset of first key data
+--------------+
| 24           | offset of second key data
+--------------+
| 39           | offset of third key data
+--------------+
| key one |
+----------------+
| key number two |
+-----------------------+
| this is the third key |
+-----------------------+

(핵심 데이터는 실제 예에서 정렬되지만 아이디어를 얻습니다).

이것은 데이터베이스에서 인덱스 블록이 실제로 어떻게 구성 되는지를 반영하지는 않습니다 . 이것은 단지 당신이 방법의 예입니다 수있는 키 데이터 변수의 길이가 인덱스 데이터 블록을 구성 할 수 있습니다.


Greg, 나는 당신의 대답을 사실상의 대답으로 선택하지 않았습니다. 왜냐하면 다른 DBMS에 대한 더 많은 연구를 할뿐만 아니라 더 많은 피드백을 원하기 때문입니다 (원래 Q에 의견을 추가하고 있습니다). 지금까지 가장 일반적인 접근 방식은 상한값 인 것으로 보이며 오버플로 테이블의 나머지 키는 전체 키가 필요할 때만 확인됩니다. 그렇게 우아하지 않습니다. 귀하의 솔루션에는 내가 좋아하는 우아함이 있지만 키가 페이지 크기를 날려 버리는 가장자리의 경우 여전히 오버플로 테이블이 필요하거나 허용하지 않습니다.
Riyad Kalla

공간이 부족합니다 ... 즉, DB 디자이너가 키 크기에 대해 약간의 제한을 가지고 살 수 있다면 귀하의 접근 방식이 가장 효율적이고 유연하다고 생각합니다. 공간과 CPU 효율의 좋은 조합. 오버 플로우 테이블은보다 융통성이 있지만, 끊임없이 오버 플로우되는 키에 대한 조회에 임의의 I / O를 추가하는 것이 굉장 할 수 있습니다. 입력 해 주셔서 감사합니다!
Riyad Kalla

Greg, 나는 이것에 대해 점점 더 생각하고 대안 솔루션을 찾고 있으며 오프셋 헤더 아이디어로 그것을 생각했다고 생각합니다. 블록을 작게 유지하면 8 비트 (1 바이트) 오프셋으로 벗어날 수 있습니다 .16 비트가 클수록 16 비트는 최대 128KB 또는 256KB 블록까지 합리적이어야합니다 (4 또는 8 바이트 키로 가정). 가장 큰 승리는 오프셋 데이터를 얼마나 저렴하고 빠르게 읽을 수 있으며 결과적으로 얼마나 많은 역 직렬화를 절약 할 수 있는가입니다. 훌륭한 제안입니다. 다시 감사합니다.
Riyad Kalla

이 또한 UpscaleDB에 사용되는 방법입니다 upscaledb.com/about.html#varlength
마티유 Rodic
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.