从数据存储区加载数据集并合并到单个字典中。资源问题

时间:2010-05-10 22:00:24

标签: python google-app-engine

我有一个产品数据库,其中包含基于langcodes的每个部件的产品,零件和标签。

我遇到的问题并没有解决的问题是用于获取不同数据集并将它们合并到符合我需要的字典中的大量资源。

数据库中的产品基于某些类型的部件(即颜色,尺寸)。每个部分都有每种语言的标签。我为此创建了4种不同的模型。 Products,ProductParts,ProductPartTypes和ProductPartLabels。

我已将其缩小到大约10行代码,这些代码会产生问题。截至目前,我有3种产品,3种类型,每种类型3种,2种语言。并且该请求需要5500毫秒才能生成。

for product in productData:
        productDict = {}
        typeDict = {}
        productDict['productName'] = product.name

        cache_key = 'productparts_%s' % (slugify(product.key()))
        partData = memcache.get(cache_key)

        if not partData:
            for type in typeData:
                typeDict[type.typeId] = { 'default' : '', 'optional' : [] }
            ## Start of problem lines ##
            for defaultPart in product.defaultPartsData:
                for label in labelsForLangCode:
                    if label.key() in defaultPart.partLabelList:
                        typeDict[defaultPart.type.typeId]['default'] = label.partLangLabel

            for optionalPart in product.optionalPartsData:
                for label in labelsForLangCode:
                    if label.key() in optionalPart.partLabelList:
                        typeDict[optionalPart.type.typeId]['optional'].append(label.partLangLabel)
            ## end problem lines ##
            memcache.add(cache_key, typeDict, 500)
            partData = memcache.get(cache_key)

        productDict['parts'] = partData    
        productList.append(productDict)

我想问题在于for循环的数量太多,并且必须反复遍历相同的数据。 labelForLangCode从ProductPartLabels获取与当前langCode匹配的所有标签。

产品的所有部件都存储在db.ListProperty(db.key)中。对于零件的所有标签也是如此。

我需要某些复杂词典的原因是我想要显示产品的所有数据及其默认部分,并显示可选项的选择器。

defaultPartsData和optionaPartsData是产品模型中的属性,如下所示:

@property
def defaultPartsData(self):
    return ProductParts.gql('WHERE __key__ IN :key', key = self.defaultParts)

@property
def optionalPartsData(self):
    return ProductParts.gql('WHERE __key__ IN :key', key = self.optionalParts)

当完成的dict在memcache中时,它可以顺利运行,但如果应用程序进入休眠模式,则不会重置内存缓存吗?此外,我想首次向用户显示页面(memcache为空),但没有出现大的延迟。

正如我上面所说,这只是少量零件/产品。当它有30个产品100个零件时,结果会是什么?

是否有一个解决方案可以创建一个计划任务,每小时将其缓存在内存缓存中?这有效吗?

我知道这很有吸引力,但我被困住了。我已经连续约12个小时了。并且无法找到解决方案。

.. fredrik

编辑:

AppStats屏幕截图here

从我可以阅读的内容中,查询在AppStats中很好。只需要大约200-400毫秒。差异怎么可能那么大?

编辑2:

我实施了dound的解决方案并添加了abit。现在它看起来像这样:

langCode = 'en'
    typeData = Products.ProductPartTypes.all()
    productData = Products.Product.all()
    labelsForLangCode = Products.ProductPartLabels.gql('WHERE partLangCode = :langCode', langCode = langCode)
    productList = []

    label_cache_key = 'productpartslabels_%s' % (slugify(langCode))
    labelData = memcache.get(label_cache_key)

    if labelData is None:
        langDict = {}
        for langLabel in labelsForLangCode:
            langDict[str(langLabel.key())] = langLabel.partLangLabel

        memcache.add(label_cache_key, langDict, 500)
        labelData = memcache.get(label_cache_key)

    GQL_PARTS_BY_PRODUCT = Products.ProductParts.gql('WHERE products = :1')
    for product in productData:
        productDict = {}
        typeDict = {}
        productDict['productName'] = product.name

        cache_key = 'productparts_%s' % (slugify(product.key()))
        partData = memcache.get(cache_key)

        if partData is None:
            for type in typeData:
                typeDict[type.typeId] = { 'default' : '', 'optional' : [] }

            GQL_PARTS_BY_PRODUCT.bind(product)
            parts = GQL_PARTS_BY_PRODUCT.fetch(1000)
            for part in parts:
                for lb in part.partLabelList:
                    if str(lb) in labelData:
                        label = labelData[str(lb)]
                        break

                if part.key() in product.defaultParts:
                    typeDict[part.type.typeId]['default'] = label
                elif part.key() in product.optionalParts:
                    typeDict[part.type.typeId]['optional'].append(label)

            memcache.add(cache_key, typeDict, 500)
            partData = memcache.get(cache_key)

        productDict['parts'] = partData    
        productList.append(productDict) 

结果好多了。我现在有大约3000毫秒没有memcache和大约700毫秒。

我仍然担心3000毫秒,并且在本地app_dev服务器上,memcache会在每次重新加载时被填满。不应该把所有东西放在那里,然后从中读取?

最后但并非最不重要的是,是否有人知道为什么请求在生产服务器上的app_dev大约需要10倍?

编辑3: 我注意到db.Model的非索引,这可能会产生差异吗?

编辑4: 在咨询了AppStats之后(理解它,花了一些时间。它找到了大问题在于part.type.typeId,其中part.type是db.ReferenceProperty。之前应该已经看过了。并且可能更好地解释:)我'我会重新考虑这一部分。并回复你。

.. fredrik

3 个答案:

答案 0 :(得分:2)

一些简单的想法:

1)由于您需要所有结果,而不是像您一样执行for循环,而是显式调用fetch()以继续并立即获取所有结果。否则,for循环可能会导致对数据存储区的多次查询,因为它只能同时获取这么多项目。例如,也许您可​​以尝试:

return ProductParts.gql('WHERE __key__ IN :key', key = self.defaultParts).fetch(1000)

2)也许只在初始请求中加载部分数据。然后使用AJAX技术根据需要加载其他数据。例如,首先返回产品信息,然后发出其他AJAX请求以获取部件。

3)像Will指出的那样,IN个查询执行一个查询PER参数。

  • 问题:IN查询会为您提供的每个参数执行一次等于查询。因此,key IN self.defaultParts实际上会len(self.defaultParts)次查询。
  • 可能的改进:尝试更多地对数据进行非规范化。具体而言,在每个部件上存储每个部件使用的产品列表。您可以像这样构建零件模型:
    class ProductParts(db.Model):
        ...
        products = db.ListProperty(db.Key)  # product keys
        ...
  • 然后,您可以对每个产品执行一次查询,而不是每个产品的N个查询。例如,您可以这样做:

parts = ProductParts.all().filter("products =", product).fetch(1000)

  • 权衡?您必须在每个ProductParts实体中存储更多数据。此外,当您编写ProductParts实体时,它会稍慢,因为它会导致在list属性中每个元素的索引中写入1行。但是,你声明你只有100种产品,所以即使每种产品都使用了一个部件,清单仍然不会太大(尼克约翰逊提到here你不会遇到麻烦,直到你试图索引一个包含约5,000个项目的列表属性。

不太重要的改进想法:

4)您可以创建GqlQuery对象ONCE,然后重用它。这不是你的主要性能问题,但它会有所帮助。例如:

GQL_PROD_PART_BY_KEYS = ProductParts.gql('WHERE __key__ IN :1')
@property
def defaultPartsData(self):
    return GQL_PROD_PART_BY_KEYS.bind(self.defaultParts)

您还应该使用AppStats,这样您就可以确切了解您的请求为何需要这么长时间。您甚至可以考虑发布有关您的请求的appstats信息的屏幕截图以及您的帖子。


如果您重新编写代码,那么代码可能会像往返数据存储区的往返次数更少(这些更改基于上面的思路#1,#3和#4)。

GQL_PARTS_BY_PRODUCT = ProductParts.gql('WHERE products = :1')
for product in productData:
    productDict = {}
    typeDict = {}
    productDict['productName'] = product.name

    cache_key = 'productparts_%s' % (slugify(product.key()))
    partData = memcache.get(cache_key)

    if not partData:
        for type in typeData:
            typeDict[type.typeId] = { 'default' : '', 'optional' : [] }

        # here's a new approach that does just ONE datastore query (for each product)
        GQL_PARTS_BY_PRODUCT.bind(product)
        parts = GQL_PARTS_BY_PRODUCT.fetch(1000)
        for part in parts:
            if part.key() in self.defaultParts:
                part_type = 'default'
            else:
                part_type = 'optional'

            for label in labelsForLangCode:
                if label.key() in defaultPart.partLabelList:
                    typeDict[defaultPart.type.typeId][part_type] = label.partLangLabel
        # (end new code)
        memcache.add(cache_key, typeDict, 500)
        partData = memcache.get(cache_key)

    productDict['parts'] = partData    
    productList.append(productDict)

答案 1 :(得分:1)

需要注意的一件重要事情是IN查询(以及!=查询)导致在幕后产生多个子查询,并且限制了30个子查询。

因此,您的ProductParts.gql('WHERE __key__ IN :key', key = self.defaultParts)查询将在幕后实际生成len(self.defaultParts)子查询,如果len(self.defaultParts)大于30,则会失败。

以下是GQL Reference的相关部分:

  

注意: IN!=运营商在幕后使用多个查询。例如,IN运算符为列表中的每个项执行单独的基础数据存储区查询。返回的实体是所有基础数​​据存储区查询的交叉产品的结果,并且是重复数据删除的。任何单个GQL查询最多允许30个数据存储区查询。

您可以尝试为您的应用安装AppStats,看看它可能在哪些方面放慢速度。

答案 2 :(得分:0)

我认为问题是设计问题:当框架特别憎恶时,想要在memcache中构建关系连接表。

GAE会把你的工作扔掉,因为它花了太长时间,但你不应该首先做这件事。我自己就是GAE tyro,所以不幸的是我无法说明应该怎么做。