postgresql Django - bulk_create()导致内存错误

tv6aics1  于 5个月前  发布在  PostgreSQL
关注(0)|答案(6)|浏览(62)

我有大约400000个对象示例要插入到postgres中。我使用bulk_create()来执行此操作,但我遇到了内存错误。
我的第一个想法是将示例列表分块:

def chunks(l, n):
    n = max(1, n)
    return [l[i:i + n] for i in range(0, len(l), n)]

for c in chunks(instances, 1000):
    Feature.objects.bulk_create(c)

字符串
但有时这种策略也会导致内存错误,因为示例的大小可能变化很大,所以一个块可能超过内存限制,而其他块则没有。
是否可以将示例列表分块,以便具有分隔大小的块?在这种情况下,最佳方法是什么?

prdp8dxp

prdp8dxp1#

如果你在调试模式下使用Django,它会跟踪你所有的SQL语句以进行调试。对于许多对象,这可能会导致内存问题。你可以使用以下命令重置:

from django import db
db.reset_queries()

字符串
查看why-is-django-leaking-memory

ie3xauqp

ie3xauqp2#

可以在bulk_create方法中指定batch_size。

Syntax: bulk_create(objs, batch_size=None)
Feature.objects.bulk_create(instances, batch_size=1000)

字符串
Django 2.2:https://docs.djangoproject.com/en/2.2/ref/models/querysets/#bulk-create
Django 3.1:https://docs.djangoproject.com/en/3.1/ref/models/querysets/#bulk-create

gijlo24d

gijlo24d3#

如果您没有在DEBUG模式下运行,并且仍然有错误,我的解决方案应该可以帮助您。首先,确保您有一组要保存的延迟生成的对象(例如,从远程API批量获取)

def generate_data():
    """Example data generator"""
    for i in range(100000):
        yield Model(counter=i)

data_gen = generate_data()
# >> print data_gen
# <generator object data at 0x7f057591c5c8>
# 
# it's a generator, objects are not yet created. 
# You can iterate it one-by-one or force generation using list(data_gen)
# But for our approach, we need generator version

字符串
接下来,我们需要一个函数,它一次最多从生成器中获取X个对象,并使用batch_create保存它。这样,在一个时刻,我们将在内存中保存不超过X个对象。

from itertools import islice

def bulk_create_iter(iterable, batch_size=10000):
    """Bulk create supporting generators. Returns only count of created objects."""
    created = 0
    while True:
        objects = Model.bulk_create(islice(iterable, batch_size))
        created += len(objects)
        if not objects:
            break
    return created


像这样使用它

print(bulk_create_iter(data_gen))
# prints 100000


不能只使用batch_create的原因是它在内部执行list(objs),因此整个生成器被示例化并保存到内存中。在这种方法中,我们一次示例化最多batch_size对象。这种方法可以用于处理非常大的集合,因为内存消耗应该是恒定的(测试了1500万条记录,内存使用量始终低于300MB)。
这个函数的通用版本,作为Django Manager类的一个方法(你可以通过写objects = BulkManager()在你的模型中使用它):

from itertools import islice
from django.db import models

class BulkManager(models.Manager):

    def bulk_create_iter(self, iterable, batch_size=10000):
        """Bulk create supporting generators, returns number of created objects."""
        created = 0
        while True:
            objects = self.bulk_create(islice(iterable, batch_size))
            created += len(objects)
            if not objects:
                break
        return created

w9apscun

w9apscun4#

我也遇到了同样的问题,最终得到了这样的解决方案:

class BulkCreateManager(object):

    model = None
    chunk_size = None
    instances = None

    def __init__(self, model, chunk_size=None, *args):
        self.model = model
        self.chunk_size = chunk_size
        self.instances = []

    def append(self, instance):
        if self.chunk_size and len(self.instances) > self.chunk_size:
            self.create()
            self.instances = []

        self.instances.append(instance)

    def create(self):
        self.model.objects.bulk_create(self.instances)

instances = BulkCreateManager(Model, 23000)
for host in hosts:
    instance = ...
    instances.append(instance)

instances.create()

字符串

acruukt9

acruukt95#

也许这对某些人会有帮助,这是一个在Django中使用generators + banchsize的例子:

from itertools import islice
from my_app.models import MyModel

def create_data(data):
    bulk_create(MyModel, generator())

def bulk_create(model, generator, batch_size=10000):
    """
    Uses islice to call bulk_create on batches of
    Model objects from a generator.
    """
    while True:
        items = list(islice(generator, batch_size))
        if not items:
            break
        model.objects.bulk_create(items)

def generator(data):
    for row in data:
        yield MyModel(field1=data['field1'])

字符串
原始文章在这里-https://concisecoder.io/2019/04/19/avoid-memory-issues-with-djangos-bulk_create/

y4ekin9u

y4ekin9u6#

我只是想分享一个稍微不同的方法,这可能会帮助别人。减少内存依赖的第一件事是不要将整个数据集加载到内存中。如果日期来自文件,只需从文件中读取一组批量大小的记录,然后创建对象列表并将其写入数据库。然后清除该对象列表并创建下一批。这样就不会将整个数据集加载到记忆一次。
在我的例子中,我把整个数据加载到内存(列表)中,因为它不是太大,我不得不做一些其他的事情。所以我决定提取列表的一个子集(批处理),并一次将其写入数据库。

# MyModelObjectList is the list variable with all the data model objects
# batch_size is the number of records you want to write to DB in one transaction
def my_bulk_create(MyModel, MyModelObjectList, batch_size=1000):
    for x in range(0, len(MyModelObjectList), batch_size):
        MyModel.objects.bulk_create(MyModelObjectList[ x: x+batch_size ]) # Extracting a subset of the list

字符串
您可以使用islice()代替

def my_bulk_create(MyModel, MyModelObjectList, batch_size=1000):    
    for x in range(0, len(MyModelObjectList), batch_size):
        MyModel.objects.bulk_create(list(islice(MyModelObjectList, x, x+batch_size))


我猜这将从这段代码中删除无限循环

def my_bulk_create(MyModel, MyModelObjectList, batch_size=1000):    
    batchStart = 0
    while True:
        batch= list(islice(MyModelObjectList, batchStart, batchStart + batch_size))
        if not batch:
            break
        batchStart += batch_size # New start position for the next iteration
        MyModel.objects.bulk_create(batch)

**注意:**这里提供的代码片段,除了第一个,我没有亲自执行过,所以你可能需要调试一下。

相关问题