web-dev-qa-db-fra.com

Performances de recherche de la chaîne VS numérique

Je travaille sur un projet qui utilise le format de clé Instagram .

TL; DR 64 bits IDS INTEGHTS.

Ils seront utilisés pour des recherches et nous les aimons aussi pour le tri et les lots car ils trient naturellement par temps de création.

Les valeurs sont comprises entre 2 ^ 63 et 2 ^ 64, de sorte que (juste) trop gros pour s'adapter à l'intérieur d'un BIGINT.

Il semble donc que nos options de stockage sont numeric(20) ou varchar. varchar n'est pas aussi idéal puisque nous devrions avoir à zéro les coussinets pour le tri au travail, mais qu'il y aurait une performance touchée à l'utilisation d'un chiffre numérique?

5
ChristopherJ

Déteste être capitaine évident sur celui-ci, mais Instagram fournit généreusement une fonction que vous avez liée à cela stocke les clés comme 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;

Ils utilisent réellement PostgreSQL. De cette fonction, vous pouvez voir qu'ils renvoient un bigint. , vous pouvez certainement stocker le résultat de cette fonction dans bigint. comme une note spéciale, ce n'est probablement pas la fonction qu'ils " en utilisant. Cette fonction a probablement une signature plus comme celle-ci,

insta5.next_id(smallint shard, OUT result bigint);

Nous le savons parce que le codage d'une mèche de 5 N'est pas tout ce qui est utile, et ils semblent indiquer qu'ils utilisent cette fonctionnalité. Donc dans cet identifiant de blog, ils se vantent que leur identifiant compromise de

  • 64bits Total
  • 64-23 = 41 bits pour horodatage
  • 64-41 = 23 bits pour Shard + ID de séquence
  • 10 bits pour l'ID de séquence.
  • 13 bits pour le shard.

Test rapide sur leur code,

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

Décomposer l'identifiant

Maintenant jouons. Pour Teh Extra Sexy, nous pouvons créer des fonctions d'assistance qui obtiennent les composants internes de l'ID. Dans le cas où vous souhaitez connaître le Shard Instagram utilise ou leur horodatage interne.

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

Jouer autour, obtenons un identifiant de test.

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

Renvoie ce qui suit,

      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)

Rouler notre propre domaine d'identifiant Instagram

Vous pouvez même créer un type explicite DOMAIN sur le type si vous souhaitez vraiment nettoyer cela .. Voici comment je voudrais personnellement stocker cela, note que j'ai fait quelques modifications supplémentaires.

  • J'ai ajouté COMMENTS - toujours bonne pratique.
  • Fait les fonctions IMMUTABLE
  • Ajouté insta5.next_id Nécessite un fragment explicite.

Laissons tomber ce que nous avions,

DROP SCHEMA insta5 CASCADE;

Et recommencer,

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

Tout fonctionne comme avant, mais maintenant vous pouvez

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

Ceci est probablement la meilleure solution que vous puissiez obtenir une timide d'une implémentation C et vous ne voulez probablement pas générer un insta5id Jamais. C'est leur travail. ;)

Comme un autre de côté important, vous ne voudrez probablement jamais faire cela. Ne suivez pas par exemple. C'est ce que le type uuid TYPE est pour et que vous devriez l'utiliser plutôt que la main en train de rouler votre propre. Spécifiquement, ceci est étroitement similaire à uuid_generate_v1() IN uuid-ossp , qui stocke un Mac (Shard) et Timeestamp

Cette fonction génère une uuid version 1. Cela implique l'adresse MAC de l'ordinateur et un horodatage. Notez que les Uuids de ce type révèlent l'identité de l'ordinateur qui a créé l'identifiant et l'heure auquel cela l'a fait, ce qui pourrait le rendre inapproprié pour certaines applications sensibles à la sécurité.

7
Evan Carroll

numeric(20) (14 octets) est plus grande et plus lente que bigint (8 octets) à tous égards. Et varchar ou text (même chose), occupez 24 octets pour 20 chiffres: plus lent, encore que numeric. De plus, les types de caractères sont encombrés avec des règles COLLATION, sauf indication explicite sans.

Testez-vous:

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

numeric désactive également les non-chiffres d'être stockés hors de la boîte (convient à vos besoins).

Face à vos options données (valeurs entre 2 ^ 63 et 2 ^ 64, si trop grande pour s'adapter à l'intérieur bigint) Je choisirais numeric et ne regarde jamais en arrière.

En rapport:


o Vous pouvez installer l'extension pguint par Peter Eisenraut, l'un des hackers de base des postgres projet.

Assurez-vous de lire le README premier. L'extension fournit plusieurs types d'entiers supplémentaires (la plupart d'entre eux non signés). Vous pourriez simplement extrasquer uint8 (Intégrice 64 bits non signé) et fossé le reste pour éviter de surpasser le système de type.

7
Erwin Brandstetter

Les valeurs sont comprises entre 2 ^ 63 et 2 ^ 64, donc (juste) trop gros pour s'adapter à l'intérieur d'un Bigint.

La gamme d'un bigint - est -9223372036854775808 à +9223372036854775807 , qui est -2 ^ 63 à 2 ^ 63-1 - ou 2 ^ 64 entiers distincts. La gamme de vos identifiants est de 2 ^ 63 entiers distincts, de sorte qu'ils vont bien dans un bigint tant que cela ne vous dérange pas d'un décalage:

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 | offset bas Val | offset haut val 
 ------------------: | ---------------------: | --------------: | -------------------: 
 9223372036854775808 | 18446744073709551616 | -1 | 9223372036854775807 

dbfiddle ici

Mon exemple utilise un décalage de -9223372036854775809 (-2 ^ 63 + 1), mais vous êtes libre de choisir n'importe quel décalage qui déborde de déborder le bigint.

Donc, vous devrez utiliser numeric lors de la présentation des clés et lorsque vous appliquez le décalage, mais pas pour enregistrer réellement les clés ou les opérations telles que le tri.