Database untuk kueri agregat jangkauan efisien?

11

Sebagai contoh yang disederhanakan, misalkan saya memiliki tabel seperti ini:

seq | value
----+------
102 | 11954
211 | 43292
278 | 19222
499 |  3843

Tabel mungkin berisi ratusan juta catatan, dan saya harus sering melakukan kueri seperti ini:

SELECT sum(value) WHERE seq > $a and seq < $b

Bahkan jika seqdiindeks, implementasi basis data yang khas akan berulang melalui setiap baris untuk menghitung jumlah dalam kasus terbaik O(n), di mana nukuran rentang.

Apakah ada database yang dapat melakukan ini secara efisien, seperti dalam O(log(n))per permintaan?

Saya telah menemukan struktur data yang disebut Pohon Segmen seperti yang dijelaskan di sini . Juga kadang-kadang disebut sebagai pohon rentang atau pohon interval, meskipun semua nama ini sering digambarkan sebagai variasi struktur data yang sedikit berbeda.

Namun, saya belum menemukan database yang mengimplementasikan struktur data seperti itu. Menerapkannya dari awal mudah untuk struktur dalam memori, tetapi menjadi rumit jika harus bertahan atau terlalu besar untuk masuk ke dalam memori. Jika ada pola yang efisien untuk mengimplementasikan ini di atas database yang ada, itu juga bisa membantu.

Catatan: Ini bukan tabel tambahan saja, jadi solusi seperti menjaga jumlah kumulatif tidak akan berfungsi dalam kasus ini.

Ralf
sumber
Ini adalah kasus penggunaan khas untuk database yang diorganisasikan kolom, yang jumlahnya banyak .
mustaccio
Bahkan basis data yang diorganisasi kolom masih membutuhkan O (n) waktu untuk memindai n baris. Yang mengatakan, banyak basis data yang diorganisir kolom sangat baik dalam memparalelkan pertanyaan seperti itu, sehingga akan berjalan jauh lebih cepat pada database seperti itu.
Brian

Jawaban:

8

Menggunakan indeks SQL Server ColumnStore

Yah, oke, hanya satu - indeks CS yang terkelompok.

Jika Anda ingin membaca tentang perangkat keras tempat saya melakukan ini, silakan ke sini . Pengungkapan penuh, saya menulis posting blog itu di situs web perusahaan tempat saya bekerja.

Aktif untuk ujian!

Berikut ini beberapa kode umum untuk membangun tabel yang cukup besar. Peringatan yang sama seperti Evan, ini bisa memakan waktu cukup lama untuk membangun dan mengindeks.

USE tempdb

CREATE TABLE t1 (Id INT NOT NULL, Amount INT NOT NULL)

;WITH T (N)
AS ( SELECT X.N
     FROM ( 
      VALUES (NULL), (NULL), (NULL),
             (NULL), (NULL), (NULL),
             (NULL), (NULL), (NULL), 
             (NULL) ) AS X (N) 
           ), NUMS (N) AS ( 
            SELECT TOP ( 710000000 ) 
                    ROW_NUMBER() OVER ( ORDER BY ( SELECT NULL )) AS N
            FROM   T AS T1, T AS T2, T AS T3, 
                   T AS T4, T AS T5, T AS T6, 
                   T AS T7, T AS T8, T AS T9, 
                   T AS T10 )
INSERT dbo.t1 WITH ( TABLOCK ) (
    Id, Amount )
SELECT NUMS.N % 999 AS Id, NUMS.N % 9999 AS Amount
FROM   NUMS;

--(705032704 row(s) affected) --Aw, close enough

Yah, Evan menang untuk kesederhanaan, tetapi saya sudah membicarakan hal itu sebelumnya.

Inilah definisi indeks. La dan dee dan dah.

CREATE CLUSTERED COLUMNSTORE INDEX CX_WOAHMAMA ON dbo.t1

Melihat hitungan, setiap Id memiliki distribusi yang cukup merata:

SELECT t.Id, COUNT(*) AS [Records]
FROM dbo.t1 AS t
GROUP BY t.Id
ORDER BY t.Id

Hasil:

Id  Records
0   5005005
1   5005006
2   5005006
3   5005006
4   5005006
5   5005006

...

994 5005005
995 5005005
996 5005005
997 5005005
998 5005005

Dengan setiap Id yang memiliki ~ 5.005.005 baris, kita dapat melihat rentang ID yang cukup kecil untuk memberi Anda jumlah 10 juta baris.

SELECT COUNT(*) AS [Records], SUM(t.Amount) AS [Total]
FROM   dbo.t1 AS t
WHERE  t.Id > 0
       AND t.Id < 3;

Hasil:

Records     Total
10010012    50015062308

Profil permintaan:

Table 't1'. Scan count 6, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 2560758, lob physical reads 0, lob read-ahead reads 0.
Table 't1'. Segment reads 4773, segment skipped 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 564 ms,  elapsed time = 106 ms.

Untuk bersenang-senang, agregasi yang lebih besar:

SELECT COUNT(*) AS [Records], SUM(CONVERT(BIGINT, t.Amount)) AS [Total]
FROM   dbo.t1 AS t
WHERE  t.Id > 0
       AND t.Id < 101;

Hasil:

Records     Total
500500505   2501989114575

Profil permintaan:

Table 't1'. Scan count 6, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 2560758, lob physical reads 0, lob read-ahead reads 0.
Table 't1'. Segment reads 4773, segment skipped 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 1859 ms,  elapsed time = 321 ms.

Semoga ini membantu!

Erik Darling
sumber
2

PostgreSQL dengan indeks BRIN

Bahkan jika seq diindeks, implementasi basis data yang khas akan berulang melalui setiap baris untuk menghitung jumlah dalam kasus terbaik O (n), di mana n adalah ukuran kisaran.

Itu tidak benar. Setidaknya, tidak ada database yang layak yang akan melakukan itu. PostgreSQL mendukung pembuatan indeks BRIN pada tabel-tabel semacam ini. Indeks BRIN super kecil dan dapat memuat ram bahkan di meja sebesar ini. Ratusan juta baris bukan apa-apa.

Di sini, 300 juta baris didefinisikan seperti yang Anda pesan. Peringatan mungkin membutuhkan waktu lama untuk membuatnya (Waktu: 336057.807 ms + 95121.809 ms untuk indeks).

CREATE TABLE foo
AS
  SELECT seq::int, trunc(random()*100000)::int AS v
  FROM generate_series(1,3e8) AS gs(seq);

CREATE INDEX ON foo USING BRIN (seq);

ANALYZE foo;

Dan sekarang...

EXPLAIN ANALYZE SELECT sum(v) FROM foo WHERE seq BETWEEN 424242 AND 6313376;
                                                                QUERY PLAN                                                                 
-------------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=1486163.53..1486163.54 rows=1 width=4) (actual time=1493.888..1493.888 rows=1 loops=1)
   ->  Bitmap Heap Scan on foo  (cost=58718.12..1471876.19 rows=5714938 width=4) (actual time=12.565..1035.153 rows=5889135 loops=1)
         Recheck Cond: ((seq >= 424242) AND (seq <= 6313376))
         Rows Removed by Index Recheck: 41105
         Heap Blocks: lossy=26240
         ->  Bitmap Index Scan on foo_seq_idx  (cost=0.00..57289.38 rows=5714938 width=0) (actual time=10.378..10.378 rows=262400 loops=1)
               Index Cond: ((seq >= 424242) AND (seq <= 6313376))
 Planning time: 0.125 ms
 Execution time: 1493.948 ms
(9 rows)

1,4 detik untuk mengumpulkan / menjumlahkan 5.889.135 baris dalam kisaran yang diberikan.

Meskipun tabelnya 10 GB, indeks BRIN adalah 304 kB.

Bahkan lebih cepat

Jika ini masih belum cukup cepat, Anda dapat melakukan cache agregat sebanyak 100 ribu baris.

CREATE MATERIALIZED VIEW cache_foo
AS
  SELECT seq/1e5::int AS grp, sum(v)
  FROM foo GROUP BY seq/1e5::int
  ORDER BY 1;

Sekarang Anda hanya perlu menggunakan baris brin dan agregat 2(1e5-1)daripada 300 juta atau apa pun.

Perangkat keras

Lenovo x230, i5-3230M, RAM 16GB, 1tb Samsung 840 SSD.

Evan Carroll
sumber
Terima kasih, saya akan membaca dan bereksperimen lebih banyak dengan indeks BRIN. Ini memang terlihat sebagai pilihan terbaik sejauh ini.
Ralf
3
Saran yang bagus, baik (indeks BRIN dan tampilan terwujud). Tetapi kueri, bahkan dengan indeks BRIN masih O (n). Harap edit dan jangan klaim sebaliknya. Pandangan terwujud mungkin lebih baik daripada O(n), mungkin O(sqrt(n)). Tergantung pada bagaimana Anda akan menentukan interval yang akan digunakan dalam materialisasi.
ypercubeᵀᴹ