对实体进行排序并过滤ListProperty,而不会产生爆炸索引

sys*_*out 5 python indexing google-app-engine explode google-cloud-datastore

我正在开发一个简单的博客/书签平台,我想添加一个标签,探险家/向下钻取功能点菜美味,以允许用户过滤帖子指定特定标签的列表.

像这样的东西: 在此输入图像描述

使用此简化模型在数据存储区中表示帖子:

class Post(db.Model):
    title = db.StringProperty(required = True)
    link = db.LinkProperty(required = True)
    description = db.StringProperty(required = True)
    tags = db.ListProperty(str)
    created = db.DateTimeProperty(required = True, auto_now_add = True)
Run Code Online (Sandbox Code Playgroud)

Post的标签存储在ListProperty中,为了检索标记有特定标签列表的帖子列表,Post模型公开了以下静态方法:

@staticmethod
def get_posts(limit, offset, tags_filter = []):
        posts = Post.all()
        for tag in tags_filter:
          if tag:
              posts.filter('tags', tag)
        return posts.fetch(limit = limit, offset = offset)
Run Code Online (Sandbox Code Playgroud)

虽然我没有太多强调,但效果很好.

当我尝试向方法添加"排序"顺序get_posts以保持按"-created"日期排序结果时,问题就出现了:

@staticmethod
def get_posts(limit, offset, tags_filter = []):
        posts = Post.all()
        for tag in tags_filter:
          if tag:
              posts.filter('tags', tag)
        posts.order("-created")
        return posts.fetch(limit = limit, offset = offset)
Run Code Online (Sandbox Code Playgroud)

排序顺序为每个标签添加一个索引以进行过滤,从而导致可怕的爆炸索引问题.
使事情变得更复杂的最后一件事是该get_posts方法应该提供一些分页机制.

你知道任何策略/想法/解决方法/黑客来解决这个问题吗?

Jam*_*inn 3

涉及键的查询使用索引,就像涉及属性的查询一样。在与属性相同的情况下,对键的查询需要自定义索引,但有几个例外:不等式过滤器或的升序排序不需要自定义索引,但 Entity.KEY_RESERVED_PROPERTY_ key _ 的降序排序需要自定义索引。

因此,使用可排序的日期字符串作为实体的主键:

class Post(db.Model):
    title = db.StringProperty(required = True)
    link = db.LinkProperty(required = True)
    description = db.StringProperty(required = True)
    tags = db.ListProperty(str)
    created = db.DateTimeProperty(required = True, auto_now_add = True)

    @classmethod
    def create(*args, **kw):
         kw.update(dict(key_name=inverse_millisecond_str() + disambig_chars()))
         return Post(*args, **kw)
Run Code Online (Sandbox Code Playgroud)

...

def inverse_microsecond_str(): #gives string of 8 characters from ascii 23 to 'z' which sorts in reverse temporal order
    t = datetime.datetime.now()
    inv_us = int(1e16 - (time.mktime(t.timetuple()) * 1e6 + t.microsecond)) #no y2k for >100 yrs
    base_100_chars = []
    while inv_us:
        digit, inv_us = inv_us % 100, inv_us / 100
        base_100_str = [chr(23 + digit)] + base_100_chars
    return "".join(base_100_chars)
Run Code Online (Sandbox Code Playgroud)

现在,您甚至不必在查询中包含排序顺序,尽管按键显式排序不会有什么坏处。

要记住的事情:

  • 除非您对所有帖子使用此处的“创建”,否则这将不起作用。
  • 您必须迁移旧数据
  • 没有祖先允许。
  • 每个索引都会存储一次键,因此值得保持简短;这就是为什么我要进行上面的 base-100 编码。
  • 由于可能发生按键冲突,这并不是 100% 可靠。上面的代码,没有 disambig_chars,名义上给出了事务之间微秒数的可靠性,所以如果你在高峰时间每秒有 10 个帖子,它会失败 1/100,000。然而,对于可能的应用程序引擎时钟滴答问题,我会削减几个数量级,所以我实际上只相信它的 1/1000。如果这还不够好,请添加 disambig_chars;如果您需要 100% 的可靠性,那么您可能不应该使用应用程序引擎,但我想您可以在 save() 上包含处理按键冲突的逻辑。