Почему агрегатные функции SQL намного медленнее, чем Python и Java (или OLAP для бедняков) - PullRequest
13 голосов
/ 09 сентября 2008

Мне нужно мнение настоящего администратора. Postgres 8.3 требует 200 мс для выполнения этого запроса на моем Macbook Pro, тогда как Java и Python выполняют одинаковые вычисления менее чем за 20 мс (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 может обработать миллионы удвоений менее чем за 20 мс, я немного завидую.

Есть ли лучший способ сделать простой OLAP дешевым (как с точки зрения денег, так и сложности сервера)? Я изучал Mondrian и Pig + Hadoop, но не очень доволен обслуживанием еще одного серверного приложения и не уверен, что они вообще помогут.


Нет, так сказать, код Python и код Java не выполняют всю работу в доме. Я просто генерирую 4 массива с 350 000 случайных значений в каждом, а затем беру среднее. Я не включаю поколение в тайминги, только шаг усреднения. Для синхронизации потоков Java используются 4 потока (по одному на среднее значение в массиве), это избыточно, но это определенно самый быстрый.

Время sqlite3 управляется программой Python и запускается с диска (не: memory:)

Я понимаю, что Postgres делает гораздо больше за кулисами, но большая часть этой работы не имеет значения для меня, поскольку это только данные для чтения.

Запрос Postgres не изменяет время при последующих запусках.

Я перезапустил тесты Python, чтобы включить его спулинг с диска. Время значительно замедляется до почти 4 секунд. Но я предполагаю, что код обработки файлов в Python в значительной степени написан на C (хотя, может быть, и не на csv lib?), Так что это указывает на то, что Postgres также не выполняет потоковую передачу с диска (или что вы правы, и я должен преклониться перед тем, кто написал свой слой хранения!)

Ответы [ 10 ]

14 голосов
/ 09 сентября 2008

Я бы сказал, что ваша тестовая схема не очень полезна. Для выполнения запроса БД сервер БД проходит несколько этапов:

  1. парсинг SQL
  2. составить план запроса, т.е. е. решить, какие индексы использовать (если есть), оптимизировать и т. д.
  3. если используется индекс, найдите в нем указатели на фактические данные, затем перейдите в соответствующее место в данных или
  4. если индекс не используется, отсканируйте всю таблицу , чтобы определить, какие строки необходимы
  5. загрузка данных с диска во временную папку (надеюсь, но не обязательно, в память)
  6. выполнить вычисления count () и avg ()

Итак, создание массива в Python и получение среднего значения в основном пропускает все эти шаги, кроме последнего. Поскольку дисковый ввод-вывод является одной из самых дорогих операций, которые должна выполнять программа, это является серьезным недостатком теста (см. Также ответы на этот вопрос , которые я задавал здесь ранее). Даже если вы прочитаете данные с диска в другом тесте, процесс будет совершенно другим, и трудно сказать, насколько уместны результаты.

Чтобы получить больше информации о том, где Postgres проводит свое время, я бы предложил следующие тесты:

  • Сравните время выполнения вашего запроса с SELECT без агрегирующих функций (т. Е. Вырезать шаг 5)
  • Если вы обнаружите, что агрегация приводит к значительному замедлению, попробуйте, если Python сделает это быстрее, получая необработанные данные с помощью простого SELECT из сравнения.

Чтобы ускорить ваш запрос, сначала уменьшите доступ к диску. Я очень сомневаюсь, что агрегация требует времени.

Есть несколько способов сделать это:

  • Кэшировать данные (в памяти!) Для последующего доступа, либо через собственные возможности движка БД, либо с помощью таких инструментов, как memcached
  • Уменьшите размер хранимых данных
  • Оптимизировать использование индексов. Иногда это может означать полное исключение использования индекса (в конце концов, это и доступ к диску). Что касается MySQL, я помню, что рекомендуется пропускать индексы, если вы предполагаете, что запрос извлекает более 10% всех данных в таблице.
  • Если ваш запрос хорошо использует индексы, я знаю, что для баз данных MySQL это помогает размещать индексы и данные на отдельных физических дисках. Однако я не знаю, применимо ли это к Postgres.
  • Также могут быть более сложные проблемы, такие как перестановка строк на диск, если по какой-то причине результирующий набор не может быть полностью обработан в памяти. Но я бы оставил исследования такого рода, пока не столкнусь с серьезными проблемами с производительностью, которые не могу найти другого способа исправить, так как они требуют знания множества мелких деталей в вашем процессе.

Обновление:

Я только что понял, что вы, похоже, не используете индексы для вышеуказанного запроса и, скорее всего, тоже не используете их, поэтому мой совет по индексам, вероятно, не помог. Сожалею. Тем не менее, я бы сказал, что агрегация - это не проблема, а доступ к диску. В любом случае, я оставлю индексные элементы, но они все еще могут быть полезны.

8 голосов
/ 09 сентября 2008

Postgres делает намного больше, чем кажется (поддерживая согласованность данных для начала!)

Если значения не обязательно должны быть на 100% точными или если таблица обновляется редко, но вы часто выполняете этот расчет, вы можете захотеть заглянуть в Материализованные представления, чтобы ускорить его.

(Обратите внимание, я не использовал материализованные представления в Postgres, они выглядят немного странно, но могут подходить для вашей ситуации).

Материализованные представления

Также рассмотрите накладные расходы на фактическое соединение с сервером и двустороннюю передачу, необходимую для отправки запроса на сервер и обратно.

Я бы посчитал, что 200 мс для чего-то подобного довольно неплохо. Быстрый тест на моем сервере Oracle, с той же структурой таблицы, содержащей около 500 000 строк и без индексов, занимает от 1 до 1,5 секунд, что почти все просто оракул. высасывая данные с диска.

Реальный вопрос в том, достаточно ли 200 мс?

-------------- Подробнее --------------------

Мне было интересно решить эту проблему, используя материализованные представления, поскольку я никогда не играл с ними. Это в оракуле.

Сначала я создал 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 может показаться конечному пользователю, если вы можете жить со значениями, которые не совсем точны до второй.

5 голосов
/ 10 сентября 2008

Я перепроверил с MySQL, указав ENGINE = MEMORY, и это ничего не меняет (все еще 200 мс). Sqlite3, использующий БД в памяти, также дает аналогичные тайминги (250 мс).

Математика здесь выглядит правильно (по крайней мере, размер, так как вот насколько велика база данных sqlite: -)

Я просто не покупаю аргумент дисковая причина-медлительность, поскольку есть все признаки того, что таблицы находятся в памяти (ребята из postgres предостерегают от слишком больших усилий, чтобы прикрепить таблицы к памяти, поскольку они клянутся, что ОС сделает это лучше чем программист)

Чтобы уточнить временные рамки, код Java не читает с диска, что делает его совершенно несправедливым сравнением, если Postgres читает с диска и вычисляет сложный запрос, но это действительно, кроме того, БД должна быть достаточно умной, чтобы перенести небольшую таблицу в память и предварительно скомпилировать хранимую процедуру ИМХО.

ОБНОВЛЕНИЕ (в ответ на первый комментарий ниже):

Я не уверен, как бы я протестировал запрос без использования функции агрегирования таким образом, чтобы это было бы справедливо, поскольку, если бы я выбрал все строки, он потратил бы кучу времени на сериализацию и форматирование всего , Я не говорю, что медлительность связана с функцией агрегации, она все равно может быть связана с параллелизмом, целостностью и друзьями. Я просто не знаю, как выделить агрегацию в качестве единственной независимой переменной.

3 голосов
/ 09 сентября 2008

Это очень подробные ответы, но в основном они задают вопрос, как мне получить эти преимущества, не оставляя Postgres, учитывая, что данные легко помещаются в память, требуют одновременного чтения, но не записывают и запрашивают один и тот же запрос снова и снова еще раз.

Можно ли предварительно скомпилировать план запроса и оптимизации? Я бы подумал, что хранимая процедура сделает это, но это не очень помогает.

Чтобы избежать доступа к диску, необходимо кэшировать всю таблицу в памяти. Могу ли я заставить Postgres сделать это? Я думаю, что он уже делает это, так как запрос выполняется всего за 200 мс после повторных запусков.

Могу ли я сообщить Postgres, что таблица предназначена только для чтения, поэтому она может оптимизировать любой код блокировки?

Я думаю, что можно оценить затраты на создание запроса с пустой таблицей (время колеблется от 20 до 60 мс)

Я до сих пор не понимаю, почему тесты Java / Python недействительны. Postgres просто не выполняет так много работы (хотя я до сих пор не рассмотрел аспект параллелизма, только кеширование и построение запросов)

UPDATE: Я не думаю, что было бы справедливо сравнивать SELECTS, как предлагалось, потянув 350 000 через этапы драйвера и сериализации в Python для запуска агрегации, и даже не пропускать агрегацию, поскольку накладные расходы на форматирование и отображение трудно отделить от времени. Если оба механизма работают в данных памяти, это должно быть сравнение яблок с яблоками, хотя я не уверен, как гарантировать, что это уже происходит.

Не могу понять, как добавлять комментарии, может, мне не хватает репутации?

2 голосов
/ 10 сентября 2008

Я сам из MS-SQL, и мы будем использовать DBCC PINTABLE , чтобы держать таблицу в кэше, и SET STATISTICS IO , чтобы увидеть, что она читает из кэша, а не диск.

Я не могу найти что-либо на Postgres для имитации PINTABLE, но pg_buffercache , кажется, дает подробности о том, что находится в кеше - вы можете проверить это и посмотреть, действительно ли ваша таблица кешируется .

Быстрый расчет конверта заставляет меня подозревать, что вы перебираете страницы с диска. Предполагая, что Postgres использует 4-байтовые целые числа, у вас есть (6 * 4) байтов на строку, поэтому ваша таблица имеет минимум (24 * 350 000) байтов ~ 8,4 МБ. Предполагая, что на жестком диске поддерживается постоянная пропускная способность 40 МБ / с, вы читаете данные с точностью до 200 мс (что, как указано , должно составлять почти все время).

Если я где-то не испортил свою математику, я не понимаю, как это возможно, что вы можете прочитать 8 МБ в свое Java-приложение и обработать его в то время, которое вы показываете - если этот файл уже не кэшируется диск или ваша ОС.

1 голос
/ 10 сентября 2008

Используете ли вы TCP для доступа к Postgres? В этом случае Нэгл возится с твоим временем.

1 голос
/ 09 сентября 2008

Я не думаю, что ваши результаты настолько удивительны - во всяком случае, Postgres так быстр.

Запускается ли запрос Postgres быстрее во второй раз, когда у него есть возможность кэшировать данные? Чтобы быть немного более справедливым, ваш тест на Java и Python должен в первую очередь покрывать расходы на получение данных (в идеале - загрузку их с диска).

Если этот уровень производительности является проблемой для вашего приложения на практике, но вам нужна СУБД по другим причинам, тогда вы можете посмотреть на memcached . Тогда вы бы быстрее кэшировали доступ к необработанным данным и могли выполнять вычисления в коде.

0 голосов
/ 09 сентября 2008

Спасибо за время Oracle, вот что я ищу (хотя и разочаровывает: -)

Материализованные представления, вероятно, заслуживают рассмотрения, так как, я думаю, я могу заранее рассчитать наиболее интересные формы этого запроса для большинства пользователей.

Я не думаю, что время выполнения запроса должно быть очень большим, так как я выполняю запросы на той же машине, на которой работает Postgres, поэтому он не может добавить большую задержку?

Я также проверил некоторые размеры кеша, и, похоже, Postgres полагается на ОС для управления кешированием, они специально упоминают BSD в качестве идеальной ОС для этого, поэтому я думаю, что Mac OS должна быть достаточно умна в обеспечении стол в память. Если кто-то не имеет в виду более конкретные параметры, я думаю, что более конкретное кэширование не в моей власти.

В конце концов, я, вероятно, могу смириться с временем отклика 200 мс, но зная, что 7 мс - возможная цель, я чувствую себя неудовлетворенным, так как даже 20-50 мс раз позволят большему количеству пользователей иметь больше актуальных запросов и избавиться от множества кеширующих и предварительно вычисленных хаков.

Я только что проверил тайминги с использованием MySQL 5, и они немного хуже, чем Postgres. Поэтому, за исключением некоторых важных достижений в области кэширования, я думаю, что именно этого я и ожидаю, идя по реляционному маршруту БД.

Хотелось бы проголосовать за ваши ответы, но у меня пока недостаточно очков.

0 голосов
/ 09 сентября 2008

Вам необходимо увеличить кэши postgres до уровня, когда весь рабочий набор помещается в память, прежде чем вы сможете увидеть производительность, сравнимую с выполнением в памяти программы.

0 голосов
/ 09 сентября 2008

Еще одна вещь, которую СУБД обычно делает для вас, - это обеспечение параллелизма, защищая вас от одновременного доступа другим процессом. Это делается путем установки замков, и от этого есть некоторые накладные расходы.

Если вы имеете дело с полностью статическими данными, которые никогда не меняются, особенно если вы работаете в основном с «однопользовательским» сценарием, то использование реляционной базы данных не обязательно принесет вам большую пользу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...