정수가 아닌 기본 키 고려 사항


16

문맥

분산 응용 프로그램의 데이터를 저장할 데이터베이스 (PostgreSQL 9.6에서)를 설계하고 있습니다. 응용 프로그램의 분산 특성으로 인해 SERIAL잠재적 인 경쟁 조건으로 인해 자동 증가 정수 ( )를 기본 키로 사용할 수 없습니다 .

자연스러운 솔루션은 UUID 또는 전역 고유 식별자를 사용하는 것입니다. 포스트 그레스가 함께 제공되는 내장 UUID타입 안성맞춤이다.

UUID와 관련된 문제는 디버깅과 관련이 있습니다. 사람에게 친숙하지 않은 문자열입니다. 식별자 ff53e96d-5fd7-4450-bc99-111b91875ec5는 나에게 아무것도 말하지 않지만 ACC-f8kJd9xKCd, 고유하지는 않지만 ACC객체를 다루고 있다고 알려줍니다 .

프로그래밍 관점에서 볼 때 여러 다른 객체와 관련된 응용 프로그램 쿼리를 디버깅하는 것이 일반적입니다. 프로그래머 ACCORD(순서) 테이블 에서 (계정) 오브젝트를 잘못 검색한다고 가정하십시오 . 사람이 읽을 수있는 식별자를 사용하여 프로그래머는 문제를 즉시 식별하고 UUID를 사용하면 무엇이 잘못되었는지 파악하는 데 시간을 소비합니다.

UUID의 "보장 된"고유성이 필요하지 않습니다. 내가 충돌없이 키를 생성하기위한 약간의 공간이 필요하지만, UUID는 과잉이다. 또한 최악의 시나리오에서는 충돌이 발생한 경우 (데이터베이스가이를 거부하고 응용 프로그램을 복구 할 수있는 경우) 끝이 아닙니다. 따라서 작지만 인간 친화적 인 식별자가 내 사용 사례에 이상적인 솔루션이라고 생각합니다.

응용 프로그램 객체 식별

내가 찾은 식별자의 형식은 다음과 같습니다 {domain}-{string}. 여기서 {domain}개체 도메인 (계정, 주문, 제품)으로 바뀌고 {string}무작위로 생성 된 문자열입니다. 경우에 {sub-domain}따라 임의 문자열 앞에 a를 삽입하는 것이 좋습니다. 고유성을 보장하기위한 길이 {domain}{string}목적을 무시합시다 .

인덱싱 / 쿼리 성능에 도움이되는 경우 형식의 크기가 고정 될 수 있습니다.

문제

그것을 아는 것은:

  • 와 같은 형식의 기본 키를 갖고 싶습니다 ACC-f8kJd9xKCd.
  • 이 기본 키는 여러 테이블의 일부입니다.
  • 이 모든 키는 6NF 데이터베이스의 여러 조인 / 관계에서 사용됩니다.
  • 대부분의 테이블은 중간에서 큰 크기 (평균 ~ 1M 행, 최대 ~ 100M 행)를 갖습니다.

성능과 관련하여이 키를 저장하는 가장 좋은 방법은 무엇입니까?

다음은 가능한 네 가지 솔루션이지만 데이터베이스에 대한 경험이 거의 없기 때문에 어느 것이 가장 좋은지 잘 모르겠습니다.

고려되는 솔루션

1. 문자열 ( VARCHAR) 로 저장

(포스트 그레스 사이에 차이가 없습니다 CHAR(n)VARCHAR(n)내가 무시 해요, 그래서 CHAR).

몇 가지 조사를 거친 후 VARCHAR, 특히 조인 작업에서을 사용한 문자열 비교 가을 사용하는 것보다 느리다 는 것을 알았습니다 INTEGER. 이것은 의미가 있지만,이 규모에서 걱정해야 할 것이 있습니까?

2. 바이너리 ( bytea) 로 저장

Postgres와 달리 MySQL에는 기본 UUID유형 이 없습니다 . BINARY36 바이트가 아닌 16 바이트 필드를 사용하여 UUID를 저장하는 방법을 설명하는 여러 게시물이 있습니다 VARCHAR. 이 게시물은 키를 바이너리 ( byteaPostgres) 로 저장한다는 아이디어를주었습니다 .

이것은 크기를 절약하지만 성능에 더 관심이 있습니다. 바이너리 또는 문자열 비교가 더 빠른 설명을 찾는 것은 운이 없었습니다. 이진 비교가 더 빠르다고 생각합니다. 이러한 경우 다음 bytea아마도 더 나은보다 VARCHAR프로그래머가 지금 인 코드 / 디코드에 데이터마다에도 불구하고.

제가 틀릴 수도 있지만, 나는 모두를 생각 bytea하고 VARCHAR(문자 또는 문자) 바이트 (평등) 바이트를 비교합니다. 이 단계별 비교를 "건너 뛰고" "전체"를 간단히 비교할 수있는 방법이 있습니까? (나는 그렇게 생각하지 않지만 검사 비용은 들지 않습니다).

나는 저장하는 bytea것이 최선의 해결책 이라고 생각 하지만, 내가 무시하고있는 다른 대안이 있는지 궁금합니다. 또한 솔루션 1에서 표현한 것과 동일한 우려가 적용됩니다. 비교에 대한 오버 헤드가 충분히 걱정해야합니까?

"크리에이티브"솔루션

나는 작동 할 수있는 두 가지 매우 "창의적 인"솔루션을 생각해 냈습니다. 어느 정도인지 확실하지 않습니다 (즉, 테이블에서 수천 행 이상으로 확장하는 데 문제가있는 경우).

3. UUID"라벨"이 붙어있는 그대로 보관하십시오.

UUID를 사용하지 않는 주된 이유는 프로그래머가 응용 프로그램을 더 잘 디버깅 할 수 있기 때문입니다. 그러나 두 가지를 모두 사용할 수 있다면 어떻게 될까요? 데이터베이스는 모든 키를 UUIDs로만 저장 하지만 쿼리가 시작되기 전 / 후에 개체를 래핑합니다.

예를 들어, 프로그래머는을 요청 ACC-{UUID}하고 데이터베이스는 ACC-부품을 무시 하고 결과를 가져 와서 모두로 반환합니다 {domain}-{UUID}.

아마도 저장 프로시 저나 함수가있는 일부 해커에서 가능할 수도 있지만 몇 가지 질문이 떠 오릅니다.

  • 이것이 (각 쿼리에서 도메인 제거 / 추가) 상당한 오버 헤드입니까?
  • 이것이 가능합니까?

전에는 저장 프로시 저나 함수를 사용한 적이 없으므로 이것이 가능한지 확실하지 않습니다. 누군가 빛을 비출 수 있습니까? 프로그래머와 저장된 데이터 사이에 투명 레이어를 추가 할 수 있다면 완벽한 솔루션 인 것 같습니다.

4. (가장 좋아하는) IPv6으로 저장 cidr

네, 잘 읽어보세요. IPv6 주소 형식이 내 문제를 완벽하게 해결하는 것으로 나타났습니다 .

  • 처음 몇 옥텟에서 도메인과 하위 도메인을 추가하고 나머지를 임의 문자열로 사용할 수 있습니다.
  • 충돌 확률은 OK입니다. (하지만 2 ^ 128을 사용하지는 않지만 여전히 괜찮습니다.)
  • 평등 비교는 (희망적으로) 최적화되어 있으므로 간단히 사용하는 것보다 더 나은 성능을 얻을 수 있습니다 bytea.
  • 실제로 contains도메인과 해당 계층 구조가 어떻게 표현되는지에 따라 와 같은 흥미로운 비교를 수행 할 수 있습니다 .

예를 들어 0000도메인 "제품"을 나타내는 코드 를 사용한다고 가정 합니다. 키 0000:0db8:85a3:0000:0000:8a2e:0370:7334는 제품을 나타냅니다 0db8:85a3:0000:0000:8a2e:0370:7334.

여기서 주요 질문은 다음과 같습니다.에 비해 데이터 유형 bytea사용에 대한 주요 장점 또는 단점이 cidr있습니까?


5
얼마나 많은 분산 노드가 가능합니까? 당신은 그들의 번호와 이름을 미리 알고 있습니까? 복합 (다중 열) PK를 고려 했습니까? 도메인 (첫 번째 질문에 따라 다름)과 일반 연속 열은 가장 작고 단순하며 가장 빠를 수 있습니다.
Erwin Brandstetter

감사합니다! @ErwinBrandstetter 응용 프로그램과 관련하여 응용 프로그램은 부하에 따라 자동 확장되도록 설계되어 있으므로 미리 정보가 거의 없습니다. 나는 (도메인, UUID)를 PK로 사용하는 것에 대해 생각했지만, 이것은 "도메인"을 계속 반복 할 것이며 도메인은 여전히 varchar많은 다른 문제 중 하나입니다. 나는 pg의 도메인에 대해 몰랐다. 주어진 쿼리가 올바른 객체를 사용하고 있는지 확인하는 데 도메인이 사용되는 것을 보았지만 여전히 정수가 아닌 인덱스를 사용해야합니다. serial하나의 잠금 단계없이 여기에 "안전한"사용 방법이 있는지 확실하지 않습니다 .
Renato Siqueira Massaro

1
도메인이 반드시이어야하는 것은 아닙니다 varchar. 그에게하고 고려 FK integer유형과 이에 대한 조회 테이블을 추가합니다. 이렇게하면 사람이 쉽게 읽을 수 있고 PK삽입 / 업데이트 이상 (존재하지 않는 도메인을 두는 것)으로부터 컴포지트 를 보호 할 수 있습니다 .
yemet


1
와 같은 형식의 기본 키를 갖고 싶습니다 ACC-f8kJd9xKCd. ← 이것은 좋은 오래된 복합 PRIMARY KEY의 일인 것 같습니다 .
MDCCL

답변:


5

사용 ltree

IPV6이 작동하면 좋습니다. "ACC"는 지원하지 않습니다. ltree그렇습니다.

레이블 경로는 점으로 구분 된 0 개 이상의 레이블 시퀀스입니다 (예 : L1.L2.L3). 계층 트리의 루트에서 특정 노드까지의 경로를 나타냅니다. 레이블 경로의 길이는 65kB 미만이어야하지만 2kB 미만으로 유지하는 것이 좋습니다. 실제로 이것은 큰 제한이 아닙니다. 예를 들어, DMOZ 카탈로그 ( http://www.dmoz.org ) 에서 가장 긴 레이블 경로 는 약 240 바이트입니다.

이런 식으로 사용하면

CREATE EXTENSION ltree;
SELECT replace('ACC-f8kJd9xKCd', '-', '.')::ltree;

샘플 데이터를 만듭니다.

SELECT x, (
  CASE WHEN x%7=0 THEN 'ACC'
    WHEN x%3=0 THEN 'XYZ'
    ELSE 'COM'
  END ||'.'|| md5(x::text)
  )::ltree
FROM generate_series(1,10000) AS t(x);

CREATE INDEX ON foo USING GIST (ltree);
ANALYZE foo;


  x  |                ltree                 
-----+--------------------------------------
   1 | COM.c4ca4238a0b923820dcc509a6f75849b
   2 | COM.c81e728d9d4c2f636f067f89cc14862c
   3 | XYZ.eccbc87e4b5ce2fe28308fd9f2a7baf3
   4 | COM.a87ff679a2f3e71d9181a67b7542122c
   5 | COM.e4da3b7fbbce2345d7772b0674a318d5
   6 | XYZ.1679091c5a880faf6fb5e6087eb1b2dc
   7 | ACC.8f14e45fceea167a5a36dedd4bea2543
   8 | COM.c9f0f895fb98ab9159f51fd0297e236d

그리고 비올라 ..

                                                          QUERY PLAN                                                          
------------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on foo  (cost=103.23..234.91 rows=1414 width=57) (actual time=0.422..0.908 rows=1428 loops=1)
   Recheck Cond: ('ACC'::ltree @> ltree)
   Heap Blocks: exact=114
   ->  Bitmap Index Scan on foo_ltree_idx  (cost=0.00..102.88 rows=1414 width=0) (actual time=0.389..0.389 rows=1428 loops=1)
         Index Cond: ('ACC'::ltree @> ltree)
 Planning time: 0.133 ms
 Execution time: 1.033 ms
(7 rows)

자세한 내용 과 연산자 는 문서를 참조하십시오

제품 ID를 만드는 경우 ltree를 사용합니다. 그것들을 만들 무언가가 필요하다면 UUID를 사용합니다.


1

bytea와의 성능 비교에 관한 것입니다. 네트워크 비교는 먼저 네트워크 부분의 공통 비트, 네트워크 부분의 길이 및 전체 마스크되지 않은 주소에서 3 단계로 수행됩니다. 참조 : network_cmp_internal

그래서 bytea보다 조금 느려 야합니다. 단일 행을 찾고있는 천만 개의 행이있는 테이블에서 간단한 테스트를 실행했습니다.

  • 숫자 ID (정수)를 사용하면 1000ms가 걸렸습니다.
  • cidr을 사용하면 1300ms가 걸렸습니다.
  • bytea를 사용하면 1250ms가 걸렸습니다.

나는 bytea와 cidr 사이에 많은 차이가 있다고 말할 수는 없습니다 (간격이 일정하게 유지되었지만) 추가 if진술-10m 튜플에는 그렇게 나쁘지 않다고 추측하십시오.

그것이 도움이되기를 바랍니다-당신이 무엇을 선택했는지 듣고 싶어합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.