数字与字符串的查找性能

Chr*_*isJ 6 postgresql index datatypes unique-constraint

我正在处理一个使用instagram 密钥格式的项目

TL;DR 64 位整数 ID。

它们将用于查找,我们也喜欢它们用于排序和批处理,因为它们会自然地按创建时间排序。

这些值介于 2^63 和 2^64 之间,因此(只是)太大而无法放入BIGINT.

因此,我们的存储选项似乎是numeric(20)varcharvarchar不是那么理想,因为我们必须将它们归零才能进行排序才能工作,但是使用数字进行查找会影响性能吗?

Eva*_*oll 9

讨厌在这个问题上成为明显的队长,但是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 妥协了

  • 总共 64 位
  • 64-23 = 41 位时间戳
  • 64-41 = 23 位用于分片 + 序列 ID
  • 序列 id 的 10 位。
  • 13 位用于分片。

快速测试他们的代码,

test=# SELECT insta5.next_id();
       next_id       
---------------------
 1671372309237077023
(1 row)
Run Code Online (Sandbox Code Playgroud)

分解ID

现在让我们玩吧。为了更加性感,我们可以创建辅助函数,从 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)

滚动我们自己的 Instagram ID 域

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 会显示创建标识符的计算机的身份及其创建时间,这可能使其不适用于某些对安全敏感的应用程序。


Erw*_*ter 8

numericbigint在各个方面都比(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 位整数)并丢弃其余部分以避免类型系统过度拥挤。