Làm thế nào để cơ sở dữ liệu lưu trữ các giá trị khóa chỉ mục (trên đĩa) cho các trường có chiều dài thay đổi?


16

Bối cảnh

Câu hỏi này liên quan đến các chi tiết triển khai cấp thấp của các chỉ mục trong cả hai hệ thống cơ sở dữ liệu SQL và NoQuery. Cấu trúc thực tế của chỉ mục (cây B +, hàm băm, SSTable, v.v.) không liên quan vì câu hỏi liên quan cụ thể đến các khóa được lưu trữ bên trong một nút của bất kỳ triển khai nào.

Lý lịch

Trong SQL (ví dụ như MySQL) và NoSQL (CouchDB, MongoDB, vv) cơ sở dữ liệu, khi bạn xây dựng một chỉ mục trên một cột hoặc lĩnh vực tài liệu JSON dữ liệu, những gì bạn đang thực sự gây ra cơ sở dữ liệu để làm là tạo cơ bản một danh sách được sắp xếp tất cả những giá trị đó cùng với một tệp được bù vào tệp dữ liệu chính trong đó bản ghi liên quan đến giá trị đó tồn tại.

(Để đơn giản, tôi có thể vẫy tay với các chi tiết bí truyền khác của các hàm ý cụ thể)

Ví dụ SQL cổ điển đơn giản

Hãy xem xét một bảng SQL tiêu chuẩn có khóa chính int 32 bit đơn giản mà chúng ta tạo ra một chỉ mục, chúng ta sẽ kết thúc với một chỉ mục trên đĩa của các khóa số nguyên được sắp xếp và liên kết với phần bù 64 bit vào tệp dữ liệu kỷ lục cuộc sống, ví dụ:

id   | offset
--------------
1    | 1375
2    | 1413
3    | 1786

Biểu diễn trên đĩa của các khóa trong chỉ mục trông giống như thế này:

[4-bytes][8-bytes] --> 12 bytes for each indexed value

Bám sát các quy tắc chuẩn về tối ưu hóa I / O của đĩa với các hệ thống tệp và hệ thống cơ sở dữ liệu, giả sử bạn lưu trữ các khóa trong các khối 4KB trên đĩa, có nghĩa là:

4096 bytes / 12 bytes per key = 341 keys per block

Bỏ qua cấu trúc tổng thể của chỉ mục (cây B +, hàm băm, danh sách được sắp xếp, v.v.) chúng ta đọc và ghi các khối 341 phím cùng một lúc vào bộ nhớ và quay trở lại đĩa khi cần.

Ví dụ truy vấn

Sử dụng thông tin từ phần trước, giả sử một truy vấn xuất hiện cho "id = 2", việc tra cứu chỉ số DB cổ điển diễn ra như sau:

  1. Đọc thư mục gốc của chỉ mục (trong trường hợp này là 1 khối)
  2. Nhị phân tìm kiếm khối đã sắp xếp để tìm khóa
  3. Lấy tập tin dữ liệu bù từ giá trị
  4. Tra cứu bản ghi trong tệp dữ liệu bằng cách sử dụng offset
  5. Trả lại dữ liệu cho người gọi

Cài đặt câu hỏi ...

Ok, đây là nơi câu hỏi đến với nhau ...

Bước # 2 là phần quan trọng nhất cho phép các truy vấn này thực hiện trong thời gian O (logn) ... thông tin phải được sắp xếp, NHƯNG bạn phải có khả năng duyệt qua danh sách theo cách sắp xếp nhanh chóng ... hơn nữa cụ thể, bạn phải có khả năng chuyển sang các độ lệch được xác định rõ theo ý muốn để đọc giá trị khóa chỉ mục ở vị trí đó.

Sau khi đọc trong khối, bạn phải có thể nhảy đến vị trí thứ 170 ngay lập tức, đọc giá trị khóa và xem liệu thứ bạn đang tìm kiếm là GT hay LT ở vị trí đó (vân vân và vân vân ...)

Cách duy nhất bạn có thể nhảy xung quanh dữ liệu trong khối như vậy là nếu các kích thước giá trị khóa đều được xác định rõ, như ví dụ của chúng tôi ở trên (4 byte rồi 8 byte mỗi khóa).

CÂU HỎI

Ok, đây là nơi tôi đang bị mắc kẹt với thiết kế chỉ mục hiệu quả ... cho các cột varchar trong cơ sở dữ liệu SQL hoặc cụ thể hơn, các trường dạng hoàn toàn tự do trong cơ sở dữ liệu tài liệu như CouchDB hoặc NoQuery, trong đó bất kỳ trường nào bạn muốn lập chỉ mục đều có thể chiều dài làm thế nào để bạn thực hiện các giá trị chính bên trong các khối của cấu trúc chỉ mục mà bạn xây dựng các chỉ số của mình?

Ví dụ: giả sử bạn sử dụng bộ đếm tuần tự cho ID trong CouchDB và bạn đang lập chỉ mục các tweet ... bạn sẽ có các giá trị từ "1" đến "100.000.000" sau vài tháng.

Giả sử bạn xây dựng chỉ mục trên cơ sở dữ liệu vào ngày 1, khi chỉ có 4 tweet trong cơ sở dữ liệu, CouchDB có thể bị cám dỗ sử dụng cấu trúc sau cho các giá trị chính bên trong các khối chỉ mục:

[1-byte][8-bytes] <-- 9 bytes
4096 / 9 = 455 keys per block

Tại một số điểm này phá vỡ và bạn cần một số byte khác nhau để lưu trữ giá trị khóa của bạn trong các chỉ mục.

Điểm thậm chí còn rõ ràng hơn nếu bạn quyết định lập chỉ mục một trường có độ dài thực sự thay đổi như "tweet_message" hoặc một cái gì đó.

Với bản thân khóa có độ dài thay đổi hoàn toàn và cơ sở dữ liệu không có cách nào để đoán một cách thông minh ở một số "kích thước khóa tối đa" khi chỉ mục được tạo và cập nhật, các khóa này thực sự được lưu trữ bên trong các khối biểu thị các phân đoạn của các chỉ mục trong các cơ sở dữ liệu này như thế nào ?

Rõ ràng nếu các khóa của bạn có kích thước thay đổi và bạn đọc trong một khối các khóa, bạn không chỉ không biết có bao nhiêu khóa thực sự trong khối, mà bạn không biết làm thế nào để nhảy vào giữa danh sách để thực hiện nhị phân tìm kiếm trên chúng

Đây là nơi tôi đang nhận được tất cả tăng gấp ba.

Với các trường được nhập tĩnh trong cơ sở dữ liệu SQL cổ điển (như bool, int, char, v.v.) Tôi hiểu chỉ mục có thể chỉ định trước độ dài khóa và bám vào nó ... nhưng trong thế giới lưu trữ dữ liệu tài liệu này, tôi bối rối làm thế nào họ mô hình hóa dữ liệu này trên đĩa một cách hiệu quả để nó vẫn có thể được quét trong thời gian O (logn) và sẽ đánh giá cao bất kỳ sự làm rõ nào ở đây.

Xin vui lòng cho tôi biết nếu cần làm rõ!

Cập nhật (Câu trả lời của Greg)

Xin vui lòng xem ý kiến ​​của tôi kèm theo câu trả lời của Greg. Sau một tuần nghiên cứu, tôi nghĩ rằng anh ấy đã thực sự vấp phải một gợi ý đơn giản và hiệu quả tuyệt vời rằng trong thực tế là rất dễ thực hiện và sử dụng trong khi mang lại hiệu quả lớn trong việc tránh việc loại bỏ các giá trị chính mà bạn không quan tâm.

Tôi đã xem xét 3 triển khai DBMS riêng biệt (CouchDB, kivaloo và InnoDB) và tất cả chúng đều xử lý vấn đề này bằng cách khử lưu lượng toàn bộ khối vào cấu trúc dữ liệu bên trong trước khi tìm kiếm các giá trị bên trong môi trường thực thi của chúng (erlang / C).

Đây là những gì tôi nghĩ là rất xuất sắc về đề nghị của Greg; kích thước khối bình thường là 2048 thường sẽ có 50 điểm bù hoặc ít hơn, dẫn đến một khối số rất nhỏ cần phải đọc.

Cập nhật (nhược điểm tiềm năng đối với đề xuất của Greg)

Để tiếp tục tốt nhất với hộp thoại này với chính mình, tôi đã nhận ra những nhược điểm sau đây ...

  1. Nếu mọi "khối" đều hướng đến dữ liệu bù, bạn không thể cho phép điều chỉnh kích thước khối trong cấu hình sau này vì bạn có thể sẽ đọc dữ liệu không bắt đầu bằng tiêu đề chính xác hoặc khối mà chứa nhiều tiêu đề.

  2. Nếu bạn đang lập chỉ mục các giá trị khóa lớn (giả sử ai đó đang cố gắng lập chỉ mục một cột char (8192) hoặc blob (8192)), có thể các khóa không khớp trong một khối và cần được tràn qua hai khối cạnh nhau . Điều này có nghĩa là khối đầu tiên của bạn sẽ có một tiêu đề bù và khối thứ hai sẽ ngay lập tức bắt đầu với dữ liệu chính.

Giải pháp cho tất cả điều này là có kích thước khối cơ sở dữ liệu cố định không thể điều chỉnh và phát triển cấu trúc dữ liệu khối tiêu đề xung quanh nó ... ví dụ: bạn sửa tất cả các kích thước khối thành 4KB (thường là tối ưu nhất) và viết rất nhỏ tiêu đề khối bao gồm "loại khối" ở đầu. Nếu đó là một khối bình thường, thì ngay sau tiêu đề khối sẽ là tiêu đề bù đắp. Nếu đó là loại "tràn", thì ngay sau tiêu đề khối là dữ liệu khóa thô.

Cập nhật (tiềm năng tuyệt vời)

Sau khi khối được đọc thành một chuỗi byte và phần bù được giải mã; về mặt kỹ thuật, bạn có thể chỉ cần mã hóa khóa mà bạn đang tìm kiếm thành byte thô và sau đó thực hiện so sánh trực tiếp trên luồng byte.

Khi tìm thấy khóa bạn đang tìm kiếm, con trỏ có thể được giải mã và theo dõi.

Một tác dụng phụ tuyệt vời khác của ý tưởng của Greg! Tiềm năng tối ưu hóa thời gian của CPU ở đây đủ lớn để thiết lập kích thước khối cố định có thể đáng giá chỉ để đạt được tất cả điều này.


Đối với bất kỳ ai khác quan tâm đến chủ đề này, nhà phát triển chính của Redis đã gặp phải vấn đề chính xác này trong khi cố gắng thực hiện thành phần "lưu trữ đĩa" không còn tồn tại cho Redis. Ban đầu, ông đã chọn kích thước khóa tĩnh "đủ lớn" là 32 byte nhưng nhận ra tiềm năng của các vấn đề và thay vào đó chọn cách lưu trữ hàm băm của các khóa (sha1 hoặc md5) chỉ để có kích thước phù hợp. Điều này giết chết khả năng thực hiện các truy vấn trong phạm vi, nhưng nó cân bằng cây một cách độc đáo FWIW. Chi tiết tại đây redis.hackyhack.net/2011-01-12.html
Riyad Kalla

Một số thông tin tôi tìm thấy. Có vẻ như SQLite có giới hạn về mức độ lớn của các khóa hoặc nó thực sự cắt ngắn giá trị khóa ở một số giới hạn trên và đặt phần còn lại vào một "trang tràn" trên đĩa. Điều này có thể làm cho các truy vấn cho các khóa lớn khủng khiếp khi tăng gấp đôi ngẫu nhiên. Cuộn xuống phần "Trang cây B" tại đây sqlite.org/fileformat2.html
Riyad Kalla

Câu trả lời:


7

Bạn có thể lưu trữ chỉ mục của mình dưới dạng danh sách các offset có kích thước cố định vào khối chứa dữ liệu chính của bạn. Ví dụ:

+--------------+
| 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 |
+-----------------------+

(tốt, dữ liệu chính sẽ được sắp xếp trong một ví dụ thực tế, nhưng bạn có ý tưởng).

Lưu ý rằng điều này không nhất thiết phản ánh cách các khối chỉ mục thực sự được xây dựng trong bất kỳ cơ sở dữ liệu nào. Đây chỉ là một ví dụ về cách bạn có thể tổ chức một khối dữ liệu chỉ mục trong đó dữ liệu chính có độ dài thay đổi.


Greg, tôi chưa chọn câu trả lời của bạn là câu trả lời defacto vì tôi hy vọng sẽ có thêm một số phản hồi cũng như nghiên cứu thêm về các DBMS khác (tôi đang thêm nhận xét của mình vào Q gốc). Cho đến nay, cách tiếp cận phổ biến nhất dường như là giới hạn trên và sau đó phần còn lại của khóa trong bảng tràn chỉ được kiểm tra khi cần đầy đủ khóa. Không phải là thanh lịch. Giải pháp của bạn có một chút tao nhã mà tôi thích, nhưng trong trường hợp cạnh có các phím thổi kích thước trang của chúng tôi, cách của bạn vẫn sẽ cần một bảng tràn hoặc chỉ không cho phép.
Riyad Kalla

Tôi đã hết dung lượng ... Tóm lại, nếu người thiết kế db có thể sống với một số giới hạn cứng về kích thước khóa, tôi nghĩ cách tiếp cận của bạn là hiệu quả và linh hoạt nhất. Kết hợp tốt đẹp của không gian và hiệu quả cpu. Các bảng tràn linh hoạt hơn, nhưng có thể là khủng khiếp khi thêm i / o ngẫu nhiên để tra cứu các khóa liên tục tràn. Cảm ơn cho đầu vào về điều này!
Riyad Kalla

Greg, tôi đã suy nghĩ về điều này ngày càng nhiều, nhìn vào các giải pháp thay thế và tôi nghĩ rằng bạn đóng đinh nó với ý tưởng tiêu đề bù đắp. Nếu bạn giữ các khối nhỏ của mình, bạn có thể thoát khỏi các offset 8 bit (1 byte), với các khối lớn hơn 16 bit sẽ an toàn nhất thậm chí lên tới các khối 128KB hoặc 256KB là hợp lý (sẽ giả sử các khóa 4 hoặc 8byte). Chiến thắng lớn là bạn có thể đọc được giá rẻ và nhanh như thế nào trong dữ liệu bù và kết quả là bạn đã tiết kiệm được bao nhiêu. Đề nghị tuyệt vời, cảm ơn bạn một lần nữa.
Riyad Kalla

Đây cũng là cách tiếp cận được sử dụng trong UpscaleDB: upscaledb.com/about.html#varlength
Mathieu Rodic 17/2/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.