代码之家  ›  专栏  ›  技术社区  ›  fredrik

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

  •  1
  • fredrik  · 技术社区  · 14 年前

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

    我现在遇到的问题是,我还没有找到足够的资源来获取不同的数据集,并将它们合并到一个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获取与当前语言代码匹配的所有标签。

    产品的所有部分都存储在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为空),但没有巨大的延迟。

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

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

    我知道这很难接受,但我被困住了。我已经连续12个小时这样做了。无法找到解决方案。

    弗雷德里克

    编辑:

    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毫秒的内存缓存和大约700毫秒的内存缓存。

    我仍然担心3000毫秒,在本地应用程序开发服务器上,每次重新加载时memcache都会被填满。难道不应该把所有的东西都放进去然后从里面读吗?

    最后但同样重要的是,是否有人知道为什么在应用程序开发的生产服务器上,请求需要10倍的时间?

    编辑3: 我注意到数据库模型的非索引,这会有区别吗?

    编辑4: 在咨询了AppStats(并了解它之后,花了一些时间。大问题在于part.type.typeid,其中part.type是db.referenceproperty。应该以前见过。也许能更好地解释一下:)我会重新考虑那部分。回到你身边。

    弗雷德里克

    3 回复  |  直到 14 年前
        1
  •  2
  •   Community Egal    7 年前

    一些简单的想法:

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

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

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

    3)正如威尔指出的, IN 查询对每个参数执行一个查询。

    • 问题 :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实体时,它将慢一点,因为它将导致在索引中写入1行 每个 列表属性中的元素。但是,你说你只有100种产品,所以即使在每种产品中使用了一个部件,列表也不会太大(尼克·约翰逊提到 here 除非您尝试用~5000个项目对列表属性进行索引,否则不会遇到麻烦。

    不太重要的改进想法:

    4)您可以创建一次gqlquery对象,然后重用它。这不是你的主要性能问题,但它会有一点帮助。例子:

    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)
    
        2
  •  1
  •   Will McCutchen    14 年前

    需要注意的一个重要事实是 IN 查询(连同 != 查询)导致在后台生成多个子查询,并且限制30个子查询。

    所以你的 ProductParts.gql('WHERE __key__ IN :key', key = self.defaultParts) 查询将实际生成 len(self.defaultParts) 后台的子查询,如果 len(自身默认部件) 大于30。

    这是 GQL Reference :

    注: 这个 != 操作员在后台使用多个查询。例如, 运算符对列表中的每个项执行单独的基础数据存储查询。返回的实体是所有基础数据存储查询的交叉乘积的结果,并且是重复数据消除的。任何单个GQL查询最多允许30个数据存储查询。

    您可以尝试安装 AppStats 让你的应用程序看看还有什么地方会慢下来。

        3
  •  0
  •   msw    14 年前

    我认为这个问题是设计中的一个问题:当框架特别厌恶这个问题时,我希望在memcache中构造一个关系联接表。

    盖伊会丢掉你的工作,因为这需要很长时间,但你不应该一开始就这么做。我自己也是盖蒂罗,所以我不能具体说明应该怎么做,不幸的是。