我遵循以下代码,以便在postgres数据库上实现并行选择查询:
https://tech.geoblink.com/2017/07/06/parallelizing-queries-in-postgresql-with-python/
我的基本问题是我有大约6,000个查询需要执行,而我正在尝试优化这些选择查询的执行。最初,它是一个单个查询,其中where id in (...)
包含所有6k谓词ID,但是我在运行的计算机上使用了超过4GB的RAM时遇到了查询问题,因此我决定将其拆分为6k个单独的查询当同步保持稳定的内存使用时。但是,在运行时需要花费更长的时间,这对我的用例来说不是一个大问题。即使这样,我仍在尝试尽可能减少时间。
这是我的代码的样子:
class PostgresConnector(object):
def __init__(self, db_url):
self.db_url = db_url
self.engine = self.init_connection()
self.pool = self.init_pool()
def init_pool(self):
CPUS = multiprocessing.cpu_count()
return multiprocessing.Pool(CPUS)
def init_connection(self):
LOGGER.info('Creating Postgres engine')
return create_engine(self.db_url)
def run_parallel_queries(self, queries):
results = []
try:
for i in self.pool.imap_unordered(self.execute_parallel_query, queries):
results.append(i)
except Exception as exception:
LOGGER.error('Error whilst executing %s queries in parallel: %s', len(queries), exception)
raise
finally:
self.pool.close()
self.pool.join()
LOGGER.info('Parallel query ran producing %s sets of results of type: %s', len(results), type(results))
return list(chain.from_iterable(results))
def execute_parallel_query(self, query):
con = psycopg2.connect(self.db_url)
cur = con.cursor()
cur.execute(query)
records = cur.fetchall()
con.close()
return list(records)
无论何时运行,都会出现以下错误:
TypeError: can't pickle _thread.RLock objects
我已经阅读了许多有关使用多处理和可腌制对象的类似问题,但是我终生无法弄清自己在做错什么。
该池通常是每个进程一个池(我认为这是最好的做法),但每个连接器类实例都共享一个池,这样它就不会为每次使用parallel_query方法创建一个池。
相似问题的最高答案:
Accessing a MySQL connection pool from Python multiprocessing
显示与我自己的实现几乎相同的实现,除了使用MySql代替Postgres。
我做错什么了吗?
谢谢!
编辑:
我找到了这个答案:
Python Postgres psycopg2 ThreadedConnectionPool exhausted
非常详细,看起来好像我误解了multiprocessing.Pool
和连接池(例如ThreadedConnectionPool
给我的东西)。但是,在第一个链接中并没有提到需要任何连接池等。此解决方案看起来不错,但似乎有很多代码可以解决我认为很简单的问题?
编辑2:
所以上面的链接解决了另一个问题,无论如何我都会碰到这个问题,因此我很高兴发现了这个问题,但是它不能解决最初不能使用imap_unordered
的问题。错误。非常沮丧。
最后,我认为可能值得注意的是,它运行在Heroku上,使用的是dyno,使用Redis rq进行调度,后台任务等,并使用Postgres的托管实例作为数据库。
答案 0 :(得分:4)
简单地说,postgres连接和sqlalchemy连接池是线程安全的,但是它们不是派生安全的。
如果要使用多处理,则应在fork之后的每个子进程中初始化引擎。
如果要共享引擎,应改用多线程。
请参阅Thread and process safety in psycopg2 documentation:
libpq连接 不应该被派生的进程使用,所以当使用这样的模块时 作为多处理或Fork Web部署方法(如FastCGI make) 确保在派生之后创建连接。
如果使用multiprocessing.Pool,则有一个关键字参数初始化程序,可用于在每个子进程上运行一次代码。试试这个:
class PostgresConnector(object):
def __init__(self, db_url):
self.db_url = db_url
self.pool = self.init_pool()
def init_pool(self):
CPUS = multiprocessing.cpu_count()
return multiprocessing.Pool(CPUS, initializer=self.init_connection(self.db_url))
@classmethod
def init_connection(cls, db_url):
def _init_connection():
LOGGER.info('Creating Postgres engine')
cls.engine = create_engine(db_url)
return _init_connection
def run_parallel_queries(self, queries):
results = []
try:
for i in self.pool.imap_unordered(self.execute_parallel_query, queries):
results.append(i)
except Exception as exception:
LOGGER.error('Error whilst executing %s queries in parallel: %s', len(queries), exception)
raise
finally:
pass
#self.pool.close()
#self.pool.join()
LOGGER.info('Parallel query ran producing %s sets of results of type: %s', len(results), type(results))
return list(chain.from_iterable(results))
def execute_parallel_query(self, query):
with self.engine.connect() as conn:
with conn.begin():
result = conn.execute(query)
return result.fetchall()
def __getstate__(self):
# this is a hack, if you want to remove this method, you should
# remove self.pool and just pass pool explicitly
self_dict = self.__dict__.copy()
del self_dict['pool']
return self_dict
现在,解决XY问题。
最初是单个查询,其中(...)中的where id 所有6k谓词ID,但我在使用up>时遇到了查询问题 运行它的机器上有4GB的RAM,所以我决定将其拆分为 6k个独立查询,可在同步时保持稳定的内存 用法。
您可能想要做的是以下选项之一:
但是,如果您坚持要通过python运行6000个ID,那么最快的查询可能既不会一次完成所有6000个ID(这将耗尽内存),也不会运行6000个单独的查询。相反,您可能想尝试对查询进行分块。例如一次发送500个ID。您将不得不尝试使用块大小来确定一次可以发送的最大ID数,同时仍能在内存预算之内。