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

Python 从数据存储加载数据集并合并到单个字典中。资源问题,python,google-app-engine,Python,Google App Engine,我有一个productdatabase,其中包含基于langcodes的每个零件的产品、零件和标签 我现在面临的问题是,需要大量的资源来获取不同的数据集,并将它们合并成一个dict来满足我的需要 数据库中的产品基于特定类型(即颜色、尺寸)的多个零件。每个部分都有一个针对每种语言的标签。我为此创建了4种不同的模型。产品、产品零件、产品零件类型和产品零件标签 我已经把它的范围缩小到了大约10行代码,这些代码可以用来生成问题。到目前为止,我有3种产品、3种类型、每种类型3个部件和2种语言。该请求需要5

我有一个productdatabase,其中包含基于langcodes的每个零件的产品、零件和标签

我现在面临的问题是,需要大量的资源来获取不同的数据集,并将它们合并成一个dict来满足我的需要

数据库中的产品基于特定类型(即颜色、尺寸)的多个零件。每个部分都有一个针对每种语言的标签。我为此创建了4种不同的模型。产品、产品零件、产品零件类型和产品零件标签

我已经把它的范围缩小到了大约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)中。零件的所有标签也是如此

我之所以需要复杂的dict,是因为我想显示产品的所有数据及其默认部分,并显示可选部分的选择器

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不是会重置吗?另外,我想在没有巨大延迟的情况下为第一次使用该页面的用户(memcache empty)显示该页面

正如我上面所说,这只是一小部分零件/产品。如果是30个产品,100个零件,结果会是什么

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

我知道这很难接受,但我被卡住了。我已经连续做了12个小时了。也找不出解决办法

…弗雷德里克

编辑:

AppStats屏幕截图

据我所知,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毫秒没有memcache

我仍然有点担心3000ms,在本地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查询对您提供的每个参数执行一个等于查询的操作。因此,self.defaultParts中的
    键实际上执行
    len(self.defaultParts)
    查询
  • 可能的改进:尝试更多地去规范化您的数据。具体来说,存储每个零件上使用的产品列表。您可以这样构造零件模型:
您还应该使用,这样您就可以确切地了解为什么您的请求花费了这么长时间。你甚至可以考虑发布关于你的请求和你的帖子的AppStas信息的截图。
如果您重新编写代码,则代码可能是这样的:以较少的数据存储往返次数获取数据(这些更改基于上面的想法1、3和4)


需要注意的一个重要事实是
查询中的
(以及
!=
查询)会导致在后台生成多个子查询,并且子查询的数量限制为30个

因此,您的
ProductParts.gql('WHERE uu-key\uuu-IN:key',key=self.defaultParts)
query实际上会在后台生成
len(self.defaultParts)
子查询,如果
len(self.defaultParts)
大于30,则会失败

以下是网站的相关部分:

注意:中的
=操作员在幕后使用多个查询。例如,
中的
操作符为列表中的每个项目执行单独的底层数据存储查询。返回的实体是所有底层数据存储查询的叉积结果,并且是重复数据消除的。任何单个GQL查询最多允许30个数据存储查询


你可以试着安装你的应用程序,看看还有什么地方它可能会慢下来。

我认为问题在于设计:当框架特别讨厌的时候,想要在memcache中构造一个关系连接表

GAE会把你的工作丢掉,因为这需要很长时间,但你一开始就不应该这么做。不幸的是,我自己也是一个同性恋者,所以我不能具体说明应该怎么做。

两者都是免费的 class ProductParts(db.Model): ... products = db.ListProperty(db.Key) # product keys ...
GQL_PROD_PART_BY_KEYS = ProductParts.gql('WHERE __key__ IN :1')
@property
def defaultPartsData(self):
    return GQL_PROD_PART_BY_KEYS.bind(self.defaultParts)
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)