web-dev-qa-db-ja.com

数値と文字列の検索パフォーマンス

instagram key format を使用しているプロジェクトで作業しています。

TL; DR 64ビット整数ID。

これらはルックアップに使用されます。また、作成時間で自然にソートされるため、ソートやバッチ処理にも適しています。

値は2 ^ 63と2 ^ 64の間なので、([])大きすぎてBIGINTの内側に収まりません。

したがって、ストレージのオプションはnumeric(20)またはvarcharのようです。 varcharは、並べ替えを機能させるためにゼロを埋め込む必要があるため、理想的ではありませんが、ルックアップに数値を使用するとパフォーマンスに影響がありますか?

5
ChristopherJ

キャプテンになるのは嫌いですが、 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;
_

彼らは実際にPostgreSQLを使用しています。その関数から、bigintが返されていることがわかります。 そのため、確かにその関数の結果をbigintに保存できます。特別な注意として、これはおそらくそれらの関数ではありません再使用。その関数はおそらくこのようなシグネチャを持っています。

_insta5.next_id(smallint shard, OUT result bigint);
_

_5_のシャードをハードコーディングすることはそれほど有用ではなく、この機能を使用していることを示しているようです。そのブログIDでは、IDの妥協を自慢しています

  • 64ビット合計
  • 64-23 =タイムスタンプに41ビット
  • 64-41 =断片+シーケンスIDの23ビット
  • シーケンスIDの10ビット。
  • シャード用の13ビット。

彼らのコードのクイックテスト、

_test=# SELECT insta5.next_id();
       next_id       
---------------------
 1671372309237077023
(1 row)
_

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;
_

遊んで、テスト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);
_

次を返します、

_      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)
_

独自のInstagram IDドメインを作成する

これを完全にクリーンアップしたい場合は、型に明示的なDOMAINを作成することもできます。これは私がこれを個人的に保存する方法です。さらにいくつか変更を加えました。

  • COMMENTSを追加しました-常に良い習慣です。
  • 関数を作成したIMMUTABLE
  • _insta5.next_id_を追加しました。明示的なシャードが必要です。

私たちが持っていたものを落としましょう、

_DROP SCHEMA insta5 CASCADE;
_

そして、最初から

_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';
_

すべては以前と同じように機能しますが、今はできます

_CREATE SCHEMA mySchema;
CREATE TABLE mySchema.mydata ( insta5id  insta5.id ) ;
_

これはおそらくC実装の恥ずかしがり屋を得ることができる最良の解決策であり、おそらく_insta5id_を生成したくないでしょう。それが彼らの仕事です。 ;)

もう1つ重要なことはさておき、あなたはおそらくこれをしたくありません。例に従ってはいけません。これが uuid type の目的であり、自分でローリングするのではなく、使用する必要があります。具体的には、これはuuid_generate_v1() in _uuid-ossp_ 、MAC(シャード)、およびタイムスタンプを格納します

この関数は、バージョン1のUUIDを生成します。これには、コンピューターのMACアドレスとタイムスタンプが含まれます。この種類のUUIDは、識別子と時刻を作成したコンピューターのIDを明らかにすることに注意してください。そのため、セキュリティが重要な特定のアプリケーションには適さなくなる可能性があります。

7
Evan Carroll

numeric(20)(14バイト)は、あらゆる点でbigint(8バイト)より大きく、遅いです。そしてvarcharまたはtext(同じこと)、20桁で24バイトを占有します。ただし、numericよりも低速です。さらに、明示的に定義されていない限り、文字タイプはCOLLATIONルールで制限されます。

自分を試す:

SELECT pg_column_size((2^64 -1)::numeric)        -- 14 bytes
     , pg_column_size((2^64 -1)::numeric::text)  -- 24 bytes

numericはまた、非数値がそのまま保存されることを許可しません(ニーズに適合)。

あなたの与えられたオプションに直面しました(2 ^ 63と2 ^ 64の間の値なので、大きすぎてbigintに収まりません)numericを選択し、決して振り返りません。

関連:


Or Postgresプロジェクトのコアハッカーの1人であるPeter Eisentrautによる extension pguint をインストールできます。

最初にreadmeを必ずお読みください。拡張機能は、いくつかの追加(ほとんどが符号なし)整数型を提供します。あなたはちょうどextraxt uint8(符号なし64ビット整数)と残りを破棄して、型システムの混雑を回避します。

7

値は2 ^ 63から2 ^ 64の間なので、BIGINTに収まらないには(ただ)大きすぎます。

bigintis -9223372036854775808 to +9223372036854775807 の範囲は、-2 ^ 63〜2 ^ 63-1または2 ^ 64の異なる整数です。識別子の範囲は2 ^ 63の異なる整数であるため、オフセットを気にしない限り、bigintにうまく収まります。

select round(power(2::numeric,63::numeric)) "2^63"
      ,round(power(2::numeric,64::numeric)) "2^64"
      ,(9223372036854775808::numeric-9223372036854775809::numeric)::bigint "offset low val"
      ,(18446744073709551616::numeric-9223372036854775809::numeric)::bigint "offset high val";
 2 ^ 63 | 2 ^ 64 |低値のオフセット|高値オフセット
 ------------------:| -------------------:| -------------:| ------------------:
 9223372036854775808 | 18446744073709551616 | -1 | 9223372036854775807 

dbfiddle ここ

私の例では-9223372036854775809(-2 ^ 63 + 1)のオフセットを使用していますが、bigintをオーバーフローしないオフセットを自由に選択できます。

したがって、キーを提示するとき、およびオフセットを適用するときにnumericを使用する必要がありますが、実際にキーやソートなどの操作を格納するためではありません。