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