针对可扩展性和性能进行的表优化

Prev Next

本文概述

  • 本文旨在探讨如何实施和维护一个兼具高性能、高可靠性、可组合性和可扩展性的 Tulip 表模型。
  • 本文旨在提供以下内容:
  • 建议将 Tulip Library 表资源作为典型用例的起点
  • 设计附加表的最佳实践
  • 构建高性能的表查询
  • 规划并实施表记录归档策略

**受众:**应用程序开发人员、解决方案架构师、数据架构师、数据平台资源


关键概念

  • 表是任何 Tulip 解决方案的基础组件,也是您组织内众多数据源之一。
  • 设计良好的表模型能够构建出高性能、可组合且可扩展的解决方案。
  • Tulip 库资源通过提供通用数据模型,帮助客户快速入门。
  • 表存在某些限制——有些是设计上的(例如速率限制),有些则是实践中的(例如记录数阈值)。
  • 设计高性能查询并归档高容量表(例如,超过 1000 万条记录)中的旧表记录,有助于保持解决方案的响应速度,从而直接提升操作员的工作效率和用户体验。

Tulip 在企业数据流中的典型定位

作为旨在提供“单一控制面板”功能的一线运营平台,您的 Tulip 解决方案托管关键制造数据,并与企业内的其他数据源集成。请参阅下图所示的架构图。 您的 Tulip 解决方案可通过连接器函数与企业业务系统交互,并通过配置为 Machine Data Source 的 MQTT 代理或 OPC UA 服务器与车间设备交互。 您的 iPaaS、ETL 解决方案以及企业数据可视化工具可通过表 API 与您的 Tulip 解决方案进行交互。

Tulip Position in Enterprise Data Flow.svg

Tulip 常见数据模型资源

Tulip 提供了多项资源,旨在通过基于 Table 的通用数据模型加速解决方案开发。如需了解更多信息,请参阅:

关于通用数据模型的更多文章

Tulip 资源库中关于通用数据模型的资源

面向可扩展性的表优化

可扩展表设计的最佳实践

Tulip 库通用数据模型中的表是基于下文所述的最佳实践构建的。这些表及其背后的设计原则,为设计能够长期在 Tulip 逻辑中保持可扩展性和可用性的表提供了实用指南。

使表具有水平可扩展性(纵向扩展而非横向扩展)。

以“物料清单(BOM)”表为例。初次接触 Tulip 的应用开发者可能会倾向于创建一个“宽”表,为装配体中的每个组件单独设置一个“编号”列。虽然从短期来看这似乎很简单,但很快就会导致查询复用变得复杂、触发器逻辑负担加重,并限制该表在其他装配体中的适用性。

在下方的物料清单(BOM)表中,各列分别代表装配体的父级和组件。如果一个装配体包含五个组件,则该表包含五条记录。这种结构支持 n 级层次结构,因为一个子装配体可以在一种关系中充当“父级”,而在另一种关系中充当“组件”。

另请注意,此表设计既与产品无关,也与行业无关——它可存储从一顿饭到商用飞机等任何物品的物料清单。

物料清单表的推荐结构| 标签 | 字段类型 | 描述 || --- | --- | --- || ID | 文本 |*必填:*唯一标识符 || 父物料定义 ID | 文本 | 父装配体的唯一标识符材料定义的唯一标识符 || 父级材料描述 | 文本 | 父级材料的描述 || 组件材料定义 ID | 文本 | 组件材料定义的唯一标识符 || 组件材料描述 | 文本 | 待组装或消耗的材料的描述 || 组件数量 | 数字 | 待组装或消耗的材料的数量 || 组件计量单位 | 文本 | 组件 || 使用点 | 文本 | 材料将被组装或消耗的位置、工序或步骤 |

避免使用过宽的表格

虽然 Tulip 平台支持多达 200 列,但如果表格列数超过约 50 列,则可能表明需要重新考虑底层数据模型,以确保长期可扩展性。

避免在单个表中存储过多的不同“事物”

在此语境下,“事物”指特定用例类型的数据实体。例如,“设备与资产”表可能同时存储车间设备和需要校准跟踪的工具的相关信息。虽然将不同类型的实体存储在同一张表中在实际操作中可行,但只有在以下情况下才最为合适:

  • 不同实体类型中记录 ID 的使用方式保持一致。
  • 在“车间设备”和“已校准工具”的示例中,记录 ID 通常是公司的资产标签。
  • 不同类型的实体共享约 50% 或更多的相同列。
  • 如果不同类型的实体仅共享极少数相同的字段,则将它们存储在单独的表中可能更为合适。
  • 实体类型之间不存在一对多关系。
  • 例如,一个批次可能包含许多工作单。即使工作单和批次共享超过 50% 的相同列——且 ID 列既可作为工作单 ID,也可作为批次 ID ——它们之间的一对多关系仍会使查询设计和触发器逻辑变得复杂,从而导致表模型对解决方案构建者和数据使用者而言都难以理解。
  • 利益相关者之间的共识。
    • 解决方案开发人员和数据用户通常都认为,不同的实体类型在根本上是相似的(例如“车间设备”和“经校准的工具”)。

表的实际限制

速率限制

Tulip 对资产实施速率限制,旨在为用户提供一致且可靠的体验,具体涉及:

  • 记录写入
  • 记录删除

点击此处了解有关表速率限制的更多信息。

记录数量限制

Tulip 平台并未对表中的记录数设置硬性限制;但根据表的使用方式,实际上存在一定的限制。随着记录数量的增加,查询和聚合操作的性能通常会下降,这可能会影响应用程序的可靠性、运维人员的体验以及生产现场的生产效率。

已有记录显示,Tulip 客户的生产环境应用在处理超过 1000 万条记录的表时,仅由记录数量导致的性能影响微乎其微。

为可靠性和性能进行的表优化

尽量减少使用关联记录

在 Tulip 平台的早期版本中,包含多个关联记录列且记录数超过 100 万的表会出现显著的性能问题。虽然平台改进已大幅解决了这一问题,但出于以下几个原因,尽量减少或限制关联记录的使用仍是一项最佳实践:

  • 大规模性能
  • 通过 API 或 CSV 下载提取表记录
  • 已知的功能限制(例如,无法根据关联表中的列对嵌入式交互式表格进行筛选)

构建高性能查询

查询性能取决于您使用的筛选类型

筛选类型 大规模性能 备注
等于 良好至优秀 在文本列上的精确匹配方面表现极佳。在可能的情况下,请优先使用“等于”筛选器,而非其他筛选类型。
以…开头 良好
包含 一般至良好 最适合短列表(< 10 个项目)。随着列表规模增大,性能会下降。
为空 一般到中等 如果该列中大多数记录的值为空,则仅依赖此筛选条件的查询可能会很慢。
不为空 一般到中等 相反,如果大多数记录为 null,则该查询可以非常快速地返回结果。
以…结尾 一般到中等 不区分大小写。当结果限制较大且记录数较多时,性能会下降。
包含 一般到中等 不区分大小写。当结果范围较大且记录数较多时,性能会下降。
不等于 较差至一般
不在 差至一般
不以…开头 差到一般
不以……结尾 差到一般
不包含 差至一般

查询性能取决于筛选条件的数量

在设计查询时,应力求取得恰当的平衡:设置足够的筛选条件以返回精确的结果,同时确保查询在类似用例中可复用。通常不需要超过三到四个筛选条件。

Queries with many filters

If you are building a query with many filters, it may indicate that your table model lacks scalability and would benefit from a partial redesign.

始终设置适当的查询限制

在每个查询中都设置 LIMIT,使用能满足用例需求的最小值:

  • **检查是否存在至少一条匹配记录:**1 条记录
  • **报表生成:**建议改用 Analytics
  • **其他用例:**根据所用的查询过滤条件,考虑实际可能检索或评估的记录最大数量,并将该数值作为 LIMIT 值(如果少于 1,000)。

为何这很重要:

使用默认限制值 1,000 的查询会尝试检索多达 1,000 条匹配记录——即使实际上只需少量记录。在仅需较小结果集即可满足需求的使用场景中,不必要的较大限制会消耗时间和资源。

表记录归档策略基础

在本讨论中,“归档”是指从表中提取记录,通过 ETL 解决方案(例如 iPaaS 或数据集成平台)将其加载到云数据平台,随后从表中删除这些记录的过程。

Tulip to ETL to Cloud Data to BI.svg

背景与建议

  • Tulip 并未对表中可存储的记录数设置硬性限制。
  • Tulip 建议将单个表的记录数控制在几百万以内,因为随着记录数的增加,查询性能可能会下降。
  • 如果使用关联记录或查询设计不当,性能问题会进一步加剧。

建议:

通过定期将较旧的记录迁移至云数据平台,将表中的记录数控制在 1000 万条以下。

规划和实施表记录归档

  1. 定义归档标准——确定用于识别每张表中符合归档条件的记录的查询过滤条件。
  2. *示例:*对于“车站状态历史”表,您可以归档所有日期早于上个月1日的记录。
    Archiving Step 1.svg
  3. 确定目标位置——选择目标云数据平台(例如:Redshift、Snowflake、Databricks 等)。
    Archiving Step 2.svg
  4. 选择 ETL 解决方案——选择数据集成平台、第三方 iPaaS 或自定义 Python 脚本来执行记录迁移。
    Archiving Step 3.svg
  5. 执行初始批量迁移——将所有符合步骤 1 中定义条件的数据记录分批迁移至步骤 2 中确定的目标平台。
    Archiving Step 4.svg
  6. 安排定期增量迁移— 设置自动化流程,持续执行类似的记录迁移操作。一种常见的最佳实践是高频执行此类记录迁移,例如对于在典型工作日期间持续更新的表,每 15 分钟执行一次。Archiving Step 5.svg

有关步骤 4 和 5 的更详细指导,请参阅下文的“表记录归档的最佳实践”。

Tulip record archiving implementation fundamentals.svg

关于 ETL 选项的补充资源:

归档表记录的最佳实践

将序列号用作游标

在对记录进行分页时,请使用序列号作为游标,而非基于偏移量的分页方式。

偏移量分页为何会失效:如果在同步运行期间插入或更新了记录,基于偏移量的分页可能会跳过或重复记录。基于序列号的游标是稳定的——无论是否存在并发写入,sequenceNumber > last_seen_seq 始终能返回正确的下一页。

表的同步分为两个阶段:首先是一次性批量加载以导入所有历史记录,随后是定期的增量迁移以捕获变更。这两个阶段都应使用序列号作为游标。

初始批量加载,随后进行增量迁移

在运行增量同步之前,请执行一次一次性批量加载——即完整加载所有历史记录。这将为目标端提供完整的基线数据。

操作模式:

  1. 从last_seq = 0开始(若恢复中断的大批量加载,则从上次保存的检查点开始)
  2. 按序列号升序检索页面
  3. 将每个页写入目标系统
  4. 每约 500 条记录保存一次检查点
  5. 当某页未返回任何记录时停止

遵守速率限制并使用指数退避

Tulip Tables API 对所有请求都实施速率限制。正确处理此限制分为两个层面:

主动限流 — 从一开始就合理安排请求间隔以保持在限流阈值内,而非等待被拒绝
被动退避 — 若在实施限流后仍收到 429 状态码(例如因其他并发进程共享同一工作区 API 配额所致),请降低 GET 请求频率并重试。

请参阅《Tulip API 速率限制指南》了解当前限制。请勿在 ETL 解决方案中硬编码限制值——应使用命名常量,以便在限制值发生变化时便于更新。

使用字段过滤器排除关联记录字段

表支持关联记录字段(tableLink 类型)——即一个表中引用另一个表中记录的字段。 当记录响应中包含 tableLink 字段时,Tulip API 必须在服务器端解析这些引用,从而产生级联的数据库查询。对于包含大量关联字段或记录数量庞大的表,这会给数据库带来巨大负载,并影响工作区中的所有用户。

解决方法:使用 fields 查询参数明确列出需要返回的字段,并完全排除 tableLink 字段。这需要少量前期准备工作——您需要先获取表结构以确定应包含哪些字段——但这是正确的做法,能带来更快、更轻量的查询。

基于检查点的错误恢复

网络故障、超时和意外错误在所难免。请设计连接器,使其能够优雅地恢复,而无需从头开始重启。

模式:

  • 每处理约 500 条记录后,将最后一个成功处理的序列号持久化到持久化存储中
  • 重启时,加载检查点并从 seq > checkpoint_seq 处继续处理
  • 在目标存储中使用 upsert 逻辑(而非 insert)写入记录,以确保从检查点重新处理记录是安全的

处理记录删除

Tulip 表 API 不提供删除事件流。如果某条记录在 Tulip 中被永久删除,连接器无法检测到此情况——该记录会直接从 API 中消失,且不会发出任何通知。如何处理此情况取决于您希望外部目标执行什么操作。

  • 场景 A:保持目标与 Tulip 同步(镜像删除操作)
    • 如果您的目标是让目标系统反映 Tulip 的当前状态(包括已删除的记录)**,则请勿在 Tulip 中永久删除记录。**连接器无法传播它从未检测到的删除操作。
    • 取而代之,请在表中添加一个布尔列(如“已归档”或“已删除”),并在需要将记录视为非活动状态时将其设为 true。由于这是字段更新,下一次增量同步会自动捕获该变化并写入目标系统,无需连接器进行特殊处理。随后,您可以在目标系统中过滤掉这些记录。
    • **在 Tulip 中:**添加一个名为“archived”的布尔列,当需要将记录视为“非活动”时,请通过您的应用、函数或自动化将其更新,而不是直接删除该记录。
    • **在目标系统中:**在查询和报表中使用“归档状态 = false”进行筛选,而不是默认所有同步的记录都是活跃的。
  • 场景 B:为提升性能清理 Tulip,同时保留目标系统中的记录
    • Tulip 表在保持精简时性能更佳。如果表变得非常大,彻底删除旧的或已完成记录是管理其大小的有效方法——例如,在保留期结束后归档已完成的工作单。
    • 在此场景下,在 Tulip 中彻底删除记录是可行的。由于连接器永远不会收到删除事件,从 Tulip 中移除的记录在目标端将不再被更新——它们将按原样保留。目标端由此成为长期归档库,而 Tulip 则保持高性能。
    • 此模式无需对连接器进行任何更改。只需确保下游消费者了解目标数据源可能包含在 Tulip 中已不存在的记录,并据此设计查询(例如,基于 completed_at 时间戳进行连接,而不是假设该记录在 Tulip 中仍然有效)。

进一步学习