13 个版本
新增 0.2.0 | 2024 年 8 月 20 日 |
---|---|
0.1.13 | 2024 年 7 月 13 日 |
0.0.999 |
|
在 文本处理 中排名 #238
每月下载量 43 次
35KB
408 行(不包括注释)
UPID
发音为 YOO-pid
也称为通用唯一前缀按字典顺序排序的标识符
这是 UPID 的规范和 Python 实现。
UPID 基于 ULID,但进行了一些修改,灵感来自 这篇文章 和 Stripe IDs。
核心思想是指定一个有意义的 前缀,存储在 128 位的 UUID 形状的槽中。因此,UPID 是 可读的(如 Stripe ID),但仍然高效地存储、排序和索引。
UPID 允许最多 4 个字符 的前缀(如果少于 4 个字符,则将右填充),包含一个非环绕的时间戳,精度约为 250 毫秒,以及 64 位的熵。
这是一个 Python 中的 UPID 示例
upid("user") # user_2accvpp5guht4dts56je5a
以及 Rust 中的实现
UPID::new("user") // user_2accvpp5guht4dts56je5a
以及在 Postgres 中的实现
CREATE TABLE users (id upid NOT NULL DEFAULT gen_upid('user') PRIMARY KEY);
INSERT INTO users DEFAULT VALUES;
SELECT id FROM users; -- user_2accvpp5guht4dts56je5a
-- this also works
SELECT id FROM users WHERE id = 'user_2accvpp5guht4dts56je5a';
与服务器代码兼容,无需额外工作
with psycopg.connect("postgresql://...") as conn:
res = conn.execute("SELECT id FROM users").fetchone()
print(res) # user_2accvpp5guht4dts56je5a
演示
您可以在 upid.rdrn.me 上尝试它。
实现
如果您没有时间看 ASCII 艺术字,您可以跳到好东西
语言 | 链接 |
---|---|
Python | 在此存储库中(向下滚动) |
Postgres | 在此存储库中(向下滚动) |
Rust | 在此存储库中(向下滚动) |
TypeScript | carderne/upid-ts |
规范
相对于 ULID 的主要变化
- 使用修改后的 Crockford's base32 形式,使用小写并包含整个字母表(以增加前缀的灵活性)。
- 不允许大写/小写解码互换。
- 文本编码仍然是每 base32 字符 5 位。
- 分配给前缀 20 位
- 分配给时间戳 40 位(从 48 位减少),在二进制中首先放置以进行排序
- 随机数 64 位(从 80 位减少)
- 版本指定器 4 位
user 2accvpp5 guht4dts56je5 a
|----| |--------| |-------------| |-----|
prefix time random version total
4 chars 8 chars 13 chars 1 char 26 chars
\________/________________|___________ |
/ | \ |
/ | \ |
40 bits 64 bits 24 bits 128 bits
5 bytes 8 bytes 3 bytes 16 bytes
time random prefix+version
二进制布局
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_high |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| time_low | random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| random | prefix_and_version |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
冲突
相对于 ULID,时间精度从 48 位减少到 40 位(保留最高有效位,因此溢出仍不会发生在 10889 年),随机数从 80 位减少到 64 位。
在40位精度下,时间戳大约为250毫秒。为了在64位随机性的情况下有50%的概率发生冲突,您需要在每250毫秒窗口内生成大约40亿个条目。
Python实现
本节的目的是尽可能简单明了地传达规范的核心工作原理。当前的Python实现完全基于mdomke/python-ulid。
安装
pip install upid
用法
从CLI运行
python -m upid user
在程序中使用
from upid import upid
upid("user")
或更明确地
from upid import UPID
UPID.from_prefix("user")
或指定您自己的时间戳或日期时间
import time, datetime
UPID.from_prefix_and_milliseconds("user", milliseconds)
UPID.from_prefix_and_datetime("user", datetime.datetime.now())
字符串之间的转换
u = UPID.from_str("user_2accvpp5guht4dts56je5a")
u.to_str() # user_2a...
输出内容
u.prefix # user
u.datetime # 2024-07-07 ...
转换为其他格式
int(u) # 2079795568564925668398930358940603766
u.hex # 01908dd6a3669b912738191ea3d61576
u.to_uuid() # UUID('01908dd6-a366-9b91-2738-191ea3d61576')
开发
代码和测试位于py/目录中。使用Rye进行开发(安装说明在链接中)。
# can be run from the repo root
rye sync
rye run all # or fmt/lint/check/test
如果您只是想浏览一下,pip也应该可以
pip install -e .
如果发现错误或改进,请提交PR!
Rust实现
当前的Rust实现基于dylanhart/ulid-rs,但使用与Python实现相同的base32查找方法。
安装
cargo add upid
用法
use upid::Upid;
Upid::new("user");
或指定您自己的时间戳或日期时间
use std::time::SystemTime;
Upid::from_prefix_and_milliseconds("user", 1720366572288);
Upid::from_prefix_and_datetime("user", SystemTime::now());
字符串之间的转换
let u = Upid::from_string("user_2accvpp5guht4dts56je5a");
u.to_string();
输出内容
u.prefix(); // user
u.datetime(); // 2024-07-07 ...
u.milliseconds(); // 17203...
转换为其他格式
u.to_bytes();
开发
代码和测试位于upid_rs/目录中。
cd upid_rs
cargo check # or fmt/clippy/build/test/run
如果发现错误或改进,请提交PR!
Postgres扩展
还有一个基于Rust实现的Postgres扩展,使用pgrx,并基于类似的扩展pksunkara/pgx_ulid。
安装
最简单的方法是尝试Docker镜像carderne/postgres-upid:16,目前为arm64和amd64架构,但仅适用于Postgres 16
docker run -e POSTGRES_HOST_AUTH_METHOD=trust -p 5432:5432 carderne/postgres-upid:16
您还可以从Releases页面获取Linux .deb
。这是为Postgres 16和amd64构建的。
一旦推出alpha版本,将会有更多架构和版本。
用法
CREATE EXTENSION upid_pg;
CREATE TABLE users (
id upid NOT NULL DEFAULT gen_upid('user') PRIMARY KEY,
name text NOT NULL
);
INSERT INTO users (name) VALUES('Bob');
SELECT * FROM users;
-- id | name
-- -----------------------------+------
-- user_2accvpp5guht4dts56je5a | Bob
您可以获取原始的bytea
数据,或前缀或时间戳
SELECT upid_to_bytea(id) FROM users;
-- \x019...
SELECT upid_to_prefix(id) FROM users;
-- 'user'
SELECT upid_to_timestamp(id) FROM users;
-- 2024-07-07 ...
或将UPID转换为常规的Postgres UUID
SELECT upid_to_uuid(gen_upid('user'));
或反向操作(尽管前缀和时间戳将不再有意义)
select upid_from_uuid(gen_random_uuid());
开发
如果您想将其安装到另一个Postgres中,您需要安装pgrx并遵循其安装说明。类似以下命令
cd upid_pg
cargo install --locked cargo-pgrx
cargo pgrx init
cargo pgrx install
一些cargo
命令与常规操作相同
cargo check # or fmt/clippy
但构建、测试和运行必须通过pgrx进行。这将将其编译到Postgres安装中,并允许在其中进行交互式会话和测试。
cargo pgrx test pg16
# or run
# or install
依赖关系
~250–370KB