Chr*_*isJ 6 postgresql index datatypes unique-constraint
我正在处理一个使用instagram 密钥格式的项目。
TL;DR 64 位整数 ID。
它们将用于查找,我们也喜欢它们用于排序和批处理,因为它们会自然地按创建时间排序。
这些值介于 2^63 和 2^64 之间,因此(只是)太大而无法放入BIGINT
.
因此,我们的存储选项似乎是numeric(20)
或varchar
。varchar
不是那么理想,因为我们必须将它们归零才能进行排序才能工作,但是使用数字进行查找会影响性能吗?
讨厌在这个问题上成为明显的队长,但是Instagram 慷慨地提供了一个功能,您可以链接到该功能将密钥存储为bigint
.
CREATE SCHEMA insta5;
CREATE SEQUENCE insta5.table_id_seq;
CREATE OR REPLACE FUNCTION insta5.next_id(OUT result bigint) AS $$
DECLARE
our_epoch bigint := 1314220021721;
seq_id bigint;
now_millis bigint;
shard_id int := 5;
BEGIN
-- The %1024, is just a way of saying they only want 10bit wraparound.
SELECT nextval('insta5.table_id_seq') % 1024 INTO seq_id;
SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
result := (now_millis - our_epoch) << 23;
result := result | (shard_id << 10);
result := result | (seq_id);
END;
$$ LANGUAGE plpgsql;
Run Code Online (Sandbox Code Playgroud)
他们实际上使用的是 PostgreSQL。从该函数中,您可以看到它们返回了一个bigint
. 所以当然你可以将该函数的结果存储在bigint
. 需要特别说明的是,这可能不是他们正在使用的功能。该函数可能具有更像这样的签名,
insta5.next_id(smallint shard, OUT result bigint);
Run Code Online (Sandbox Code Playgroud)
我们知道这一点,因为硬编码一个分片5
并不是那么有用,而且它们似乎表明他们正在使用此功能。所以在那个博客 ID 他们吹嘘他们的 ID 妥协了
快速测试他们的代码,
test=# SELECT insta5.next_id();
next_id
---------------------
1671372309237077023
(1 row)
Run Code Online (Sandbox Code Playgroud)
现在让我们玩吧。为了更加性感,我们可以创建辅助函数,从 ID 获取内部组件。如果您想知道 Instagram 正在使用的分片或其内部时间戳。
-- 13 bits for shard
CREATE FUNCTION insta5.get_shard(id bigint)
RETURNS smallint
AS $$
SELECT ((id<<41)>>51)::smallint;
$$ LANGUAGE sql;
-- 10 bits for sequence id
CREATE FUNCTION insta5.get_sequence(id bigint)
RETURNS smallint
AS $$
SELECT ((id<<54)>>54)::smallint;
$$ LANGUAGE sql;
-- 41 bits for timestamp
CREATE OR REPLACE FUNCTION insta5.get_ts(id bigint)
RETURNS timestamp without time zone
AS $$
SELECT to_timestamp(((id >> 23) + 1314220021721 ) / 1000 )::timestamp without time zone;
$$ LANGUAGE sql;
Run Code Online (Sandbox Code Playgroud)
玩玩,让我们得到一个测试ID。
SELECT insta5.next_id();
next_id
---------------------
1671390786412876801
(1 row)
SELECT
insta5id,
insta5.get_ts(insta5id),
insta5.get_shard(insta5id),
insta5.get_sequence(insta5id)
FROM (VALUES
(1671390786412876801::bigint),
(insta5.next_id())
) AS t(insta5id);
Run Code Online (Sandbox Code Playgroud)
返回以下内容,
insta5id | get_ts | get_shard | get_sequence
---------------------+---------------------+-----------+--------------
1671390786412876801 | 2017-12-16 17:02:09 | 5 | 1
1671392537048257538 | 2017-12-16 17:05:38 | 5 | 2
(2 rows)
Run Code Online (Sandbox Code Playgroud)
DOMAIN
如果你想真正清理它,你甚至可以在类型上创建一个显式。这就是我个人存储它的方式,注意我做了一些进一步的修改。
COMMENTS
——总是好的做法。IMMUTABLE
insta5.next_id
需要显式分片。让我们放下我们所拥有的,
DROP SCHEMA insta5 CASCADE;
Run Code Online (Sandbox Code Playgroud)
然后重新开始,
CREATE SCHEMA insta5;
COMMENT ON SCHEMA insta5 IS 'Instagram';
CREATE DOMAIN insta5.id AS bigint;
COMMENT ON DOMAIN insta5.id IS $$Instagram's internal ID type, based on example from "Sharding & IDs at Instagram"$$;
CREATE SEQUENCE insta5.table_id_seq;
CREATE OR REPLACE FUNCTION insta5.next_id(shard_id smallint)
RETURNS insta5.id
AS $$
DECLARE
our_epoch bigint := 1314220021721;
seq_id bigint;
result insta5.id;
now_millis bigint;
BEGIN
SELECT nextval('insta5.table_id_seq') % 1024 INTO seq_id;
SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
result := (now_millis - our_epoch) << 23;
result := result | (shard_id << 10);
result := result | (seq_id);
RETURN result;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION insta5.next_id(smallint)
IS 'Modifications made to require shard id';
CREATE OR REPLACE FUNCTION insta5.get_shard(id insta5.id)
RETURNS smallint
AS $$
SELECT ((id<<41)>>51)::smallint;
$$ LANGUAGE sql
IMMUTABLE;
COMMENT ON FUNCTION insta5.get_shard(insta5.id)
IS '13 bits from insta5.id representing shard';
CREATE OR REPLACE FUNCTION insta5.get_sequence(id insta5.id)
RETURNS smallint
AS $$
SELECT ((id<<54)>>54)::smallint;
$$ LANGUAGE sql
IMMUTABLE;
COMMENT ON FUNCTION insta5.get_sequence(insta5.id)
IS '10 bits from insta5.id representing sequence';
CREATE OR REPLACE FUNCTION insta5.get_ts(id insta5.id)
RETURNS timestamp without time zone
AS $$
SELECT to_timestamp(((id >> 23) + 1314220021721 ) / 1000 )::timestamp without time zone;
$$ LANGUAGE sql
IMMUTABLE;
COMMENT ON FUNCTION insta5.get_ts(insta5.id)
IS '41 bits from insta5.id representing timestamp';
Run Code Online (Sandbox Code Playgroud)
一切都像以前一样,但现在你可以
CREATE SCHEMA mySchema;
CREATE TABLE mySchema.mydata ( insta5id insta5.id ) ;
Run Code Online (Sandbox Code Playgroud)
这可能是您可以避免使用 C 实现的最佳解决方案,并且您可能不想生成一个insta5id
永远。那是他们的工作。;)
另一个重要的问题是,您可能永远不想这样做。不要以身作则。这就是该uuid
类型的用途,您应该使用它而不是手动滚动您自己的类型。具体来说,这与uuid_generate_v1()
in非常相似uuid-ossp
,它存储 MAC(分片)和时间戳
此函数生成版本 1 UUID。这涉及计算机的MAC 地址和时间戳。请注意,此类 UUID 会显示创建标识符的计算机的身份及其创建时间,这可能使其不适用于某些对安全敏感的应用程序。
numeric
bigint
在各个方面都比(8 个字节)更大更慢。不幸的是,就像你说的,bigint
只是不够大。最大值为2^63-1
= 9223372036854775801
,19 位十进制数字。
varchar
或者text
(内部相同的东西)更大更慢,但是。另外,COLLATION
除非明确定义,否则字符类型受规则限制。
SELECT pg_column_size(numeric '18446744073709551616') -- 16 bytes in RAM
, pg_column_size(text '18446744073709551616') -- 24 bytes in RAM
Run Code Online (Sandbox Code Playgroud)
(但磁盘上有 13 / 21 个字节。)参见:
db<>在这里摆弄
numeric
为尾随零节省额外的空间,而text
不能。
numeric
不允许存储非数字并强制执行一些开箱即用的理智。
面对您的要求,选择 numeric
永不回头。
有关的:
或者您可以安装Peter Eisentraut的扩展pguint
,他是 Postgres 项目的核心黑客之一。
请务必先阅读自述文件。该扩展提供了几个额外的(大多数是无符号的)整数类型。您可能只是提取uint8
(无符号 64 位整数)并丢弃其余部分以避免类型系统过度拥挤。
归档时间: |
|
查看次数: |
3326 次 |
最近记录: |