Почему агрегатные функции SQL намного медленнее, чем Python и Java (или OLAP бедняка)

мне нужно мнение реального DBA. 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 приводит к замедлению 10x)

настройки, которые я пробовал без успеха, включают (слепо следуя некоторым веб-советам):

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? Я могу понять, что кислота должна идти с затратами, но это своего рода безумие на мой взгляд. Я не прошу скорости игры в реальном времени, но поскольку Java может обрабатывать миллионы двойников менее чем за 20 мс, я немного ревную.

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


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

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

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

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

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

10 ответов


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

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

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

овеществленный Вид

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

Я бы счел 200 мс для чего - то вроде этого довольно хорошим, быстрый тест на моем сервере oracle, та же структура таблицы с около 500k строк и без индексов, занимает около 1-1.5 секунд, что почти все просто oracle сосать данные с диска.

реальный вопрос, 200ms быстро достаточно?

-------------- больше --------------------

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

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


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

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

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

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

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

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

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

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

обновление:

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


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

математика здесь выглядит правильно (по крайней мере, размер, так как это, как большой SQLite db :-)

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

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

UPDATE (в ответ на первый комментарий ниже):

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


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

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

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

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

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

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

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

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


Я сам парень MS-SQL, и мы бы использовали DBCC PINTABLE сохранить таблицу в кэше, и УСТАНОВИТЬ СТАТИСТИКУ IO чтобы увидеть, что он читает из кэша, а не с диска.

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

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

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


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

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

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


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


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

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


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


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

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

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

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

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

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

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