在键控时间范围内查询排序值的索引

Index to query sorted values in keyed time range

假设我有 key/value/timerange 个元组,例如:

CREATE TABLE historical_values(
  key TEXT,
  value NUMERIC,
  from_time TIMESTAMPTZ,
  to_time TIMESTAMPTZ
)

并希望能够有效地查询特定键和时间的值(降序排序),例如:

SELECT value
FROM historical_values
WHERE
  key = [KEY]
  AND from_time <= [TIME]
  AND to_time >= [TIME]
ORDER BY value DESC

我应该使用哪种 index/types 来获得最佳查找性能?我怀疑我的解决方案将涉及 tstzrangegist 索引,但我 不确定如何让它很好地满足键匹配和值排序要求。

编辑:这里有一些关于用法的更多信息。

数据库设计

对于像这样的大 table(“1k 键和每个键 5m 值”),我建议优化存储,如:

CREATE TABLE hist_keys (
   key_id serial PRIMARY KEY
 , key text NOT NULL UNIQUE
);

CREATE TABLE hist_values (
   hist_value_id bigserial PRIMARY KEY  -- optional, see below!
 , key_id        int NOT NULL REFERENCES hist_keys
 , value         numeric
 , from_time     timestamptz NOT NULL
 , to_time       timestamptz NOT NULL
 , CONSTRAINT range_valid CHECK (from_time <= to_time)  -- or < ?
);

也有助于索引性能。

并考虑分区key_id 上的列表分区。甚至可以在 from_time 上添加子分区(这次是范围分区)。 Read the manual here.

每个 key_id 一个分区,(并且启用 constraint exclusion!)Postgres 只会查看给定键的小分区(和索引),而不是整个大分区 table.重大胜利。

但我强烈建议首先升级到至少 Postgres 10,其中添加了 "declarative partitioning"。使管理分区更容易。

更好的是,跳到 Postgres 11(目前是测试版),它增加了对分区的重大改进(包括性能改进)。最值得注意的是,为了您的目标 获得最佳查找性能 ,引用 chapter on partitioning in release notes for Postgres 11 (currently beta):

  • Allow faster partition elimination during query processing (Amit Langote, David Rowley, Dilip Kumar)

    This speeds access to partitioned tables with many partitions.

  • Allow partition elimination during query execution (David Rowley, Beena Emerson)

    Previously partition elimination could only happen at planning time, meaning many joins and prepared queries could not use partition elimination.

索引

value 列的角度来看,所选行的小子集对于每个新查询都是任意的。我不希望您会找到一种有用的方法来支持 ORDER BY value DESC 与索引。我会专注于其他专栏。 也许 添加 value 作为每个索引的最后一列,如果你可以从中获得仅索引扫描(可能用于 btree 和 GiST)。

不分区:

CREATE UNIQUE INDEX hist_btree_idx ON hist_values (key_id, from_time, to_time <b>DESC</b>);

UNIQUE 是可选的,但请参阅下文。
请注意 from_timeto_time 的相反排序顺序的重要性。参见(密切相关!):

这与在 (key_id, from_time, to_time) 上实现 PK 的索引几乎相同。遗憾的是,我们不能将其用作 PK 指标。 Quoting the manual:

Also, it must be a b-tree index with default sort ordering.

所以我在上面建议的 table 设计中添加了一个 bigserial 作为代理主键,并添加了 NOT NULL 约束和 UNIQUE 索引来强制执行唯一性规则。

在 Postgres 10 或更高版本中考虑 IDENTITY 列:

  • Auto increment table column

在这种特殊情况下,您甚至可以使用 PK 约束来避免重复索引并将 table 保持在最小大小。取决于完整的情况。 FK 约束或类似约束可能需要它。参见:

  • How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?

A GiST 索引 就像您已经怀疑的那样可能更快。我建议将您原来的 timestamptz 列保留在 table 中(16 字节而不是 tstzrange 的 32 字节)并在安装附加模块后添加 key_id btree_gist:

CREATE INDEX hist_gist_idx ON hist_values
USING GiST (key_id, tstzrange(from_time, to_time, '[]'));

表达式tstzrange(from_time, to_time, '[]')构造一个范围包括上下界。 Read the manual here.

您的查询需要匹配索引:

SELECT value
FROM   hist_values
WHERE  key = [KEY]
AND    tstzrange(from_time, to_time, '[]') @>  tstzrange([TIME_FROM], [TIME_TO], '[]') 
ORDER  BY value DESC;

相当于你原来的。
@> being the range contains operator.

列表分区 key_id

每个 key_id 有一个 单独的 table,我们可以从中省略 key_id索引,提高大小和性能 - 特别是对于 GiST 索引 - 我们也不需要额外的模块 btree_gist。得到约 1000 个分区和相应的索引:

CREATE INDEX hist999_gist_idx ON hist_values USING GiST (tstzrange(from_time, to_time, '[]'));

相关:

  • Store the day of the week and time?