将Python的Postgres psycopg2查询性能提高到与Java JDBC驱动程序相同的级别

时间:2011-03-04 19:33:38

标签: performance postgresql jdbc psycopg2

概述

我正在尝试提高SQLAlchemy数据库查询的性能。我们正在使用psycopg2。在我们的生产系统中,我们选择使用Java,因为它速度提高了至少50%,即使不是接近100%。所以我希望Stack Overflow社区中的某个人能够提高我的表现。

我认为我的下一步将是最终修补psycopg2库,使其行为类似于JDBC驱动程序。如果是这种情况并且有人已经这样做了,那就没问题,但我希望我仍然可以通过Python进行设置或重构调整。

详细

我有一个简单的“SELECT * FROM someLargeDataSetTable”查询运行。数据集的大小为GB。快速绩效图表如下:

时间表

        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的表现会更好。一些笔记......

时间表注释:

  • 数据量是近似值,但它们可以让您了解数据量。
  • 我正在使用Linux bash命令行中的'time'工具。
  • 时间是挂钟时间(即真实时间)。
  • 我正在使用Python 2.6.6并且我正在运行python -u
  • 获取大小为10,000
  • 我并不是真的担心Psql时序,它只是作为一个参考点。我可能没有为它正确设置fetchsize。
  • 我真的不担心提取大小以下的时间,因为我的应用程序可以忽略不到5秒。
  • Java和Psql似乎占用了大约1GB的内存资源; Python更像是100MB(yay !!)。
  • 我正在使用[cdecimals]库。
  • 我注意到[recent article]正在讨论与此相似的事情。似乎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__)

最终评论

不幸的是,除非在SQLAlchemy中有一种方法指定输出的null ='userDefinedValueOrString'和delimiter ='userDefinedValueOrString',否则processrow函数需要保持不变。我们目前使用的Java已经这样做了,所以比较(与processrow)需要苹果对苹果。如果有办法用纯Python或设置调整来提高processrow或SQLAlchemy的性能,我很感兴趣。

4 个答案:

答案 0 :(得分:3)

这不是一个开箱即用的答案,你可能需要做一些客户/数据库工作来确定究竟是什么错误

备份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是否存在严重差异。

答案 1 :(得分:2)

下面的内容可能超出您的想法或您的环境中可接受的内容,但我会将选项放在桌面上以防万一。

  1. SELECT中每个test.sql的目的地是否真的是一个简单的|分隔结果文件?
  2. 不可移植性(Postgres特异性)是否可以接受?
  3. 你的后端是Postgres 8.2还是更新?
  4. 脚本是否与数据库后端在同一主机上运行,​​或者是否可以从后端生成| - 分离的结果文件(例如,共享?)
  5. 如果上述所有问题的答案都是,那么您可以将SELECT ...语句转换为COPY ( SELECT ... ) TO E'path-to-results-file' WITH DELIMITER '|' NULL E'\\N'

答案 2 :(得分:1)

另一种方法是使用ODBC。这假设Python ODBC驱动程序运行良好。

PostgreSQL具有适用于Windows和Linux的ODBC驱动程序。

答案 3 :(得分:0)

作为主要使用汇编程序编写程序的人,有一件事情显而易见。你在开销上浪费了时间,而且开销是需要的。

而不是使用python,它将自己包装在与DB周围的C包装器集成的其他东西中......只需用C语言编写代码。我的意思是,需要多长时间? Postgres并不难与之接口(恰恰相反)。 C是一个简单的语言。您正在执行的操作看起来非常简单。您也可以使用嵌入在C中的SQL,这只是预编译的问题。无需翻译您的想法 - 只需将其与C一起编写并使用提供的ECPG编译器(阅读postgres手册第29章iirc)。

尽可能多地删除中间界面内容,切断中间人并与本地数据库进行交谈。在我看来,在尝试使系统更简单时,实际上使它变得比它需要的更复杂。当事情变得非常混乱时,我常常问自己一个问题:“我最害怕接触的代码是什么?” - 这通常指向我需要改变的东西。

抱歉唠叨,但也许退后一步,一些新鲜空气会有所帮助;)