큰 텍스트 답변에서 PostgreSQL에서 고유 인덱스를 빌드하는 방법: SHA512가 SHA256보다 빠릅니까?

7442 단어 railsdatabasepostgres
관계형 데이터베이스 스키마는 이를 사용하는 앱과 최대한 독립적이어야 한다고 생각합니다(이 규칙에 대한 예외가 있지만 이 게시물에는 적용되지 않음).

이를 수행하는 한 가지 방법은 Rails가 다음과 같은 문을 모델링하는지 확인하는 것입니다.

validates username, uniqueness: true


PostgreSQL의 실제unique indexes에 해당합니다.

그 두 가지 이유:
  • DBMS가 작업을 수행하도록 하고 제약 조건을 확인하도록 구축되었습니다
  • .
  • 데이터는 모든 종류의 방법에서 "들어갈 수"있습니다(예: SQL 스크립트 폐기).

  • 오늘날 데이터베이스가 단일 앱에서만 사용된다고 해도 미래에는 둘 이상이 있을 수 있으며 기존 테이블에 인덱스를 추가하거나 큰 테이블에서 중복 행을 정리해야 하는 것은 항상 약간의 고통입니다(잠금 때문에 그것에 대해 다른 기사를 작성할 수도 있습니다..).

    그때 무슨 일이 있었나요?



    그것은 곧은 것 같죠? 인덱스가 필요한 열을 나열하고, 이에 대한 Rails 마이그레이션을 작성하고, 마이그레이션을 실행하고, 잊어버리십시오.

    무작위 테스트가 문자 그대로 나를 감독에서 구한 곳입니다.

    RSS 피드에서 20개 이상의 항목을 가져와 기사로 변환하고 DB에 삽입한 다음 개수를 확인하여 일치하는지 확인하는 테스트가 코드베이스에 있습니다.

    그것들은 모두 다른 기사이지만 데이터베이스는 어쨌든 고유한지 확인할 것입니다(명백한 이유로).

    카운트가 일치하지 않았고 매우 심각한 디버깅 마술(일명 중단점 설정 및 인쇄 항목) 후에 다음을 발견했습니다.

    [1] pry(#<RssReader>)> p e
    #<ActiveRecord::StatementInvalid: PG::ProgramLimitExceeded: ERROR:  index row size 7280 exceeds btree version 4 maximum 2704 for index "index_articles_on_body_markdown_and_user_id_and_title"
    DETAIL:  Index row references tuple (8,1) in relation "articles".
    HINT:  Values larger than 1/3 of a buffer page cannot be indexed.
    Consider a function index of an MD5 hash of the value, or use full text indexing.
    : INSERT INTO "articles" ("body_markdown", "boost_states", "cached_tag_list", "cached_user", "cached_user_name", "cached_user_username", "created_at", "description", "feed_source_url", "password", "path", "processed_html", "published_from_feed", "reading_time", "slug", "title", "updated_at"
    


    무엇을 기다립니다!?

    약간의 파기 후에 나는 내 부주의를 깨달았습니다. 인덱싱할 텍스트가 너무 크고 PostgreSQL 버퍼 페이지에 맞지 않으면 인덱싱이 작동하지 않을 것입니다.

    PostgreSQL 버퍼 페이지 크기는 확대될 수 있지만 이는 요점을 벗어나는 것이며 좋은 생각도 아닙니다.

    그래서 해결책은 무엇입니까?



    해결책은 열의 해시를 만들고 열 자체 대신 인덱스를 만드는 것입니다.

    이에 대해 여러 가지 방법이 있지만 이것이 우리의 특정 상황에 대해 선택한 것입니다.

    CREATE UNIQUE INDEX CONCURRENTLY "index_articles_on_digest_body_markdown_and_user_id_and_title"
    ON "articles"
    USING btree (digest("body_markdown", 'sha512'::text), "user_id", "title");
    


    분해해 보겠습니다.
  • CREATE UNIQUE INDEX는 설명이 필요 없습니다. 동일한 값을 두 번 삽입할 수 없도록 열에 인덱스를 생성합니다
  • .
  • CONCURRENTLY는 PostgreSQL 영역에서 큰 변화입니다. 간단히 말해서 백그라운드에서 인덱스를 비동기적으로 추가합니다. 기본적으로 인덱스가 작성되는 동안 테이블에 대한 작업을 차단하지 않습니다.
  • btreestandard default index for PostgreSQL
  • digest("body_markdown", 'sha512'::text)는 마법이 일어나는 곳입니다. 우리는 PostgreSQL에게 SHA512 해시를 생성하도록 지시하고(MD5 😅) 인덱스 비교를 위해 그것을 사용합니다
  • .
  • "user_id", "title" 단일 컬럼에 대한 인덱스가 아니라 멀티 컬럼 인덱스
  • 이기 때문에 존재합니다.

    데이터베이스에 값을 두 번 추가하려고 하면 다음과 같이 됩니다.

    $ pgcli PracticalDeveloper_development
    PracticalDeveloper_development> insert into articles (body_markdown, user_id, title, created_at, updated_at) select body_markdown, user_id, title, now(), now() from articles order by random() limit 1;
    duplicate key value violates unique constraint "index_articles_on_digest_body_markdown_and_user_id_and_title"
    DETAIL:  Key (digest(body_markdown, 'sha512'::text), user_id, title)=(\x1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75, 10,  The Curious Incident of the Dog in the Night-Time Voluptas quia) already exists.
    


    일반 psql 대신 사용하는 pgcli에 대한 보너스 팁

    이 조사의 결과는 .

    좋은 웹페이지 즐겨찾기