3

我刚刚开始为多租户 Web 应用程序设计ElasticSearch映射。在这个应用程序中,有站点 ID:s 和页面 ID:s。页面 ID:每个站点都是唯一的,并且是随机生成的。页面可以有子页面。

什么是最好的:

1)使用带有站点+页面ID的复合键:s?像这样:

"sitePageIdPath": "(siteID):(grandparent-page-ID).(parent-page-ID).(page-ID)"

或者:

2) 对站点 ID 和页面 ID 使用单独的字段?像这样:

"siteId": "(siteID)",
"pageIdPath": "(grandparent-page-ID).(parent-page-ID).(page-ID)"

?

我在想,如果我将站点 ID 和页面 ID 合并到一个字段中,那么 ElasticSearch 将需要处理该字段,这应该比使用两个字段更高效——无论是在索引时还是在搜索时?并且需要更少的存储空间。

但是,也许有一些我不知道的缺点?因此这个问题。

一些细节:1)我正在使用单个索引,并且我过度分配分片(100 个分片),正如使用“用户”数据流模式时所建议的那样。2) 我在 URL (ie &routing=site-ID) 中明确指定路由参数,而不是通过索引文档中的任何siteId字段。

7小时后更新:

1)所有查询都应按站点ID(即租户ID)过滤。如果我确实将站点 ID 与页面 ID 结合起来,我想/希望我可以使用前缀过滤器来过滤站点 ID。我想知道这是否会像在单个专用siteId字段上进行过滤一样快(例如,可以缓存结果)。

2) 示例查询:全文搜索。列出所有用户。列出所有页面。列出某个页面的所有子/后续页面。加载单个页面(通过_source)。

22小时后更新:

3)我可以按页面 ID 搜索,因为作为 ElasticSearch 的_id,我存储:(site-ID):(page-ID)。因此,页面 ID 作为pageIdPath的最后一个元素“隐藏”不是问题。我之前可能应该提到我有一个单独的页面 ID 字段,但我认为让我们保持简短的问题。

4)我index: not_analyzed用于这些 ID 字段。

4

2 回答 2

3

如果您使用 1 个字段,则在索引和搜索时会出现性能问题。我认为您错误地认为 1 提交会加快速度。

如果使用 1 个字段,您基本上有 2 个映射选择:

  1. 如果您使用默认映射,则字符串(siteID):(grandparent-page-ID).(parent-page-ID).(page-ID)将被分析器分解为 tokens (siteID) (grandparent-page-ID) (parent-page-ID) (page-ID)。现在,您的 id 就像一袋单词,当您希望它与 siteID 匹配时,术语或前缀过滤器可能会从 pageID 中找到匹配项。

  2. 如果您设置自己的分析器(我想知道您是否能想到这样做的好方法),首先想到的是关键字(或 not_analyzed)分析器。这会将字符串保留为一个标记,因此您不会丢失上下文。但是,现在使用前缀过滤器时性能会受到很大影响。想象一下,我将字符串索引"123.456.789"为一个标记(siteID、parentpageID.pageID)。我想通过 sideID = 123 过滤,所以我使用前缀过滤器。正如您在此处所读到的,此前缀过滤器实际上已扩展bool为数百个术语的查询,这些术语全部通过 OR 运算(123123112321233等...),当您可以更好地构建数据时,这会极大地浪费计算能力。

我敦促您阅读有关 lucene 的 PrefixQuery 及其工作原理的更多信息。

如果我是你,我会这样做。

映射

"properties": {
  "site_id": {
    "type": "string",
    "index": "not_analyzed" //keyword would also work here, they are basically the same
  },
  "parent_page_id": {
    "type": "string",
    "index": "not_analyzed"
  },
  "page_id": {
    "type": "string",
    "index": "not_analyzed"
  }<
  "page_content": {
    "type": "string",
    "index": "standard" //you may want to use snowball to enable stemming
  }
}

查询

在siteID“123”下文本搜索“elasticsearch tutorial”

"filtered": {
  "query": {
    "match": {
      "page_content": "elasticsearch tutorial"
    }
  },
  "filter": {
    "term": {
      "site_id": "123"
    }
  }
}

站点“123”下页面“456”的所有子页面

"filtered": {
  "query": {
    "match_all": {}
  },
  "filter": {
    "and": [
      {
        "term": {
          "site_id": "123"
        }
      },
      {
        "term": {
          "parent_page_id": "456"
        }
      }
  }
}
于 2013-07-28T18:32:41.370 回答
0

编辑:这个答案有问题,即可能BooleanQuery.TooManyClauses exceptions;请在原始答案之后查看下面的更新。/编辑

我认为可以将站点ID和页面ID结合起来,并在查询时使用[与站点ID匹配的前缀过滤器]。我在查询 DSL 文档中找到了这个信息:

一些过滤器已经产生了易于缓存的结果,缓存和不缓存它们的区别在于是否将结果放入缓存中。这些过滤器,包括 term、terms、prefix和 range过滤器

因此,我认为结合站点 ID 和页面 ID 应该可以提高性能。而且我想不出任何其他问题(请记住,仅通过页面 ID 查找是没有意义的,因为没有站点 ID,页面 ID 就没有任何意义。)


更新:

我猜反对票主要是1) 因为如果我合并(Site-ID):(Parent-page-ID):(Page-ID)到一个字段中会出现性能问题,然后尝试搜索页面 ID。但是页面 ID 在该_id字段中可用,即:(site-ID):(page-ID),因此这应该不是问题。(也就是说,我不只使用 1 个字段 - 我正在使用 2 个字段。)

对应于 Ramseykhalaf 查询的查询将是:

"filtered": {
  "query": {
    "match": {
      "page_content": "search phrase"
    }
  },
  "filter" : {
    "prefix" : {
      "_id" : "123:"    // site ID is "123"
    }
  }
}

和:

"filtered": {
  "query": {
    "match_all": {}
  },
  "filter": {
    "and": [{
      "prefix" : {
        "_id" : "123:"  // site ID is "123"
      }, {
      "prefix": {
        "pageIdPath": "456:789:"  // section and sub section IDs are 456:789
                               // (I think I'd never search for a *subsection* only,
                               // without also knowing the parent section ID)
      }
    }]
  }
}

(我将 sitePageIdPath 重命名 pageIdPath因为站点 ID 存储在_id中)


另一个 2) 否决的次要原因可能是(我直到现在才知道)前缀查询被分解为与具有指定前缀的所有术语匹配的布尔查询,在我的情况下,这些布尔查询可能包括真的非常多的术语,如果相关网站中真的有很多页面(可能有)或部分 ID(没有)。那么直接使用term查询会更快吗?并且不能导致太多子句异常(请参见下面的链接)。

有关 PrefixQuery 的更多信息,请参阅:
如何提高单个字符 PrefixQuery 的性能?
使用 Lucene:如果我进行前缀搜索,为什么会收到太多子句错误?

这种to-boolean-query转换显然不仅发生在前缀查询,而且也发生在范围查询中,请参阅例如帮助找出maxClauseCount设置为1024错误的原因和Lucene BooleanQuery.TooManyClauses docs“尝试时抛出添加多个 BooleanQuery.getMaxClauseCount() 子句。如果在搜索期间将PrefixQuery、FuzzyQuery、WildcardQuery 或 TermRangeQuery 扩展为多个术语,通常会发生这种情况“

于 2013-07-28T13:50:17.750 回答