효율적인 범위 집계 쿼리를위한 데이터베이스?


11

간단한 예로, 다음과 같은 테이블이 있다고 가정하십시오.

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

테이블에는 수억 개의 레코드가 포함될 수 있으므로 다음과 같은 쿼리를 자주 수행해야합니다.

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

seq인덱스 된 경우에도 일반적인 데이터베이스 구현은 각 행을 반복하여 최상의 경우 합계를 계산합니다 ( O(n)여기서는 n범위의 크기).

O(log(n))쿼리별로 효율적으로 수행 할 수있는 데이터베이스가 있습니까?

여기에 설명 된 것처럼 세그먼트 트리라는 데이터 구조를 보았습니다 . 이러한 이름이 모두 데이터 구조의 약간 다른 변형으로 설명 되기는하지만 때때로 범위 트리 또는 간격 트리라고도합니다.

그러나 나는 그러한 데이터 구조를 구현하는 데이터베이스를 보지 못했습니다. 메모리 내 구조에서는 처음부터 처음부터 구현하는 것이 쉽지만, 메모리에 맞지 않거나 너무 커야하는 경우 까다로워집니다. 기존 데이터베이스 위에이를 구현하기위한 효율적인 패턴이있는 경우에도 도움이 될 수 있습니다.

참고 :이 테이블은 추가 전용 테이블이 아니므로이 경우 누적 합계를 유지하는 등의 솔루션이 작동하지 않습니다.


이것은 거기에있는 열을 조직 데이터베이스의 일반적인 사용 사례입니다 많은 .
mustaccio

열 구성 데이터베이스조차도 n 행을 스캔하는 데 여전히 O (n) 시간이 필요합니다. 즉, 많은 열 구성 데이터베이스는 이러한 쿼리를 병렬 처리하는 데 매우 적합하므로 이러한 데이터베이스에서 훨씬 빠르게 실행됩니다.
Brian

답변:


8

SQL Server ColumnStore 인덱스 사용

글쎄, 하나는 클러스터 된 CS 인덱스입니다.

내가 한 하드웨어에 대해 읽으려면 여기로 가십시오 . 전체 공개, 나는 내가 일하는 회사의 웹 사이트에 블로그 게시물을 썼습니다.

시험에!

다음은 꽤 큰 테이블을 작성하는 일반적인 코드입니다. Evan과 동일한 경고입니다. 빌드 및 색인 작성에 시간이 걸릴 수 있습니다.

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

글쎄, Evan은 단순성을 위해 이겼지 만 전에는 그것에 대해 이야기 했습니다 .

색인 정의는 다음과 같습니다. 라와 디와 다.

CREATE CLUSTERED COLUMNSTORE INDEX CX_WOAHMAMA ON dbo.t1

카운트를 살펴보면 모든 Id에 꽤 고른 분포가 있습니다.

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

결과 :

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

...

994 5005005
995 5005005
996 5005005
997 5005005
998 5005005

~ 5,005,005 개의 행을 가진 모든 Id와 함께, 우리는 1000 만 개의 행 합계를 얻기 위해 아주 작은 범위의 ID를 볼 수 있습니다.

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

결과:

Records     Total
10010012    50015062308

쿼리 프로필 :

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.

재미를 위해 더 큰 집계 :

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

결과 :

Records     Total
500500505   2501989114575

쿼리 프로필 :

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.

도움이 되었기를 바랍니다!



2

BRIN 인덱스 가있는 PostgreSQL

seq가 색인화 되더라도 일반적인 데이터베이스 구현 은 각 행을 반복하여 최상의 경우 O (n)에서 합계를 계산합니다. 여기서 n은 범위의 크기입니다.

그건 사실이 아니야. 적어도 적절한 데이터베이스는 그렇게하지 않습니다. PostgreSQL은 이러한 종류의 테이블에서 BRIN 인덱스 생성을 지원합니다 . BRIN 인덱스 는 매우 작 으며이 큰 테이블에서도 램에 적합합니다. 수백만 행이 아무것도 아닙니다.

여기에 주문한대로 3 억 개의 행이 정의되었습니다. 경고 생성하는 데 시간이 오래 걸릴 수 있습니다 (색인의 경우 336057.807ms + 95121.809ms).

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;

그리고 지금...

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)

지정된 범위에서 5,889,135 개의 행을 집계 / 합산하는 데 1.4 초

테이블은 10GB이지만 BRIN 인덱스는 304kB입니다.

더 빠른

여전히 빠르지 않은 경우 집계를 100k 행씩 캐시 할 수 있습니다.

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

이제는 2(1e5-1)3 억 또는 그 이상이 아닌 브린 및 집계 행만 사용하면 됩니다.

하드웨어

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


감사합니다. BRIN 지수를 자세히 읽고 실험 해 보겠습니다. 이것은 지금까지 가장 좋은 옵션처럼 보입니다.
Ralf

3
좋은 제안, (BRIN 지수와 구체화 된 관점). 그러나 BRIN 인덱스를 사용하더라도 쿼리는 여전히 O (n)입니다. 수정하지 말고 수정 해주세요. 구체화 된 관점이 O(n)아마도 낫다 O(sqrt(n)). 구체화에 사용될 간격을 정의하는 방법에 따라 다릅니다.
ypercubeᵀᴹ
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.