Повышение производительности запросов Postgres psycopg2 для Python к тому же уровню драйвера JDBC Java - PullRequest
44 голосов
/ 04 марта 2011

Обзор

Я пытаюсь улучшить производительность запросов к нашей базе данных для SQLAlchemy. Мы используем psycopg2. В нашей производственной системе мы решили использовать Java, потому что она просто быстрее как минимум на 50%, если не ближе к 100%. Поэтому я надеюсь, что у кого-то из сообщества Stack Overflow есть способ улучшить мою производительность.

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

Детали

У меня запущен простой запрос "SELECT * FROM someLargeDataSetTable". Размер набора данных - ГБ. Быстрый график производительности выглядит следующим образом:

Таблица времени

        Records    | JDBC  | SQLAlchemy[1] |  SQLAlchemy[2] |  Psql
-------------------------------------------------------------------- 
         1 (4kB)   | 200ms |         300ms |          250ms |   10ms
        10 (8kB)   | 200ms |         300ms |          250ms |   10ms
       100 (88kB)  | 200ms |         300ms |          250ms |   10ms
     1,000 (600kB) | 300ms |         300ms |          370ms |  100ms
    10,000 (6MB)   | 800ms |         830ms |          730ms |  850ms  
   100,000 (50MB)  |    4s |            5s |           4.6s |     8s
 1,000,000 (510MB) |   30s |           50s |            50s |  1m32s  
10,000,000 (5.1GB) | 4m44s |         7m55s |          6m39s |    n/a
-------------------------------------------------------------------- 
 5,000,000 (2.6GB) | 2m30s |         4m45s |          3m52s | 14m22s
-------------------------------------------------------------------- 
[1] - With the processrow function
[2] - Without the processrow function (direct dump)

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

Таблица синхронизации Примечания:

  • Размеры данных являются приблизительными, но они должны дать вам представление о количестве данных.
  • Я использую инструмент time из командной строки Linux bash.
  • Время - это время настенных часов (т.е. реальное).
  • Я использую Python 2.6.6 и работаю с python -u
  • Размер выборки составляет 10 000
  • Меня не очень беспокоит время Psql, оно просто в качестве ориентира. Возможно, я не правильно установил для него размер выборки.
  • Я также действительно не беспокоюсь по поводу времени ниже размера выборки, так как менее 5 секунд незначительно для моего приложения.
  • Java и Psql занимают около 1 ГБ ресурсов памяти; Python больше походит на 100 МБ (yay !!).
  • Я использую библиотеку [cdecimals] .
  • Я заметил, что [недавняя статья] обсуждает нечто подобное. Похоже, что дизайн драйвера JDBC полностью отличается от дизайна psycopg2 (который, я думаю, довольно раздражает, учитывая разницу в производительности).
  • Мой вариант использования в основном заключается в том, что мне приходится запускать ежедневный процесс (с примерно 20 000 различных шагов ... несколько запросов) по очень большим наборам данных, и у меня есть очень специфическое окно времени, когда я могу завершить этот процесс. Java, которую мы используем, - это не просто JDBC, это «умная» оболочка поверх движка JDBC ... мы не хотим использовать Java, и мы хотели бы прекратить использовать «умную» часть.
  • Я использую один из блоков нашей производственной системы (база данных и внутренний процесс) для выполнения запроса. Так что это наш лучший выбор времени. У нас есть блоки QA и Dev, которые работают намного медленнее, и дополнительное время запроса может стать значительным.

testSqlAlchemy.py

#!/usr/bin/env python
# testSqlAlchemy.py
import sys
try:
    import cdecimal
    sys.modules["decimal"]=cdecimal
except ImportError,e:
    print >> sys.stderr, "Error: cdecimal didn't load properly."
    raise SystemExit
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

def processrow (row,delimiter="|",null="\N"):
    newrow = []
    for x in row:
        if x is None:
            x = null
        newrow.append(str(x))
    return delimiter.join(newrow)

fetchsize = 10000
connectionString = "postgresql+psycopg2://usr:pass@server:port/db"
eng = create_engine(connectionString, server_side_cursors=True)
session = sessionmaker(bind=eng)()

with open("test.sql","r") as queryFD:
   with open("/dev/null","w") as nullDev:
        query = session.execute(queryFD.read())
        cur = query.cursor
        while cur.statusmessage not in ['FETCH 0','CLOSE CURSOR']:
            for row in query.fetchmany(fetchsize):
                print >> nullDev, processrow(row)

По истечении времени я также запустил cProfile, и это дамп худших преступников:

Профиль синхронизации (с обработкой)

Fri Mar  4 13:49:45 2011    sqlAlchemy.prof

         415757706 function calls (415756424 primitive calls) in 563.923 CPU seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001  563.924  563.924 {execfile}
        1   25.151   25.151  563.924  563.924 testSqlAlchemy.py:2()
     1001    0.050    0.000  329.285    0.329 base.py:2679(fetchmany)
     1001    5.503    0.005  314.665    0.314 base.py:2804(_fetchmany_impl)
 10000003    4.328    0.000  307.843    0.000 base.py:2795(_fetchone_impl)
    10011    0.309    0.000  302.743    0.030 base.py:2790(__buffer_rows)
    10011  233.620    0.023  302.425    0.030 {method 'fetchmany' of 'psycopg2._psycopg.cursor' objects}
 10000000  145.459    0.000  209.147    0.000 testSqlAlchemy.py:13(processrow)

Профиль синхронизации (без процесса)

Fri Mar  4 14:03:06 2011    sqlAlchemy.prof

         305460312 function calls (305459030 primitive calls) in 536.368 CPU seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001  536.370  536.370 {execfile}
        1   29.503   29.503  536.369  536.369 testSqlAlchemy.py:2()
     1001    0.066    0.000  333.806    0.333 base.py:2679(fetchmany)
     1001    5.444    0.005  318.462    0.318 base.py:2804(_fetchmany_impl)
 10000003    4.389    0.000  311.647    0.000 base.py:2795(_fetchone_impl)
    10011    0.339    0.000  306.452    0.031 base.py:2790(__buffer_rows)
    10011  235.664    0.024  306.102    0.031 {method 'fetchmany' of 'psycopg2._psycopg.cursor' objects}
 10000000   32.904    0.000  172.802    0.000 base.py:2246(__repr__)

Заключительные комментарии

К сожалению, функция processrow должна остаться, если в SQLAlchemy нет способа указать выходные данные null = 'userDefinedValueOrString' и delimiter = 'userDefinedValueOrString'. Java, которую мы используем в настоящее время, уже делает это, поэтому сравнение (с processrow) должно было быть как яблоки, так и яблоки. Если есть способ улучшить производительность либо Processrow, либо SQLAlchemy с помощью чистого Python или настройки параметров, я очень заинтересован.

Ответы [ 4 ]

3 голосов
/ 02 декабря 2011

Это не готовый ответ, со всеми вещами client / db, возможно, вам понадобится проделать определенную работу, чтобы точно определить, что не так

резервное копирование postgresql.conf изменение

log_min_duration_statement to 0 
log_destination = 'csvlog'              # Valid values are combinations of      
logging_collector = on                # Enable capturing of stderr and csvlog 
log_directory = 'pg_log'                # directory where log files are written,
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log' # log file name pattern,        
debug_print_parse = on
debug_print_rewritten = on
debug_print_plan output = on
log_min_messages = info (debug1 for all server versions prior to 8.4)

Остановите и перезапустите сервер базы данных (перезагрузка может не принять изменения) Воспроизведите свои тесты, убедившись, что время сервера и время клиента совпадают, и вы записали время запуска и т. Д.

Скопируйте файл журнала из импорта в редактор по вашему выбору (Excel или другая электронная таблица может быть полезна для предварительной обработки SQL и планов и т. Д.)

Теперь проверьте время на стороне сервера и обратите внимание:

  • - это то, что sql сообщается на сервере одинаково в каждом случае

  • если то же самое у вас должно быть то же время

  • - клиент, генерирующий курсор, а не передающий sql

  • - это один из драйверов, выполняющий много приведений / преобразований между наборами символов или неявное преобразование других типов, таких как даты или отметки времени.

и т. Д.

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

2 голосов
/ 11 июня 2011

Приведенные ниже сведения, вероятно, нацелены на то, что вы не имеете в виду или на то, что считается приемлемым в вашей среде, но на всякий случай я поставлю этот вариант на стол.

  1. Является ли пункт назначения каждого SELECT в вашем test.sql действительно простым | -разделенным файлом результатов?
  2. Допустима ли непереносимость (специфичность по Postgres)?
  3. Ваш бэкэнд Postgres 8.2 или новее?
  4. Будет ли скрипт выполняться на том же хосте, что и серверная часть базы данных, или было бы приемлемо сгенерировать | -файл (-ы) с результатами внутри серверной части (например, для общего ресурса?)

Если ответ на все вышеупомянутые вопросы да , то вы можете преобразовать свои SELECT ... заявления в COPY ( SELECT ... ) TO E'path-to-results-file' WITH DELIMITER '|' NULL E'\\N'.

1 голос
/ 16 марта 2011

Альтернативой может быть использование ODBC. Предполагается, что драйвер Python ODBC работает хорошо.

PostgreSQL имеет драйверы ODBC для Windows и Linux.

0 голосов
/ 03 сентября 2011

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

Вместо того, чтобы использовать python, который оборачивается чем-то еще, что интегрируется с чем-то, что является оболочкой C вокруг БД .... просто напишите код на C. Я имею в виду, сколько времени это может занять? С Postgres не сложно взаимодействовать (как раз наоборот). С - легкий язык. Операции, которые вы выполняете, кажутся довольно простыми. Вы также можете использовать SQL, встроенный в C, это просто вопрос предварительной компиляции. Не нужно переводить то, о чем вы думали - просто напишите это вместе с C и используйте поставляемый компилятор ECPG (см. Руководство по эксплуатации postgres, глава 29, iirc).

Уберите как можно больше промежуточных интерфейсных вещей, отрежьте посредника и начните общаться с базой данных. Мне кажется, что, пытаясь упростить систему, вы на самом деле делаете ее более сложной, чем она должна быть. Когда вещи становятся действительно беспорядочными, я обычно задаю себе вопрос: «Какой кусок кода я больше всего боюсь касаться?» - это обычно указывает на то, что нужно изменить.

Извините за болтовню, но, возможно, шаг назад и немного свежего воздуха поможет;)

...