PostgreSQL:PrimaryKey
PostgreSQL에서 Primary Key (PK) 에 대한 내용.
BIGINT 를 예측 불가능한 ID 로 사용하는 방법
몇 가지 방법이 있다:
Feistel Cipher 를 사용
순차 시퀀스를 암호화하여 충돌 없이 랜덤하게 보이는 ID를 생성합니다.
-- Feistel cipher function for pseudo-random ID generation
-- 페이스텔 암호 기반 의사난수 ID 생성 함수
CREATE OR REPLACE FUNCTION feistel_encrypt(value BIGINT) RETURNS BIGINT AS $$
DECLARE
l1 BIGINT;
r1 BIGINT;
l2 BIGINT;
r2 BIGINT;
i INT;
key BIGINT := 1234567890; -- Change this secret key / 비밀키 변경 필요
BEGIN
l1 := (value >> 32) & 4294967295;
r1 := value & 4294967295;
FOR i IN 1..3 LOOP
l2 := r1;
r2 := l1 # ((((r1 * key) + i) * 2654435769) & 4294967295);
l1 := l2;
r1 := r2;
END LOOP;
RETURN (l1 << 32) | r1;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Create sequence / 시퀀스 생성
CREATE SEQUENCE uid_seq;
-- Usage example / 사용 예시
CREATE TABLE users (
id BIGINT PRIMARY KEY DEFAULT feistel_encrypt(nextval('uid_seq')),
name TEXT
);
pgcrypto + 충돌 체크
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- Random BIGINT with collision check
-- 충돌 체크를 포함한 랜덤 BIGINT 생성
CREATE OR REPLACE FUNCTION generate_random_uid(table_name TEXT, column_name TEXT)
RETURNS BIGINT AS $$
DECLARE
new_id BIGINT;
exists_check BOOLEAN;
BEGIN
LOOP
-- Generate random positive BIGINT / 양수 BIGINT 생성
new_id := abs(('x' || substr(md5(gen_random_bytes(8)::text), 1, 16))::bit(64)::bigint);
-- Check for collision / 충돌 체크
EXECUTE format('SELECT EXISTS(SELECT 1 FROM %I WHERE %I = $1)', table_name, column_name)
INTO exists_check USING new_id;
IF NOT exists_check THEN
RETURN new_id;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
간단한 XOR + Shuffle 방식
-- Simple XOR-based obfuscation / 간단한 XOR 기반 난독화
CREATE OR REPLACE FUNCTION obfuscate_id(id BIGINT) RETURNS BIGINT AS $$
DECLARE
secret BIGINT := 6432197583241678905; -- Secret key / 비밀키
multiplier BIGINT := 6364136223846793005;
BEGIN
RETURN ((id * multiplier) # secret) & 9223372036854775807;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Reverse function / 역함수 (필요시)
CREATE OR REPLACE FUNCTION deobfuscate_id(obfuscated BIGINT) RETURNS BIGINT AS $$
DECLARE
secret BIGINT := 6432197583241678905;
-- Modular inverse of multiplier / multiplier의 모듈러 역원
inverse BIGINT := 13877824140714322085;
BEGIN
RETURN (((obfuscated & 9223372036854775807) # secret) * inverse) & 9223372036854775807;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
결론
| 방식 | 충돌 가능성 | 복호화 | 성능 | 복잡도 |
| ❌ 없음 | ✅ 가능 | 빠름 | 중간 | |
| ❌ 없음 (체크) | ❌ 불가 | 느림 | 낮음 | |
| XOR Shuffle | ❌ 없음 | ✅ 가능 | 매우 빠름 | 낮음 |
Feistel 방식이 가장 균형 잡힌 선택입니다. 충돌이 절대 없고, 필요시 복호화도 가능하며, 충분히 랜덤해 보입니다.
보안이 매우 중요하다면 애플리케이션 레벨에서 Hashids/Sqids 라이브러리를 함께 사용하는 것도 고려해보세요.