SQL 집계 함수가 Python 및 Java(또는 Poor Man's OLAP)보다 훨씬 느린 이유는 무엇입니까?

StackOverflow https://stackoverflow.com/questions/51553

문제

실제 DBA의 의견이 필요합니다.Postgres 8.3은 내 Macbook Pro에서 이 쿼리를 실행하는 데 200ms가 걸리는 반면 Java와 Python은 20ms(350,000행) 이내에 동일한 계산을 수행합니다.

SELECT count(id), avg(a), avg(b), avg(c), avg(d) FROM tuples;

SQL 데이터베이스를 사용할 때 이것이 정상적인 동작입니까?

스키마(테이블에는 설문 조사에 대한 응답이 포함되어 있음):

CREATE TABLE tuples (id integer primary key, a integer, b integer, c integer, d integer);

\copy tuples from '350,000 responses.csv' delimiter as ','

컨텍스트를 위해 Java와 Python으로 몇 가지 테스트를 작성했는데 SQL이 무너졌습니다(순수 Python 제외).

java   1.5 threads ~ 7 ms    
java   1.5         ~ 10 ms    
python 2.5 numpy   ~ 18 ms  
python 2.5         ~ 370 ms

모든 열이 문자열이라고 가정하더라도 sqlite3도 Postgres와 경쟁적입니다(대조:Postgres에서 정수 대신 숫자 열로 전환하는 것만으로도 10배의 속도 저하가 발생합니다.

내가 시도했지만 성공하지 못한 튜닝에는 다음이 포함됩니다(일부 웹 조언을 맹목적으로 따름).

increased the shared memory available to Postgres to 256MB    
increased the working memory to 2MB
disabled connection and statement logging
used a stored procedure via CREATE FUNCTION ... LANGUAGE SQL

그래서 제 질문은 '여기서 제 경험이 정상인가요? 그리고 이것이 SQL 데이터베이스를 사용할 때 기대할 수 있는 것인가요?'입니다.ACID에는 비용이 따른다는 점은 이해할 수 있지만 제 생각에는 이것은 좀 미친 짓입니다.실시간 게임 속도를 요구하는 것은 아니지만 Java는 20ms 이내에 수백만 개의 더블을 처리할 수 있기 때문에 약간 질투가 납니다.

비용과 서버 복잡성 측면에서 저렴하게 간단한 OLAP을 수행할 수 있는 더 좋은 방법이 있습니까?저는 Mondrian과 Pig + Hadoop을 살펴봤지만 또 다른 서버 애플리케이션을 유지 관리하는 것에 별로 흥미를 느끼지 않았고 그들이 도움이 될지도 확신하지 못했습니다.


아니요, Python 코드와 Java 코드는 말하자면 내부에서 모든 작업을 수행합니다.저는 각각 350,000개의 무작위 값을 가진 4개의 배열을 생성한 다음 평균을 구합니다.타이밍에는 세대를 포함하지 않고 평균화 단계만 포함합니다.Java 스레드 타이밍은 4개의 스레드(어레이당 평균 1개)를 사용하므로 과도하지만 확실히 가장 빠릅니다.

sqlite3 타이밍은 Python 프로그램에 의해 구동되며 디스크에서 실행됩니다(:memory: 아님).

나는 Postgres가 뒤에서 훨씬 더 많은 일을 하고 있다는 것을 알고 있지만, 이 작업은 읽기 전용 데이터이기 때문에 대부분의 작업은 나에게 중요하지 않습니다.

Postgres 쿼리는 후속 실행 시 타이밍을 변경하지 않습니다.

디스크에서 스풀링하는 작업을 포함하도록 Python 테스트를 다시 실행했습니다.타이밍이 거의 4초로 상당히 느려집니다.그러나 나는 Python의 파일 처리 코드가 거의 C에 있다고 추측합니다(csv lib는 아닐 수도 있지만?). 따라서 이는 Postgres가 디스크에서 스트리밍하지 않는다는 것을 나타냅니다(또는 귀하의 말이 맞으므로 저는 고개를 숙여야 합니다). 누가 저장 레이어를 작성하기 전에!)

도움이 되었습니까?

해결책

Postgres는 보이는 것보다 훨씬 더 많은 일을 하고 있습니다(처음에는 데이터 일관성을 유지하는 것입니다!)

값이 100% 정확할 필요는 없거나 테이블이 거의 업데이트되지 않지만 이 계산을 자주 실행하는 경우 구체화된 뷰를 조사하여 속도를 높일 수 있습니다.

(참고로 저는 Postgres에서 구체화된 뷰를 사용하지 않았습니다. 약간 해킹된 것처럼 보이지만 귀하의 상황에 적합할 수 있습니다.)

구체화된 뷰

또한 실제로 서버에 연결하는 오버헤드와 서버에 요청을 보내고 다시 보내는 데 필요한 왕복 시간도 고려하세요.

나는 이와 같은 작업에 200ms가 꽤 좋다고 생각합니다. 내 oracle 서버에서의 빠른 테스트는 약 500,000개의 행이 있고 인덱스가 없는 동일한 테이블 구조이며 약 1 - 1.5초가 소요됩니다. 이는 거의 모두 오라클이 데이터를 빨아들이는 것입니다. 디스크에서.

진짜 질문은 200ms가 충분히 빠른가입니다.

-------------- 더 --------------------

저는 구체화된 뷰를 사용하여 이 문제를 해결하는 데 관심이 있었습니다. 왜냐하면 실제로 사용해 본 적이 없기 때문입니다.이것은 오라클에 있습니다.

먼저 1분마다 새로 고쳐지는 MV를 만들었습니다.

create materialized view mv_so_x 
build immediate 
refresh complete 
START WITH SYSDATE NEXT SYSDATE + 1/24/60
 as select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

새로 고치는 동안 반환된 행이 없습니다.

SQL> select * from mv_so_x;

no rows selected

Elapsed: 00:00:00.00

새로 고치면 원시 쿼리를 수행하는 것보다 훨씬 빠릅니다.

SQL> select count(*),avg(a),avg(b),avg(c),avg(d) from so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:05.74
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

기본 테이블에 삽입하면 결과를 MV에서 즉시 볼 수 없습니다.

SQL> insert into so_x values (1,2,3,4,5);

1 row created.

Elapsed: 00:00:00.00
SQL> commit;

Commit complete.

Elapsed: 00:00:00.00
SQL> select * from mv_so_x;

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899459 7495.38839 22.2905454 5.00276131 2.13432836

Elapsed: 00:00:00.00
SQL> 

하지만 잠시 기다리면 MV가 뒤에서 업데이트되고 결과가 원하는 대로 빠르게 반환됩니다.

SQL> /

  COUNT(*)     AVG(A)     AVG(B)     AVG(C)     AVG(D)
---------- ---------- ---------- ---------- ----------
   1899460 7495.35823 22.2905352 5.00276078 2.17647059

Elapsed: 00:00:00.00
SQL> 

이는 이상적이지 않습니다.처음에는 실시간이 아니기 때문에 삽입/업데이트가 즉시 표시되지 않습니다.또한 필요 여부에 관계없이 MV를 업데이트하기 위해 실행되는 쿼리가 있습니다(이는 시간 프레임에 관계없이 또는 요청에 따라 조정할 수 있음).그러나 이것은 초 단위까지 정확하지 않은 값을 사용할 수 있는 경우 MV가 최종 사용자에게 얼마나 빠르게 표시될 수 있는지를 보여줍니다.

다른 팁

나는 당신의 테스트 계획이 실제로 유용하지 않다고 말하고 싶습니다.db 쿼리를 수행하기 위해 db 서버는 여러 단계를 거칩니다.

  1. SQL을 파싱하다
  2. 쿼리 계획을 세우십시오. i.이자형.사용할 인덱스(있는 경우)를 결정하고 최적화하는 등의 작업을 수행합니다.
  3. 인덱스를 사용하는 경우 실제 데이터에 대한 포인터를 검색한 다음 데이터의 적절한 위치로 이동하거나
  4. 인덱스가 사용되지 않으면 스캔 테이블 전체 어떤 행이 필요한지 결정하기 위해
  5. 디스크의 데이터를 임시 위치(반드시 그런 것은 아니지만 메모리)로 로드합니다.
  6. count() 및 avg() 계산을 수행합니다.

따라서 Python에서 배열을 만들고 평균을 구하면 기본적으로 마지막 단계를 제외하고 모든 단계를 건너뜁니다.디스크 I/O는 프로그램이 수행해야 하는 가장 비용이 많이 드는 작업 중 하나이므로 이는 테스트의 주요 결함입니다. 이 질문 예전에 여기에 물어봤습니다.)다른 테스트에서 디스크의 데이터를 읽는 경우에도 프로세스가 완전히 다르며 결과가 얼마나 관련성이 있는지 말하기 어렵습니다.

Postgres가 시간을 보내는 위치에 대한 자세한 정보를 얻으려면 다음 테스트를 제안합니다.

  • 쿼리의 실행 시간을 집계 함수가 없는 SELECT와 비교합니다(예:이자형.5단계 잘라내기)
  • 집계로 인해 상당한 속도 저하가 발생하는 경우 Python이 더 빠르게 수행하여 비교에서 일반 SELECT를 통해 원시 데이터를 가져옵니다.

쿼리 속도를 높이려면 먼저 디스크 액세스를 줄이세요.시간이 걸리는 집계라는 것이 매우 의심됩니다.

이를 수행하는 방법에는 여러 가지가 있습니다.

  • db 엔진의 자체 기능이나 memcached와 같은 도구를 통해 후속 액세스를 위해 데이터를 메모리에 캐시합니다.
  • 저장된 데이터의 크기를 줄이세요
  • 인덱스 사용을 최적화합니다.때때로 이는 인덱스 사용을 완전히 건너뛰는 것을 의미할 수 있습니다(결국 디스크 액세스도 마찬가지입니다).MySQL의 경우 쿼리가 테이블에 있는 모든 데이터의 10% 이상을 가져오는 것으로 가정하면 인덱스를 건너뛰는 것이 권장된다는 것을 기억하는 것 같습니다.
  • 쿼리에서 인덱스를 잘 활용한다면 MySQL 데이터베이스의 경우 인덱스와 데이터를 별도의 물리적 디스크에 저장하는 것이 도움이 된다는 것을 알고 있습니다.그러나 그것이 Postgres에 적용 가능한지는 모르겠습니다.
  • 어떤 이유로 결과 집합을 메모리에서 완전히 처리할 수 없는 경우 행을 디스크로 교체하는 것과 같은 보다 정교한 문제가 있을 수도 있습니다.그러나 다른 해결 방법을 찾을 수 없는 심각한 성능 문제가 발생할 때까지 이러한 종류의 연구는 그대로 두겠습니다. 프로세스의 세부적인 세부 사항에 대한 지식이 필요하기 때문입니다.

업데이트:

방금 위의 쿼리에 대해 인덱스를 사용하지 않는 것 같고 인덱스도 사용하지 않을 가능성이 높으므로 인덱스에 대한 내 조언이 아마도 도움이 되지 않았다는 것을 깨달았습니다.죄송합니다.그래도 집계는 문제가 아니지만 디스크 액세스는 문제라고 말하고 싶습니다.어쨌든 색인 항목은 그대로 두겠습니다. 여전히 일부 용도가 있을 수 있습니다.

ENGINE = MEMORY를 지정하여 MySQL을 다시 테스트했는데 아무 것도 변경되지 않았습니다(여전히 200ms).메모리 내 DB를 사용하는 Sqlite3도 비슷한 타이밍(250ms)을 제공합니다.

수학 여기 올바른 것 같습니다(적어도 크기는 sqlite db의 크기이므로 :-)

나는 테이블이 메모리에 있다는 모든 징후가 있기 때문에 디스크 원인-느림 주장을 믿지 않습니다. (포스트그레스 사람들은 모두 OS가 프로그래머보다 더 잘할 것이라고 맹세하기 때문에 테이블을 메모리에 고정하려고 너무 열심히 노력하지 말라고 경고합니다. )

타이밍을 명확히 하기 위해 Java 코드는 디스크에서 읽지 않으므로 Postgres가 디스크에서 읽고 복잡한 쿼리를 계산하는 경우 완전히 불공평한 비교가 됩니다. 테이블을 메모리에 저장하고 저장 프로시저 IMHO를 미리 컴파일합니다.

업데이트(아래 첫 번째 의견에 대한 응답):

공정한 방식으로 집계 함수를 사용하지 않고 쿼리를 테스트하는 방법을 잘 모르겠습니다. 모든 행을 선택하면 모든 항목을 직렬화하고 형식화하는 데 많은 시간이 소요되기 때문입니다.속도 저하가 집계 기능으로 인한 것이라고 말하는 것은 아니지만 여전히 동시성, 무결성 및 친구로 인한 오버헤드일 수 있습니다.집계를 유일한 독립 변수로 분리하는 방법을 모르겠습니다.

이는 매우 상세한 답변이지만 데이터가 메모리에 쉽게 들어가고 동시 읽기가 필요하지만 쓰기가 필요하지 않으며 동일한 쿼리로 계속해서 쿼리된다는 점을 고려할 때 Postgres를 떠나지 않고 어떻게 이러한 이점을 얻을 수 있는지에 대한 질문을 던집니다.

쿼리 및 최적화 계획을 미리 컴파일할 수 있나요?저장 프로시저가 이 작업을 수행할 것이라고 생각했지만 실제로는 도움이 되지 않습니다.

디스크 액세스를 방지하려면 전체 테이블을 메모리에 캐시해야 합니다. Postgres가 그렇게 하도록 강제할 수 있나요?하지만 반복 실행 후 쿼리가 단 200ms 만에 실행되기 때문에 이미 이 작업을 수행하고 있는 것 같습니다.

테이블이 읽기 전용이므로 모든 잠금 코드를 최적화할 수 있도록 Postgres에 알릴 수 있습니까?

빈 테이블을 사용하여 쿼리 구성 비용을 추정하는 것이 가능하다고 생각합니다(타이밍 범위는 20-60ms).

Java/Python 테스트가 왜 유효하지 않은지 아직도 알 수 없습니다.Postgres는 그다지 많은 작업을 수행하지 않습니다(아직 동시성 측면에 대해서는 다루지 않았으며 캐싱 및 쿼리 구성만 다루었습니다).

업데이트:집계를 실행하기 위해 드라이버 및 직렬화 단계를 통해 Python으로 350,000을 가져오는 방식으로 제안된 SELECTS를 비교하거나 형식화 및 표시의 오버헤드가 타이밍과 분리하기 어렵기 때문에 집계를 생략하는 것도 공정하지 않다고 생각합니다.두 엔진이 모두 메모리 데이터에서 작동하는 경우 사과 대 사과 비교여야 하지만 이미 그런 일이 발생하고 있다고 보장하는 방법은 잘 모르겠습니다.

댓글을 추가하는 방법을 알 수 없습니다. 평판이 충분하지 않은 것 같습니다.

저는 MS-SQL 전문가입니다. DBCC 핀테이블 테이블을 캐시된 상태로 유지하고 통계 IO 설정 디스크가 아닌 캐시에서 읽는 중인지 확인하세요.

Postgres에서 PINTABLE을 모방하는 항목을 찾을 수 없지만 pg_buffercache 캐시에 있는 내용에 대한 세부 정보를 제공하는 것 같습니다. 이를 확인하고 테이블이 실제로 캐시되고 있는지 확인할 수 있습니다.

봉투 계산을 빠르게 살펴보면 디스크에서 페이징하고 있는 것으로 의심됩니다.Postgres가 4바이트 정수를 사용한다고 가정하면 행당 (6 * 4)바이트가 있으므로 테이블은 최소 (24 * 350,000)바이트 ~ 8.4MB입니다.HDD의 지속적인 처리량이 40MB/s라고 가정하면 데이터를 읽는 데 약 200ms가 소요됩니다(이는 지적한대로, 거의 모든 시간이 소요되는 곳이어야 합니다).

내가 어딘가에서 계산을 망친 것이 아니라면 Java 앱에서 8MB를 읽고 표시된 시간에 처리할 수 있는 방법을 알 수 없습니다. 해당 파일이 드라이브나 컴퓨터에 이미 캐시되어 있지 않은 한 OS.

나는 당신의 결과가 그렇게 놀랍다고 생각하지 않습니다. 어쨌든 Postgres가 너무 빠르다는 것입니다.

데이터를 캐시할 기회가 생기면 Postgres 쿼리가 두 번째로 더 빠르게 실행됩니까?좀 더 공정하게 하려면 Java 및 Python에 대한 테스트가 처음에 데이터를 획득하는 비용(이상적으로는 디스크에서 로드하는 비용)을 포함해야 합니다.

이 성능 수준이 실제로 애플리케이션에 문제가 되지만 다른 이유로 RDBMS가 필요한 경우 다음을 살펴볼 수 있습니다. 멤캐시드.그러면 원시 데이터에 더 빠르게 캐시된 액세스를 갖고 코드에서 계산을 수행할 수 있습니다.

Postgres에 액세스하기 위해 TCP를 사용하고 있습니까?이 경우 Nagle은 타이밍을 엉망으로 만들고 있습니다.

RDBMS가 일반적으로 수행하는 또 다른 기능은 다른 프로세스의 동시 액세스로부터 사용자를 보호하여 동시성을 제공하는 것입니다.이는 잠금 장치를 배치하여 수행되며 이로 인해 약간의 오버헤드가 발생합니다.

절대 변경되지 않는 완전히 정적 데이터를 처리하는 경우, 특히 기본적으로 "단일 사용자" 시나리오에 있는 경우 관계형 데이터베이스를 사용해도 반드시 많은 이점을 얻을 수 있는 것은 아닙니다.

프로그램을 메모리 내에서 수행하는 것과 비슷한 성능을 기대하려면 전체 작업 세트가 메모리에 들어갈 정도로 postgres의 캐시를 늘려야 합니다.

Oracle 타이밍을 알려주셔서 감사합니다. 제가 찾고 있던 것이 바로 그것이었습니다. (하지만 실망스럽긴 합니다 :-)

대부분의 사용자에 대해 이 쿼리의 가장 흥미로운 형식을 미리 계산할 수 있다고 생각하므로 구체화된 뷰는 고려해 볼 가치가 있을 것입니다.

Postgres를 실행하는 동일한 시스템에서 쿼리를 실행하고 있으므로 쿼리 왕복 시간이 매우 높아야 한다고 생각하지 않으므로 대기 시간이 많이 추가될 수 없습니까?

나는 또한 캐시 크기에 대해 몇 가지 검사를 해보았는데 Postgres는 캐싱을 처리하기 위해 OS에 의존하는 것 같습니다. 특히 BSD를 이에 대한 이상적인 OS로 언급하므로 Mac OS는 테이블을 캐시 크기로 가져오는 데 꽤 현명해야 한다고 생각합니다. 메모리.누군가가 더 구체적인 매개변수를 염두에 두지 않는 한 더 구체적인 캐싱은 통제할 수 없다고 생각합니다.

결국에는 200ms의 응답 시간을 참을 수 있지만 7ms가 가능한 목표라는 사실을 알면 만족스럽지 않습니다. 20-50ms 시간이라도 더 많은 사용자가 최신 쿼리를 사용하고 불필요한 작업을 제거할 수 있기 때문입니다. 캐싱과 미리 계산된 해킹이 많습니다.

방금 MySQL 5를 사용하여 타이밍을 확인했는데 Postgres보다 약간 나쁩니다.따라서 몇 가지 주요 캐싱 혁신을 제외하면 이것이 관계형 DB 경로로 갈 것으로 기대할 수 있는 것 같습니다.

귀하의 답변 중 일부에 찬성표를 던지고 싶지만 아직 포인트가 부족합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top