PostgreSQL에서 삽입 성능을 높이는 방법


216

Postgres 삽입 성능을 테스트하고 있습니다. 데이터 유형으로 숫자가 하나의 열이있는 테이블이 있습니다. 색인도 있습니다. 이 쿼리를 사용하여 데이터베이스를 채웠습니다.

insert into aNumber (id) values (564),(43536),(34560) ...

위의 쿼리로 한 번에 4 백만 개의 행을 한 번에 매우 빠르게 삽입했습니다. 데이터베이스가 6 백만 행에 도달 한 후 15 분마다 성능이 크게 1 백만 행으로 감소했습니다. 삽입 성능을 향상시키는 트릭이 있습니까? 이 프로젝트에서 최적의 삽입 성능이 필요합니다.

RAM이 5GB 인 컴퓨터에서 Windows 7 Pro 사용


5
질문에 Pg 버전을 언급 할 가치가 있습니다. 이 경우에는 많은 차이가 없지만 많은 질문이 있습니다.
Craig Ringer

1
테이블에서 인덱스를 삭제하고있는 경우 트리거하고 삽입 스크립트를 실행하십시오. 벌크로드를 완료하면 인덱스를 다시 작성할 수 있습니다.
Sandeep 2011

답변:


481

PostgreSQL 매뉴얼, 주제에 대한 depesz의 탁월한 기사이 SO 질문 에서 데이터베이스 채우기를 참조하십시오 .

(주이 대답은 기존의 DB로하거나 새로 작성하는 대량의 데이터로드에 관한입니다. 당신이있는 거 관심 DB 성능 복원하는 경우 pg_restore또는 psql실행 pg_dump출력을,이 정도의 이후 적용되지 않습니다 pg_dumppg_restore이미 생성 등의 작업을 수행 할 스키마 + 데이터 복원이 완료된 후 트리거 및 색인) .

해야 할 일이 많이 있습니다. 이상적인 솔루션은 UNLOGGED인덱스가없는 테이블 로 가져온 다음 로그로 변경하고 인덱스를 추가하는 것입니다. 불행히도 PostgreSQL 9.4에서는 테이블 UNLOGGED을 로그로 변경하는 기능이 지원되지 않습니다 . ALTER TABLE ... SET LOGGED이 작업을 수행 할 수 있도록 9.5가 추가 되었습니다.

대량 가져 오기를 위해 데이터베이스를 오프라인으로 만들 수있는 경우을 사용하십시오 pg_bulkload.

그렇지 않으면:

  • 테이블에서 트리거를 비활성화하십시오.

  • 가져 오기를 시작하기 전에 색인을 삭제하고 나중에 다시 작성하십시오. ( 동일한 데이터를 점진적으로 추가하는 것보다 한 번에 인덱스를 작성하는 데 훨씬 적은 시간 이 걸리며 결과 인덱스는 훨씬 더 컴팩트합니다.)

  • 단일 트랜잭션 내에서 가져 오기를 수행하는 경우 커밋하기 전에 외래 키 제약 조건을 삭제하고 가져 오기를 수행 한 다음 제약 조건을 다시 만드는 것이 안전합니다. 잘못된 데이터가 발생할 수 있으므로 가져 오기가 여러 트랜잭션으로 분할 된 경우이 작업을 수행하지 마십시오.

  • 가능하면 s COPY대신 사용하십시오INSERT

  • 사용할 수 없다면 실용적인 경우 COPY다중 값 사용을 고려하십시오 INSERT. 이미하고있는 것 같습니다. 한 번에 너무 많은 값 을 나열하려고 시도하지 마십시오 VALUES. 이러한 값은 몇 번 이상 메모리에 맞아야하므로 명령문 당 수백 개로 유지하십시오.

  • 거래 당 수십만 또는 수백만 건의 삽입을 수행하여 삽입물을 명시 적 트랜잭션에 배치하십시오. 실질적인 한계 AFAIK는 없지만 배치를 사용하면 입력 데이터에서 각 배치의 시작을 표시하여 오류를 복구 할 수 있습니다. 다시, 당신은 이미 이것을하고있는 것 같습니다.

  • 사용 synchronous_commit=off거대한 commit_delayfsync를 줄일 수는 ()의 요금으로 제공됩니다. 그러나 작업을 큰 트랜잭션으로 일괄 처리 한 경우별로 도움이되지 않습니다.

  • INSERT또는 COPY여러 연결에서 병렬로. 하드웨어 디스크 하위 시스템에 따라 달라집니다. 일반적으로 직접 연결된 스토리지를 사용하는 경우 실제 하드 드라이브 당 하나의 연결을 원합니다.

  • 높은 checkpoint_segments값을 설정하고 활성화하십시오 log_checkpoints. PostgreSQL 로그를보고 너무 자주 발생하는 체크 포인트에 대해 불평하지 않는지 확인하십시오.

  • 가져 오는 동안 시스템이 충돌하는 경우 전체 PostgreSQL 클러스터 (데이터베이스 및 동일한 클러스터에있는 다른 클러스터)를 치명적인 손상으로 잃어 버릴 염려가 없다면 Pg를 중지하고 설정 fsync=off하고 Pg를 시작 하고 가져 오기를 수행 할 수 있습니다 . 그런 다음 (직접적으로) Pg를 중지하고 fsync=on다시 설정 하십시오. WAL 구성을 참조하십시오 . PostgreSQL 설치시 데이터베이스에 관심있는 데이터가 이미있는 경우에는이 작업을 수행하지 마십시오. 설정 fsync=off하면 full_page_writes=off; 도 설정할 수 있습니다 . 데이터베이스 손상 및 데이터 손실을 방지하기 위해 가져온 후에 다시 켜십시오. Pg 매뉴얼의 내구성이없는 설정 을 참조하십시오 .

또한 시스템 조정을 살펴 봐야합니다.

  • 스토리지에는 가능한 한 양질의 SSD를 사용하십시오 . 안정적인 전원 보호 후기 입 캐시를 갖춘 우수한 SSD는 커밋 속도를 매우 빠르게 만듭니다. 디스크 플러시 / 수를 줄이는 위의 조언을 따르면 덜 유익 fsync()하지만 여전히 큰 도움이 될 수 있습니다. 데이터 유지에 신경 쓰지 않는 한 적절한 전원 장애 보호없이 저렴한 SSD를 사용하지 마십시오.

  • 직접 연결된 스토리지에 RAID 5 또는 RAID 6을 사용하는 경우 지금 중지하십시오. 데이터를 백업하고 RAID 배열을 RAID 10으로 재구성 한 후 다시 시도하십시오. RAID 5/6은 대량 쓰기 성능에 대한 희망이 없습니다. 큰 캐시를 가진 좋은 RAID 컨트롤러가 도움이 될 수 있습니다.

  • 큰 배터리 지원 후기 저장 캐시와 함께 하드웨어 RAID 컨트롤러를 사용할 수있는 옵션이 있으면 커밋이 많은 워크로드의 쓰기 성능을 실제로 향상시킬 수 있습니다. commit_delay와 함께 비동기 커밋을 사용하거나 대량로드 중에 더 적은 수의 트랜잭션을 수행하는 경우별로 도움이되지 않습니다.

  • 가능하면 WAL ( pg_xlog)을 별도의 디스크 / 디스크 어레이에 저장하십시오. 동일한 디스크에서 별도의 파일 시스템을 사용하는 것은 별 의미가 없습니다. 사람들은 종종 WAL에 RAID1 쌍을 사용하도록 선택합니다. 이는 커밋 속도가 높은 시스템에 더 큰 영향을 미치며, 로그되지 않은 테이블을 데이터로드 대상으로 사용하는 경우에는 거의 영향을 미치지 않습니다.

빠른 테스트를 위해 PostgreSQL 최적화에 관심이있을 수도 있습니다 .


1
양질의 SSD를 사용하면 RAID 5/6의 쓰기 패널티가 다소 완화된다는 데 동의하십니까? 분명히 페널티가 있지만 HDD와 비교할 때 그 차이는 훨씬 덜 고통 스럽다고 생각합니다.

1
나는 그것을 테스트하지 않았습니다. 불쾌한 쓰기 증폭 효과와 (작은 쓰기의 경우) 읽기-수정-쓰기주기에 대한 필요성은 여전히 ​​존재하지만 과도한 탐색에 대한 심각한 처벌은 문제가되지 않아야합니다.
크레이그 벨소리

예를 들어 indisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html )을 false 로 설정하여 인덱스를 삭제하지 않고 비활성화 한 다음 데이터를로드 한 다음 인덱스를 온라인으로 가져올 수 REINDEX있습니까?
Vladislav Rastrusny

1
@CraigRinger 저는 Perc H730에서 SSD를 사용하여 RAID-5와 RAID-10을 테스트했습니다. RAID-5는 실제로 더 빠릅니다. 또한 큰 bytea와 함께 삽입 / 트랜잭션이 복사보다 빠르다는 점에 주목할 가치가 있습니다. 그래도 전반적으로 좋은 조언.
atlaste

2
누구든지 UNLOGGED?로 주요 속도 향상을보고 있습니다 . 빠른 테스트는 10-20 % 개선과 같은 것을 보여줍니다.
serg

15

COPY table TO ... WITH BINARY문서에 따른 사용 은 " 텍스트 및 CSV 형식보다 다소 빠릅니다 ." 삽입 할 행이 수백만 개이고 이진 데이터에 익숙한 경우에만이 작업을 수행하십시오.

다음은 바이너리 입력과 함께 psycopg2를 사용하는 Python예제 레시피입니다 .


1
이진 모드는 타임 스탬프와 같은 일부 입력에서 구문 분석이 중요하지 않은 시간을 크게 절약 할 수 있습니다. 많은 데이터 유형의 경우 대역폭 증가로 인해 많은 이점을 제공하지 않거나 약간 느려질 수도 있습니다 (예 : 작은 정수). 좋은 지적입니다.
Craig Ringer

11

우수한 Craig Ringer의 게시물 및 depesz의 블로그 게시물 외에도 트랜잭션 내부에서 준비된 명령문 삽입을 사용하여 ODBC ( psqlodbc ) 인터페이스를 통해 삽입 속도를 높이려면 추가로 수행해야 할 몇 가지 사항이 있습니다. 빨리 일하십시오 :

  1. Protocol=-1연결 문자열 에 지정하여 롤백시 오류 수준을 "트랜잭션"으로 설정 하십시오. 기본적으로 psqlodbc는 "문"레벨을 사용하여 전체 트랜잭션이 아닌 각 명령문에 대해 SAVEPOINT를 작성하여 삽입 속도를 느리게합니다.
  2. UseServerSidePrepare=1연결 문자열 에 지정하여 서버 측 준비 명령문을 사용하십시오 . 이 옵션이 없으면 클라이언트는 삽입되는 각 행과 함께 전체 insert 문을 보냅니다.
  3. 다음을 사용하여 각 명령문에서 자동 커미트 사용 안함 SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. 모든 행이 삽입되면를 사용하여 트랜잭션을 커밋하십시오 SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);. 트랜잭션을 명시 적으로 열 필요는 없습니다.

불행히도 psqlodbc SQLBulkOperations는 준비되지 않은 일련의 insert 문을 실행하여 "구현" 하므로 가장 빠른 삽입을 위해서는 위의 단계를 수동으로 코딩해야합니다.


A8=30000000연결 스트링에서 큰 소켓 버퍼 크기 도 인서트 속도를 높이는 데 사용되어야합니다.
Andrus

10

나는 오늘 같은 문제에 약 6 시간을 보냈습니다. 인서트는 5MI (총 30MI 중) 행까지 '일반'속도 (100K 당 3 초 미만)로 진행 한 다음 성능이 크게 저하됩니다 (100K 당 1 분까지 줄어 듭니다).

나는 작동하지 않은 모든 것들을 나열하지 않고 고기를 바로 자르지 않을 것입니다.

나는 기본 키 떨어 행복하게 100K 당 3 초 이하의 일정한 속도로 목적지로 유입 (GUID를했다) 대상 테이블에 내 30MI 또는 행을.


7

UUID로 열을 삽입하고 ( 정확하게 는 아닙니다 ) @Dennis 답변 에 추가하려면 (아직 의견을 말할 수 없습니다) gen_random_uuid () (PG 9.4 및 pgcrypto 모듈 필요)를 사용하는 것보다 )) uuid_generate_v4 ()보다 빠름

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)

vs


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

또한 제안 된 공식 방법입니다.

노트

임의로 생성 된 (버전 4) UUID 만 필요한 경우 pgcrypto 모듈에서 gen_random_uuid () 함수를 대신 사용하십시오.

이로 인해 삽입 시간이 2 시간에서 10 분으로 줄어 3.7M 줄에 줄었습니다.


1

최적의 삽입 성능을 얻으려면 옵션 인 경우 색인을 비활성화하십시오. 그 외에는 더 나은 하드웨어 (디스크, 메모리)도 도움이됩니다


-1

이 삽입 성능 문제도 발생했습니다. 내 솔루션은 삽입 작업을 완료하기 위해 몇 가지 이동 루틴을 생성합니다. 그 동안 SetMaxOpenConns적절한 숫자를 지정해야합니다. 그렇지 않으면 너무 많은 열린 연결 오류가 경고됩니다.

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

내 프로젝트의 로딩 속도가 훨씬 빠릅니다. 이 코드는 방금 작동 방식에 대한 아이디어를 제공했습니다. 독자는 쉽게 수정할 수 있어야합니다.


글쎄, 당신은 말할 수 있습니다. 그러나 그것은 내 경우에 수백만 행의 실행 시간을 몇 시간에서 몇 분으로 줄입니다. :)
Patrick
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.