2014-08-28 4 views
1

매초마다 많은 삽입이있는 거대한 테이블 (현재 3mil 행, 계수가 1000만큼 증가 할 것으로 예상 됨)이 있습니다. 테이블은 절대로 업데이트되지 않습니다.Postgres : (지연된) 읽기 및 쓰기 액세스가있는 거대한 테이블

이제 예상대로 꽤 느린 테이블에서 쿼리를 실행해야합니다. 이러한 쿼리는 100 % 정확할 필요는 없지만 결과가 하루 이전이지만 오래된 것은 아닙니다.

현재 두 개의 단일 정수 열에 두 개의 인덱스가 있고 쿼리 속도를 높이기 위해 두 개의 인덱스 (정수 및 타임 스탬프 열)를 추가해야합니다. 내가 지금까지 한

아이디어 :

  1. 전혀 거대한 테이블에 테이블에
  2. 없음 인덱스를 두 누락 된 인덱스를 추가 및 두 번째 테이블에 (매일 작업으로) 내용을 복사 (단지 중요한 행) 다음 두 번째 테이블에 인덱스를 만들고 해당 테이블에 대한 쿼리를 실행?
  3. 거대한 테이블을 파티션하기
  4. 마스터/슬레이브 설정 (마스터에 쓰고 슬레이브에서 읽음).

성능면에서 어떤 옵션이 가장 좋습니까? 다른 제안이 있으십니까?

EDIT : 여기

는 테이블 (I는 외부 키를 표시하고 쿼리 비트 prettified있다)

CREATE TABLE client_log 
(
    id     serial   NOT NULL, 
    logid    integer   NOT NULL, 
    client_id   integer   NOT NULL, (FOREIGN KEY) 
    client_version  varchar(16), 
    sessionid   varchar(100) NOT NULL, 
    created   timestamptz  NOT NULL, 
    filename   varchar(256), 
    funcname   varchar(256), 
    linenum   integer, 
    comment   text, 
    domain    varchar(128), 
    code    integer, 
    latitude   float8, 
    longitude   float8, 
    created_on_server timestamptz  NOT NULL, 
    message_id   integer,     (FOREIGN KEY) 
    app_id    integer   NOT NULL, (FOREIGN KEY) 
    result    integer 
); 

CREATE INDEX client_log_code_idx ON client_log USING btree (code); 
CREATE INDEX client_log_created_idx ON client_log USING btree (created); 
CREATE INDEX clients_clientlog_app_id ON client_log USING btree (app_id); 
CREATE INDEX clients_clientlog_client_id ON client_log USING btree (client_id); 
CREATE UNIQUE INDEX clients_clientlog_logid_client_id_key ON client_log USING btree (logid, client_id); 
CREATE INDEX clients_clientlog_message_id ON client_log USING btree (message_id); 

및 예 쿼리

SELECT 
    client_log.comment, 
    COUNT(client_log.comment) AS count 
FROM 
    client_log 
WHERE 
    client_log.app_id = 33 AND 
    client_log.code = 3 AND 
    client_log.client_id IN (SELECT client.id FROM client WHERE 
     client.app_id = 33 AND 
     client."replaced_id" IS NULL) 
GROUP BY client_log.comment ORDER BY count DESC; 

client_log_code_idx은 위의 쿼리에 필요한 인덱스입니다. client_log_created_idx 색인이 필요한 다른 검색어가 있습니다.

그리고 쿼리 계획 : 일반적으로

Sort (cost=2844.72..2844.75 rows=11 width=242) (actual time=4684.113..4684.180 rows=70 loops=1) 
    Sort Key: (count(client_log.comment)) 
    Sort Method: quicksort Memory: 32kB 
    -> HashAggregate (cost=2844.42..2844.53 rows=11 width=242) (actual time=4683.830..4683.907 rows=70 loops=1) 
     -> Hash Semi Join (cost=1358.52..2844.32 rows=20 width=242) (actual time=303.515..4681.211 rows=1202 loops=1) 
       Hash Cond: (client_log.client_id = client.id) 
       -> Bitmap Heap Scan on client_log (cost=1108.02..2592.57 rows=387 width=246) (actual time=113.599..4607.568 rows=6962 loops=1) 
        Recheck Cond: ((app_id = 33) AND (code = 3)) 
        -> BitmapAnd (cost=1108.02..1108.02 rows=387 width=0) (actual time=104.955..104.955 rows=0 loops=1) 
          -> Bitmap Index Scan on clients_clientlog_app_id (cost=0.00..469.96 rows=25271 width=0) (actual time=58.315..58.315 rows=40662 loops=1) 
           Index Cond: (app_id = 33) 
          -> Bitmap Index Scan on client_log_code_idx (cost=0.00..637.61 rows=34291 width=0) (actual time=45.093..45.093 rows=36310 loops=1) 
           Index Cond: (code = 3) 
       -> Hash (cost=248.06..248.06 rows=196 width=4) (actual time=61.069..61.069 rows=105 loops=1) 
        Buckets: 1024 Batches: 1 Memory Usage: 4kB 
        -> Bitmap Heap Scan on client (cost=10.95..248.06 rows=196 width=4) (actual time=27.843..60.867 rows=105 loops=1) 
          Recheck Cond: (app_id = 33) 
          Filter: (replaced_id IS NULL) 
          Rows Removed by Filter: 271 
          -> Bitmap Index Scan on clients_client_app_id (cost=0.00..10.90 rows=349 width=0) (actual time=15.144..15.144 rows=380 loops=1) 
           Index Cond: (app_id = 33) 
Total runtime: 4684.843 ms 
+1

추가 정보없이 답을 얻을 수 없습니다. 쿼리는 어떻게 생겼습니까? 쿼리 실행 계획은 무엇입니까 (http://wiki.postgresql.org/wiki/SlowQueryQuestions 참조). 그러나 일반적으로 다른 인덱스를 시도해 보는 것이 좋습니다 (삽입 속도는 불필요한 모든 인덱스를 삭제하십시오). 그런 다음 분할을 시도하십시오. –

+0

'id serial'은 아마 기본 키로 사용됩니다 : try - >>'id (big) serial NOT NULL PRIMARY KEY' – wildplasser

+0

좋은 지적입니다. 나는 그것을 변경했다 :'ALTER TABLE client_log DROP COLUMN id;''ALTER TABLE client_log ADD COLUMN id bigserial NOT NULL PRIMARY KEY;'(_id_는 다른 곳에서는 사용되지 않았다.) – kev

답변

4

, 시간 관련 데이터가 지속적으로 데이터베이스에 삽입되는 시스템에서, 나는 시간에 따라 partitioning을 권 해드립니다.

이것은 쿼리 시간을 향상시킬 수있는 것은 아니지만 그렇지 않으면 데이터 관리를 어렵게하기 때문입니다. 하드웨어가 크지 만 용량에 한계가 있으므로 결국 특정 날짜보다 오래된 행을 제거해야합니다. 행을 제거하는 비율은 들어오는 비율과 동일해야합니다.

큰 테이블이 하나 있고 DELETE를 사용하여 이전 행을 제거하면 많은 죽은 튜플을 남겨 둡니다. 진공 청소기를 사용해야합니다. 귀중한 디스크 IO를 사용하여 자동 진공 장치가 계속 실행됩니다.

한편, 시간별로 파티션을 나누면 관련성이 떨어지는 하위 테이블을 삭제하는 것만 큼 날짜가 지난 데이터를 쉽게 삭제할 수 있습니다.

인덱스의 경우 인덱스가 상속되지 않으므로 파티션을로드 할 때까지 인덱스를 만들 때 절약 할 수 있습니다.사용 사례에서 1 일의 파티션 크기를 가질 수 있습니다. 즉, 데이터를 삽입 할 때 색인을 지속적으로 업데이트 할 필요가 없습니다. 쿼리를 수행하는 데 필요한 추가 인덱스를 갖는 것이 더 실용적입니다.

예제 쿼리는 'created'시간 필드에서 필터링하지 않지만 다른 쿼리는 수행한다고 말합니다. 시간별로 파티션을 나누고 u 리를 구성하는 f}에주의하면 제한 조건 제외가 실행되고 u 리와 관련된 특정 파티션 만 포함됩니다.

1

분할을 제외하고 나는 테이블을 많은 테이블, 일명 Sharding으로 분할하는 것을 고려할 것이다.

나는 당신의 도메인의 전체 그림이없는 있지만, 이들은 몇 가지 제안은 다음과 같습니다

각 클라이언트가 자신의 스키마에 자신의 테이블을 얻을 수 (또는 클라이언트의 세트가 얼마나 많은 클라이언트에 따라 스키마를 공유하면 가지고 있고 얼마나 많은 새로운 클라이언트를 얻을 것으로 예상하는지).

create table client1.log(id, logid,.., code, app_id); 
create table client2.log(id, logid,.., code, app_id); 

이렇게 표를 분할하면 삽입시 경합이 감소해야합니다.

표를 더 분할 할 수 있습니다. 각 클라이언트 스키마 내에서 "코드"또는 "app_id"또는 사용자에게 적합한 다른 테이블 당 테이블을 분할 할 수도 있습니다.. 이것은 지나치게 많을 수 있지만 "코드"및/또는 "app_id"값의 수가 자주 변경되지 않으면 쉽게 구현할 수 있습니다. 새로운 작은 테이블에도 code/app_id 열을 유지하고 다른 유형의 로그 레코드를 삽입 할 수 없도록 열에 제약 조건을 적용하십시오.

create view client_log as 
select * from error_log 
union all 
select * from warning_log 
union all 
select * from message_log; 
:

create schema client1; 
set search_path = 'client1'; 

create table error_log(id serial, code text check(code ='error')); 
create table warning_log(id serial, code text check(code ='warning')); 
create table message_log(id serial, code text check(code ='message')); 

당신이 모든 테이블 위에 뷰를 사용할 수있는 클라이언트의 전체 그림 (모든 행)을 활용하려면 다음 작업도 검색 옵티마이 도움이 될 것입니다 제약이 예 참조

점검 제한 조건은 옵티마이 저가 "코드"가 존재할 수있는 테이블 만 검색하도록 허용해야합니다.

explain 
select * from client_log where code = 'error'; 
-- Output 
Append (cost=0.00..25.38 rows=6 width=36) 
    -> Seq Scan on error_log (cost=0.00..25.38 rows=6 width=36) 
     Filter: (code = 'error'::text)