python 使用MongoEngine批量更新文档的最有效方法

kcrjzv8t  于 2023-04-19  发布在  Python
关注(0)|答案(2)|浏览(293)

bounty已结束,回答此问题可获得+100声望奖励,奖励宽限期8小时后结束,Tsadoq希望引起更多关注此问题:一个可行的解决方案或证明这是最好的解决方案。

因此,我有一个文档集合(例如Person),其结构如下:

class Person(Document):
    name = StringField(max_length=200, required=True)
    nationality = StringField(max_length=200, required=True)
    earning = ListField(IntField())

当我保存文档时,我只输入namenationality字段,因为这是信息。
然后,我想不时地更新特定国籍的每个人的收入。让我们想象一下,有一些公式允许我计算收入字段(例如,我查询一些神奇的API称为EarningAPI,它返回一个人的earning,给定其name)。
要更新它们,我会做一些类似的事情:

japanese_people = Person.objects(Q(nationality='Japanese'))).all()
for japanese_person in japanese_people:
    japanese_person.earning.append(EarningAPI(japanese_person.name))

Person.objects.insert(japanese_people, load_bulk=False)

EarningAPI也可以批量工作,这样我就可以给予一个名字列表,它会返回一个收入列表(每个名字一个)。这种方法更快,更便宜。
一个接一个的方法正确吗?利用批次的最佳方法是什么?
谢谢

pbpqsu0x

pbpqsu0x1#

使用Mongoengine bulk update without objects.update()的方法:

from pymongo import UpdateOne
from mongoengine import Document, ValidationError

class Person(Document):
    name = StringField(max_length=200, required=True)
    nationality = StringField(max_length=200, required=True)
    earning = ListField(IntField())

japanese_people = Person.objects(Q(nationality='Japanese')).all()

japanese_ids = [person.id for person in japanese_people]
earnings = EarningAPI(japanese_ids) 
# I'm assuming it takes a list of id's as input and returns a list of earnings. 

bulk_operatons = [
    UpdateOne(
        {'_id': j_id},
        {'$set': {'earning': earn}},
        upsert=True
    ),
    for j_id, earn in zip(japanese_ids, earnings)
]

result = Person._get_collection().bulk_write(bulk_operations, ordered=False)

我不能确定这是否比一个接一个的方法更快,因为我没有访问你的神奇API来进行基准测试,但这应该是通过批处理完成的方式。

2vuwiymt

2vuwiymt2#

一个接一个地进行API调用和更新是正确的方法,是的,但是批量进行也是正确的。两种方法都有其优点和缺点,即一个接一个的方法需要更简单的代码,更容易阅读和维护,以换取更慢的性能,而批处理方法将更加复杂和难以编写,但将通过减少API请求开销来提高性能(对于批量大小为n的情况,您的API请求开销将减少到逐个方法开销的1/n左右)。
你应该采取哪种方法取决于很多因素,包括你的问题集的大小(考虑到问题的大小是否会随着时间的推移而增加以及增加多少),您从中获取数据的API是否有配额或速率限制,您的应用程序可以同时处理多少数据,以及可能还有许多其他因素。通过考虑这些因素,您可以确定是否需要执行批处理调用以及应该使用的批处理大小。
一个简单的伪代码示例如下所示:

people = Person.get({nationality: 'Japanese'})
for(i = 0; i < people.length; i += batch_size) {
    people_batch = []
    names = []
    for(j = i; j < min(i + batch_size, people.length); j++) {
        people_batch.append(people[j])
        names.append(people[j].name)
    }

    earnings = EarningAPI(names)

    for(k = 0; k < people_batch.length; k++) {
        people_batch[k].earning.append(earnings[k])
    }

    Person.update(people_batch)
}

上面的例子是一个更通用的解决方案,它可以处理更大的数据量。还要注意,批量大小为1的代码与逐个代码的功能大致相同。
如果你的数据集足够小,那么你可以简单地批量拉取API数据,并同时更新所有Person条目:

people = Person.get({nationality: 'Japanese'})
for(i = 0; i < people.length; i += batch_size) {
    names = []
    for(j = i; j < min(i + batch_size, people.length); j++) {
        names.append(people[j].name)
    }

    earnings = EarningAPI(names)

    for(k = 0; k < names.length; k++) {
        people[i + k].earning.append(earnings[k])
    }
}

Person.update(people)

由于我不精通Python,而且问题中有太多未知数,我无法编写特定于Python的解决方案,但我确实希望上述解决方案结构足以帮助您实现最适合您的用例的解决方案。

相关问题