如何避免为我的python脚本对MongoDB进行多次查询

时间:2014-06-28 09:22:39

标签: python performance mongodb pymongo database-performance

帮我解决这个问题并改进我的脚本。这是一篇很长的帖子,请耐心等待。我正在编写一个小型网络应用程序,帮助用户维护他们感兴趣的产品的心愿单。我使用MongoDB来存储数据,使用Python Tornado作为服务器。

数据库中有两个集合,一个用于存储产品详细信息,另一个用于维护用户详细信息。每当用户添加新产品链接时,该URL将被添加到产品集合中,其对象ID将被添加到用户的文档中。它实际上是一个列表,它将被添加到其中。

因此,产品集合中的典型文档将是:

{
  _id: ObjectId("53a2bfcfa7603606c2765342"),
  name: "Nexus 7 from Google (7-Inch, 16 GB, Black)",
  url: "http://www.amazon.com/Nexus-Google-7-Inch-Black-Tablet/dp/B00DVFLJDS/",
  base_price: 222,
  current_price: 213.96,
}

用户的用户将是:

{
  _id: ObjectId("539c4adea760360886d7ef02"),
  email_id: "johnappleseed@apple.com",
  tracked_products: [
    ObjectId("53a2bfcfa7603606c2765342"),
    ObjectId("53a2d2ada7603606c2765344"),
    ObjectId("53a2d294a7603606c2765343")
  ]
}

现在我已经编写了一个每天运行一次的cron作业并更新了current_price。如果current_price低于base_price,我必须向用户发送提醒。这对我来说太棘手了。即使产品价格发生任何变化,我也希望发送电子邮件。

一种简单但效率低下的方法,检查价格,如果价格有变化,则在产品文档中添加新值。可能是alert: yes然后循环遍历所有用户,遍历跟踪产品的每个对象ID,如果与这些对象ID匹配的任何文档的警报字段为是,则将用户电子邮件添加到电子邮件队列。另一个重置警报的cron工作。代码将是

email_id_queue = []
for product in product.collection:
    # price changed? if yes, then update
    # add/update alert: yes
    # ...
    product.collection.save(product)

for user in users.collection:
    for product_id in user['tracked_products']:
        product = products_collection.find({'_id': product_id})
        if product.get('alert', None):
            email_id_queue.append(user['email_id'])
            break

# send emails to all of those who are in email_id_queue

我发现上面的实现效率不高,我相信可以有很多方法来改进它。现在我有以下解决方案,每当我找到价格发生变化的产品时,我会将其添加到单独的列表中。当我遍历用户时,我将检查它是否有任何这些对象ID不是。

email_id_queue = []
price_changed_products = set()

for product in product.collection:
    # price changed? if yes, then update
    # ...
    # ...
    price_changed_products.add(product['_id'])
    product.collection.save(product)

for user in users.collection:
    if set(user['tracked_products']) & price_changed_products:
        email_id_queue.append(user[email_id])

# send emails to all of those who are in email_id_queue

reddit上的用户建议我另类。我将维护另一个集合,可能被称为trackers。此集合将包含product_ids以及跟踪它的用户文档的对象ID。可能是这样的:

{
    _id: ObjectId("77a2bfcfa7603606c2765399"),
    product_id: ObjectId("53a2bfcfa7603606c2765342"),
    subscribers: ['user1@email.com', 'user2@email.com', 'user3@email.com']
}

现在每当价格发生变化时,我都会将该产品的订阅者列表添加到电子邮件队列中。这不需要任何额外的循环。每当我检查新价格时(循环的早期),我都可以这样做。

但是,如果我还要发送其他通知,例如Chrome提醒,iOS推送等,那么上面的工作就不会发生。所以我想让我们跟踪user_id而不是他们的电子邮件。类似的东西:

{
    _id: ObjectId("77a2bfcfa7603606c2765399"),
    product_id: ObjectId("53a2bfcfa7603606c2765342"),
    subscribers: [ObjectId("12a2bfcfa7603606c2765399"), ObjectId("14a2bfcfa7603606c2765399"), ObjectId("56a2bfcfa7603606c2765399")]
}

然后再次使用用户的对象ID查询数据库并发送通知。最后它将是:

user_ids_queue = set()

for product in product.collection:
    # price changed? if yes, then update
    # ...
    # ...
    # find the subscribers:
    product_document = db.trackers.find_one({'product_id': product['_id']})
    user_ids_queue.add(product_document['trackers'])
    product.collection.save(product)

# now send notifications to each of those users:

for user_id in user_ids_queue:
    user_document = db.users.find_one({'_id': user_id})
    # now send notifications:
    add_to_email_queue(user_document['email'])
    add_to_chrome_queue(user_document['chrome_id'])
    add_to_ios_queue(user_document['ios_push_id'])
    # etc

但我可以做得更好吗?任何帮助表示赞赏!谢谢!

0 个答案:

没有答案