优化过滤器和 API 脚本以加快 Shotgun 站点速度

Shotgun 非常灵活,支持通过过滤器面板自定义页面,以及开发基于 API 的复杂集成脚本。借助这种灵活性,可以轻松地创建直观上很合理但实际上性能低下且会占用大量系统资源的复杂或高级过滤器

本文档的目的是帮助 Shotgun 用户(特别是编写 API 脚本的用户)了解如何创建快速高效的复杂过滤器。大部分编写良好的过滤器只需几分之一秒即可完成操作。然而,低效过滤器虽然返回的数据完全相同,但可能需要几秒甚至几分钟才能完成操作。

为了优化过滤器性能,我们提供以下建议:

  • 最大程度减少数据库必须加载和处理的表行数。
    • 最有效的方法是,对主实体表列使用过滤器,而不是属于其他表的字段,例如,链接字段、多实体字段、标记和 URL 字段。
  • 避免使用文本匹配过滤器,因为它们速度缓慢,本身不适用于此目的。

慢速过滤器示例

过滤器示例

以下原因导致上述版本页面过滤器示例的执行速度很缓慢:

  1. 所有过滤器都是“文本匹配”过滤器(“名称包含”(name contains)、“包含”(contains)、“开始于”(starts with)等),它们会降低数据库的评估速度。
  2. 这些文本匹配过滤器需要检查的版本数量非常巨大(即所有版本)!
  3. 许多过滤器位于链接字段上,因此,数据库必须从其他表中提取数据来对过滤器进行评估。

此外,由于版本表通常是增长最快的表,因此,随着时间的推移,此过滤器将会变得越来越慢。

为了提高复杂过滤器的性能,可以添加一些能够更有效地将大多数版本排除在考虑范围之外的过滤器。这样可以确保很多版本不需要对慢速文本匹配查询进行评估。

适合的设置将取决于您的工作流。如果您的工作流很大程度上依赖于文本匹配过滤器或链接字段过滤器,您可能会发现需要对工作流进行调整,以便更好地利用这些过滤器来有效过滤出版本或其他实体。有效更改工作流的一个示例是更好地利用状态字段。

过滤器改进示例

改进的过滤器示例

现在,我们已经新添加了三个更高效的过滤器。这三个过滤器可以消除工作室的大多数版本,从而改善此复杂过滤器,因此,对慢速模式匹配过滤器和链接字段过滤器的评估将在版本数量大为减少的情况下进行。

  • 项目(Project):我们已经使用“是”(is)过滤器对项目进行了明确限制。这将有效消除其他所有过去及当前项目中的所有版本,对于具有很多项目的工作室而言,将会产生很大的影响。这可能需要更改工作流才能使项目过滤器保持最新,因为该页面的用户常常收集所有 VFX 剪辑版本,而无论项目为何。
  • 状态(Status):最好在每个版本得到审核后将其状态从“Pending Review”更新为“Viewed”(已查看)。这样可以轻松将版本页面过滤为只有少数相关的新版本。
  • 创建日期(Date Created)(或“更新日期”(Date Updated)):所有实体都具有这些字段,它们通常可以有效地过滤页面内容。

过滤器性能详细信息

1. 文本匹配过滤器速度缓慢。

文本匹配过滤器速度缓慢的原因是,数据库必须对结果中的每个表行执行复杂的测试。没有优化策略可用于这些过滤器类型。

本准则并不表示您不应该使用文本匹配过滤器,只是表示除了文本匹配过滤器之外,您还必须使用其他更高效的过滤器类型来减少结果数量(这是第二项准则,详细说明见下文)。其他这些过滤器将确保您的文本匹配过滤器应用于较少的表行,从而消耗较少的资源。

以下是速度本身就很慢的文本匹配过滤器类型:

  • 包含(contains)
  • 不包含(does not contain) (not_contains)
  • 开始于(starts with) (starts_with)
  • 结束于(ends with) (ends_with)
  • 名称包含(name contains) (name_contains)
  • 名称不包含(name doesn't contain) (name_not_contains)

在以下情况下,文本匹配查询尤其缓慢:

  • 它们是实体搜索上的唯一过滤器
  • 它们是“OR”(“任一”)过滤器组的一部分

因此,与使用其他高效过滤器类型来减少结果数量的复杂过滤器相比,其过滤结果的速度可能要慢几百或几千倍。

如果您目前依赖于性能低下的 API 脚本的文本匹配过滤器,可能需要阅读“更改集成方法以避免文本匹配”部分。

2. 使用一个简单高效的过滤器处理实体自己的字段,以减少返回的实体数量。

这是确保复杂过滤器良好执行的最重要原则。

对实体自己的字段使用高效过滤器时,数据库可以立即快速地将实体的大多数表行排除在考虑范围之外。因此,在后续步骤中,数据库需要处理的数据将会减少。复杂过滤器的所有其余部分(例如,其他过滤器、排序或分组)也将因此而变得更快,从而为您带来更多的好处。

缩小最终结果范围可以减少响应 API 脚本或 Shotgun 页面所需的数据格式设置和网络资源,从而进一步提高性能。

哪些类型的过滤器可以这样有效地提高性能?

  1. 有效“减少”过滤器必须能够大幅减少需要考虑的实体数量。
    如果有 100 万个版本,对只有 8,000 个版本的项目进行过滤应该很有用。另一方面,对包含 800,000 个版本的状态(如“已查看”(viewed))进行过滤不太可能提高性能。
  2. 过滤器必须位于 AND(“所有”)过滤器组中。
    请注意,在 python API 中,过滤器的顶层条件阵列是一个 AND 组。在以下示例中,“entity”上的过滤器有可能会大量减少行数:
    EFFICIENT_filters = [[ "entity", "is", { "type": "Asset", "id": 9 } ]]

    但是,如果该过滤器位于其他过滤器组的一个子组中,它将不得再位于更高级别的任何 OR(“任一”)过滤器组内。在以下示例中,“entity”上的过滤器可能不会提高性能:
    SLOW_filters = [ 
      {
        "filter_operator":"or",
        "filters":[
          [ "code", "contains", "e" ],
          [ "entity", "is", { "type": "Asset", "id": 9 } ]
        ]
      }
    ]

    位于任何级别 OR 组内的过滤器不一定会应用于所有表行,因此,不能确保需要考虑的行数会减少。(有关例外,请参见下面的“高级性能”部分。)
  3. 过滤器字段不是链接字段。
    链接字段上的过滤器不会快速地消除行,因为它们需要数据库查找和评估其他实体表中的行。您可能需要链接字段上的过滤器,但也可以在属于实体本身的字段上使用其他过滤器,以便有效地消除行。(有关例外,请参见下面的“高级性能”部分。)
  4. 过滤器字段的数据类型不是多实体或标记列表。
    多实体和标记字段的速度不快,因为它们类似于链接字段。这些字段上的过滤器需要数据库评估其他表中的行,因此,不会有效地消除主表中的行。
    对多实体字段或标记字段执行过滤没有任何问题,但为了获得良好的性能,您需要使用额外的过滤器才能更有效地减少实体数量。
    请注意,与多实体字段不同的是,单实体字段的速度很快,是一个不错的“减少”过滤器。这是因为它们属于主实体表。
  5. 过滤器不是文本匹配过滤器类型。
    文本匹配过滤器速度缓慢,无法通过数据库进行很好的优化。
    我们所有其他的过滤器类型(如“是”(is)、“不是”(is not)、“大于”(greater than)、“晚于”(is after)、“在接下来 __ 周”(in the next __ weeks)等)相对较为高效,前提是过滤器可以显著消除行数。

示例

如果站点具有大量版本,以下过滤器的执行速度可能很缓慢:

SLOW_filters = [
  {
    "filter_operator":"or",
    "filters":[
      [ "code", "contains", "_rev_" ],
      [ "code", "contains", "_redo_" ]
    ]
  }
]

有一些可以明显消除行的备选方案就是对项目或日期范围进行限制。我们将在下面的示例中尝试这两项操作。

请注意,顶层过滤器是一个 OR(“任一”)过滤器。请确保新的减少过滤器不在 OR 过滤器组之内。

EFFICIENT_filters = [
  [ "project", "is", { "type": "Project", "id": 49 } ],
  [ "updated_at", "in_last", [ 1, "WEEK" ] ],
  {
    "filter_operator":"or",
    "filters":[
      [ "code", "contains", "_rev_" ],
      [ "code", "contains", "_redo_" ]
    ]
  }
]

通过数据库索引获益

有些字段可以提供特别高效的过滤效果,因为它们会在数据库中建立索引。借助索引字段上的过滤器,数据库可以通过查询索引字段值的有效哈希索引来确定需要考虑的行。

注意:除非您遵守提供的其他过滤器准则,否则,使用索引字段绝对没有任何好处。目标是有效地减少处理的行数,使用索引字段将有所帮助。

数据库索引可以提高从数据库读取数据的性能,但会降低向数据库写入数据的速度,因为每次写入时都必须更新索引。因此,Shotgun 不会为每个表中的每个字段建立索引。

默认情况下,对于每个实体类型,Shotgun 会为以下常用字段建立索引:

  • ID (id)
  • 项目(Project) (project)
  • 状态(Status) (sg_status_list)
  • 创建日期(Date Created) (created_at)
  • 更新日期(Date Updated) (updated_at)

如果正向过滤器(例如,“是”(is)或“大于”(greater than))使用其中一个字段,则与非索引字段上的过滤器相比,它可以更快地消除需要考虑的行数,因而可以显著提高性能。

请注意,负向过滤器(例如“不是”(is not))通常不支持数据库使用索引。但是,负过滤器仍可用来提高性能,前提是它们能够显著减少必须处理的行数。

索引有效性的一个示例是,我们的 API 事件轮询脚本通过对事件日志条目 ID 字段使用过滤器来将事件日志条目表行仅限于少量新行(如“ID”,“大于”(is greater than),“N”)。对于非常大的客户端数据库,该过滤器的执行速度非常快,即使事件日志条目表达到最大时也是如此。

但是,为了确定何时使用索引或使用哪个索引,数据库需要使用为每个索引保留的复杂启发法和统计信息。因此,对其中一个索引字段使用过滤器时,很难预测性能何时能够得到提升。

因此,请务必提供一种高效过滤器,以显著减少需要考虑的行数,而不一定在索引字段上使用过滤器。不要额外花费大量的精力来使用实际上并不适合当前情况的索引字段。

3. 链接字段会增加每个过滤器和每个过滤行的累计成本。

链接字段的速度不是特别慢,但会增加一些开销,因为它们需要评估其他表中的行。

如果高效过滤器可以消除主实体表中的大多数行,则复杂过滤器也可以在链接字段上使用其他过滤器,而不会对性能造成很大的负面影响。

另一方面,如果要评估的表行数量很大,则链接字段上的每个过滤器、排序或分组将会导致复杂过滤器的成本显著增加。另外,“双重”链接字段将会产生更大的影响,因为它们需要对更多表中的行进行评估。

更改集成方法以避免文本匹配

如果您发现您的 API 集成在很大程度上依赖于文本匹配过滤器或链接字段上的过滤器,而未按照第二条准则应用速度更快的过滤器类型,则您的集成不太可能随着生产活动的增加而很好地进行扩展。您应该考虑寻找一种方法,依赖于对实体本身字段的精确匹配,即使这种方法需要费些功夫,但它终将取得成功。例如,您可能需要自动更新状态,或者添加并填充可用于精确匹配值的新列表或文本字段。

下面是一个 API 摘要过滤器示例,它依赖于文本匹配来识别一个假想动画工作流中报告的 PublishedFile 动画文件类型:

filters = [
  {
    "filter_operator":"or","filters":[
      [ "path_cache", "contains", "/anim_face" ],
      [ "path_cache", "contains", "/anim_fine" ]
    ]
  }
]
summaries = sg.summarize(entity_type='PublishedFile', filters, summary_fields=[{'field':'id', 'type':'count'}])

可通过多种可行的方法来进一步限制此过滤器,例如,限制项目、日期范围或 PublishedFileType。

但是,如果工作流仅支持识别特定的动画文件类型,则针对资源类型添加一个新的自定义列表列(始终通过自定义发布脚本或其他某种方式设置)将会提高性能。使用名为“My Resource Subtype”的字段时,该过滤器具有如下效果:

filters = [
  {
    "filter_operator":"or",
    "filters":[
      [ "sg_my_resource_subtype", "is", "anim_face" ],
      [ "sg_my_resource_subtype", "is", "anim_fine" ]
    ]
  }
]
summaries = sg.summarize(entity_type='PublishedFile', filters, summary_fields=[{'field':'id', 'type':'count'}])

如果“anim_face”和“anim_fine”的已发布文件非常多,该过滤器仍可能很慢,但应该比以前的文本匹配版本要快得多。(如果您想了解为何该 OR(“任一”)过滤器可以提高性能,请参见下面的“高级性能”部分。)

高级性能说明

使用 OR(“任一”)组

实际上,OR 组中的一些过滤器组合仍然可以有效地消除表行,这足以提高复杂过滤器的性能。当然,这取决于特定的过滤器及其对表行的消除程度。请参见上面“更改集成方法”部分的示例。

使用链接字段

实际上,通过实体字段链接的字段上的过滤器可能也会获得不错的结果。这是因为链接字段指定了链接字段值的实体类型。如果实体字段中的极少数值属于链接的实体类型,则通过该实体字段链接的过滤器可能会消除父实体表中的大多数行。请注意,这不适用于多实体字段。

下面是任务过滤器的一个示例:

filters = [[ "entity.Sequence.sg_status_list", "is", "hld" ]]

例如,如果极少数任务的“entity”字段中包含镜头序列,则上述任务过滤器实际上可能会表现不错。这是因为需要处理的实体列中包含镜头序列的任务行数很少。

另一方面,如果有数百万个任务链接到镜头序列,这些过滤器的性能会比较低下,因为这数百万个任务行都需要处理。

有关如何充分利用 Shotgun 的详细信息,请参见我们的最佳实践核对表

关注

0 评论

登录写评论。