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

标签 python google-app-engine

我有一个产品数据库,其中包含基于语言代码的产品、零件和每个零件的标签。

我遇到但尚未解决的问题是使用大量资源来获取不同的数据集并将它们合并到字典中以满足我的需求。

数据库中的产品基于一定类型(即颜色、尺寸)的多个部件。每个部分都有每种语言的标签。我为此创建了 4 个不同的模型。产品、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)

当完成的字典在内存缓存中时,它可以顺利工作,但是如果应用程序进入休眠状态,内存缓存不会重置吗?另外,我想向第一次用户显示页面(内存缓存为空),而不会造成巨大的延迟。

正如我上面所说,这只是少量的零件/产品。如果有 30 种产品,100 个零件,结果会怎样?

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

我知道这需要考虑很多,但我被困住了。我已经连续做了大约 12 个小时了。并且无法找出解决方案。

..弗雷德里克

编辑:

AppStats 屏幕截图 here .

据我所知,AppStats 中的查询接缝很好。只需要大约 200-400 毫秒。差别怎么会这么大?

编辑2:

我实现了 doound 的解决方案并添加了 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) 

结果好多了。我现在在不使用 memcache 的情况下大约需要 3000 毫秒,而在不使用 memcache 的情况下大约需要 700 毫秒。

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

最后但并非最不重要的一点是,有人知道为什么请求在生产服务器上花费的时间是 app_dev 的 10 倍吗?

编辑3: 我注意到没有 db.Model 被索引,这会有所不同吗?

编辑4: 在咨询了AppStats之后(并理解它,花了一些时间。看来大问题在于part.type.typeId,其中part.type是一个db.ReferenceProperty。以前应该见过它。也许解释得更好:)我'我会重新考虑那部分。然后回复您。

..弗雷德里克

最佳答案

一些简单的想法:

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

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

2) 可能只加载初始请求中的部分数据。然后使用 AJAX 技术根据需要加载其他数据。例如,首先返回产品信息,然后发出额外的 AJAX 请求来获取零件。

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

  • 问题:IN 查询会对您提供的每个参数执行一次 equals 查询。所以<strong>key</strong> IN self.defaultParts实际上是len(self.defaultParts)查询。
  • 可能的改进:尝试更多地对数据进行非规范化。具体来说,在每个部件上存储每个部件所使用的产品列表。您可以像这样构建您的零件模型:
    class ProductParts(db.Model):
        ...
        products = db.ListProperty(db.Key)  # product keys
        ...
  • Then you can do ONE query to per product instead of N queries per product. For example, you could do this:

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

  • The trade-off? You have to store more data in each ProductParts entity. Also, when you write a ProductParts entity, it will be a little slower because it will cause 1 row to be written in the index for each element in your list property. However, you stated that you only have 100 products so even if a part was used in every product the list still wouldn't be too big (Nick Johnson mentions here that you won't get in trouble until you try to index a list property with ~5,000 items).

Less critical improvement idea:

4) You can create the GqlQuery object ONCE and then reuse it. This isn't your main performance problem by any stretch, but it will help a little. Example:

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)

关于python - 从数据存储加载数据集并合并到单个字典中。资源问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2806760/

相关文章:

python - 我的数据集显示一个字符串,而它应该是一个大括号集/字典

google-app-engine - 使用 Google+ API 登录后获取电子邮件 ID/用户名

java - GWT(服务器端)可以多线程

python - Networkx 绘图标签部分在框外

python - 拆分列表中的字符串以查找和替换 python 中的元素

python - 如何在 Google App Engine 中获取当前英国时间

java - Java Google App Engine 在线程安全模式下的效率提高了多少?

java - 在数据存储中存储和检索多值属性

Python不写标题

python - 如何摆脱 Pandasplot() 的额外图例条目?