multiprocessing / psycopg2 TypeError:无法腌制_thread.RLock对象

时间:2018-09-27 13:07:03

标签: python postgresql sqlalchemy psycopg2 python-multiprocessing

我遵循以下代码,以便在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的托管实例作为数据库。

1 个答案:

答案 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个独立查询,可在同步时保持稳定的内存   用法。

您可能想要做的是以下选项之一:

  1. 编写一个子查询,该子查询生成所有6000个ID,并在原始批量查询中使用该子查询。
  2. 如上所述,但是将子查询写为CTE
  3. 如果您的ID列表来自外部来源(即不是来自数据库),则可以创建一个包含6000个ID的临时表,然后针对该临时表运行原始批量查询

但是,如果您坚持要通过python运行6000个ID,那么最快的查询可能既不会一次完成所有6000个ID(这将耗尽内存),也不会运行6000个单独的查询。相反,您可能想尝试对查询进行分块。例如一次发送500个ID。您将不得不尝试使用块大小来确定一次可以发送的最大ID数,同时仍能在内存预算之内。