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?
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
Test rapide sur leur code,
test=# SELECT insta5.next_id();
next_id
---------------------
1671372309237077023
(1 row)
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)
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.
COMMENTS
- toujours bonne pratique.IMMUTABLE
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é.
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.
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.