Postgresql 索引位集大批量更新的优化方法

Postgresql 索引位集大批量更新的优化方法,postgresql,apache-spark,database-performance,jsonb,Postgresql,Apache Spark,Database Performance,Jsonb,此问题的环境是AWS RDS上的PostgreSQL 9.6.5 问题是针对包含以下逻辑数据模型的3亿行表的最佳模式设计和批量更新策略: id:主键,最多40个字符长的字符串 code:整数1-999 年:整数年 标志:变量编号(1000+),每个与名称关联,随时间增加新标志。理想情况下,一个标志应该被认为有三个值:缺席(null),打开(true/1)和关闭(false/0)。以额外更新(见下文)为代价,可以将标志视为一个简单位(开或关,不存在)。“开”值通常非常稀疏:

此问题的环境是AWS RDS上的PostgreSQL 9.6.5

问题是针对包含以下逻辑数据模型的3亿行表的最佳模式设计和批量更新策略:

  • id
    :主键,最多40个字符长的字符串
  • code
    :整数1-999
  • :整数年
  • 标志:变量编号(1000+),每个与名称关联,随时间增加新标志。理想情况下,一个标志应该被认为有三个值:缺席(
    null
    ),打开(
    true
    /
    1
    )和关闭(
    false
    /
    0
    )。以额外更新(见下文)为代价,可以将标志视为一个简单位(开或关,不存在)。“开”值通常非常稀疏:<1/1000
查询通常涉及一个或多个标志(按名称)的存在或不存在的布尔表达式,偶尔还涉及
code
year

数据通过Apache Spark进行批量更新,即更新可以表示为平面文件(例如,以拷贝格式)或SQL操作。一次只有一个更新处于活动状态。对
code
year
的更新非常罕见。每次更新对标志的更新会影响1-5%的行(300-1500万行)。更新行可以包括所有标志及其值,只包括要更新的“开”标志或值已更改的标志。在前一种情况下,Spark需要查询数据以获取标志的当前值

更新期间将有少量读取负载

问题是关于支持所述查询和更新的最佳模式和相关更新策略

迄今为止的一些研究评论:

  • 使用1000+个布尔列将创建非常高效的行表示,但除了一些DDL复杂性之外,还需要1000+个索引

  • 如果有一种索引单个位的方法,那么位字符串将是非常棒的。此外,它们也不能提供一种表示缺席旗帜的好方法。使用这种方法需要在标志名和位ID之间维护一个查找表。如果需要,合并更新可以与
    | |
    一起使用,但是,考虑到PostgreSQL的MVCC,仅更新标志而不是替换整行似乎没有多大好处

  • JSONB字段提供索引。它们还提供
    null
    表示,但这是有代价的:所有“关闭”的标志都需要显式设置,这会使字段非常大。如果我们忽略
    null
    表示,JSONB字段将相对较小。为了进一步缩小它们,我们可以在查找表中使用1-3个字符的短字段名。关于:合并的注释与使用位字符串的注释相同

  • tsvector
    /
    tsquery
    :没有使用此数据类型的经验,但在理论上,似乎是一组“开”标志的精确名称表示。必须使用查找表将标志名称映射到具有额外要求的标记,以确保不会因词干生成而发生冲突


不要将标志存储在主表中

假设主表名为
data
,定义如下内容:

CREATE TABLE flag_names (
   id smallint PRIMARY KEY,
   name text NOT NULL
);

CREATE TABLE flag (
   flagname_id smallint NOT NULL REFERENCES flag_names(id),
   data_id text NOT NULL REFERENCES data(id),
   value boolean NOT NULL,
   PRIMARY KEY (flagname_id, data_id)
);
如果创建了新标志,请在
标志\u名称
中插入新行

如果标志设置为
TRUE
FALSE
,请在
标志
表中插入或更新一行


标志
数据
连接起来,以测试是否设置了某个标志。

我必须补充一点,这样做还有一个性能原因-对大行的更新速度很慢。由于每人有1000多个标志,您是否建议使用一个3000亿行的表,每次最多更新90-450亿行?如果您不建议这样做,也就是说,如果您只想保留设置为“开”的标志,为什么要有一个值列?您希望更新多达1000个小行比更新一个大行快吗?另外,我很好奇,对于模式,对标志
A或(B和C)而不是(D和E)
的查询是什么样子的。将标志信息拆分为多行需要额外的工作来评估同一帐户的标志相关性;所以,也许您必须只存储
TRUE
标志才能使其正常工作。数千列无法工作,索引非第一正常形式的数据是一件痛苦的事情。查询应该不会太困难:
从flag f_a JOIN flag f_b USING(data_id)选择data_id,其中f_a.flagname_id=$1和f_b.flagname_id=$2和f_c.flagname_id=$3和f_a.value或(f_b.value和f_c.value)