7P by GN⁺ | ★ favorite | 댓글 2개
  • Postgres 공식 문서는 훌륭하지만 Postgres 17 PDF가 3,200페이지에 달해, 초급자가 실무 전에 스키마 설계·SQL 동작·운영 함정을 모두 문서만으로 익히기 어려움
  • 특별한 이유가 없다면 데이터는 정규화하고, 읽기 성능을 위해 중복 데이터를 두는 비정규화는 불일치와 쓰기 복잡도라는 비용을 감수해야 함
  • SQL 키워드는 대소문자를 가리지 않지만, NULL은 “알 수 없음” 에 가까워 일반 언어의 null처럼 비교하면 예상과 다른 결과가 나옴
  • psql은 pager, \x, .psqlrc, \pset null, 자동완성, 백슬래시 명령, \copy만 잘 써도 출력 가독성·탐색·CSV 내보내기가 크게 편해짐
  • 인덱스·락·트랜잭션·JSONB는 강력하지만, 쿼리 계획과 운영 제약을 모르면 성능 저하나 가용성 문제로 이어질 수 있음

방대한 공식 문서 전에 알아둘 맥락

  • Postgres 공식 문서는 현재 버전 17 기준 US letter PDF로 출력하면 3,200페이지이며, A4로 출력하면 3,024페이지임
  • Postgres를 쓰기 전에 알면 좋은 실무 지식이 많고, 일부는 다른 SQL DBMS에도 적용될 수 있지만 적용 범위가 항상 확실한 것은 아님

데이터는 기본적으로 정규화하기

  • 정규화는 데이터베이스 스키마에서 중복되거나 불필요한 데이터를 제거하는 과정임
  • documents 테이블에 user_email을 직접 저장하면 사용자가 이메일을 바꿀 때 해당 사용자의 모든 문서 행을 갱신해야 함
    • 대신 documents의 각 행이 users 같은 다른 테이블의 행을 user_id 외래 키로 참조하게 만들 수 있음
  • “1st normal form” 같은 각 정규형을 모두 외울 필요는 없지만, 일반적인 정규화 과정은 더 유지보수하기 쉬운 스키마로 이어질 수 있음
  • 비정규화는 특정 데이터를 매번 재계산하지 않고 빠르게 읽기 위해 중복 데이터를 두는 방식임
    • 직원 교대 근무 앱에서는 올해 누적 근무 시간을 매번 모든 shift duration 합산으로 계산하지 않고, 주기적으로 또는 근무 시간 변경 시 계산해 저장할 수 있음
    • 이 데이터는 Postgres 내부에 둘 수도 있고 Redis 같은 캐시 계층에 둘 수도 있음
  • 비정규화에는 거의 항상 비용이 따르며, 대표적인 비용은 데이터 불일치 가능성과 쓰기 복잡도 증가임

Postgres 프로젝트의 “하지 말 것” 조언

  • 공식 Postgres 위키에는 “Don’t do this” 목록이 있음
  • 모든 항목을 이해하지 못해도 괜찮고, 이해하지 못하는 항목은 그 실수를 할 가능성도 낮음
  • 특히 다음 조언을 기억할 만함

SQL에서 헷갈리기 쉬운 동작

  • SQL 키워드는 대문자일 필요 없음

    • SQL 키워드는 대소문자를 구분하지 않음
    • 다음 쿼리들은 같은 의미임
    SELECT * FROM my_table WHERE x = 1 AND y > 2 LIMIT 10;
    select * from my_table where x = 1 and y > 2 limit 10;
    SELECT * from my_table WHERE x = 1 and y > 2 LIMIT 10;
    
    • 이 특성은 Postgres에만 한정된 것은 아님
  • NULL은 일반 언어의 null/nil과 다름

    • SQL의 NULL은 일반 프로그래밍 언어의 null이나 nil보다 “알 수 없음”에 가까움
    • NULL = NULLtrue가 아니라 NULL을 반환함
    • 한쪽이 NULL인 비교는 대부분 결과도 NULL
    • NULL 비교에는 다음 연산을 사용해야 함
      • x IS NULL: xNULL이면 true
      • x IS NOT NULL: xNULL이 아니면 true
      • x IS NOT DISTINCT FROM y: x = y와 비슷하지만 NULL을 일반 값처럼 취급
      • x IS DISTINCT FROM y: x != y/x <> y와 비슷하지만 NULL을 일반 값처럼 취급
    • WHERE 절은 조건이 true일 때만 행을 반환함
      • SELECT * FROM users WHERE title != 'manager'titleNULL인 행을 반환하지 않음
      • NULL != 'manager'의 결과가 NULL이기 때문임
    • COALESCE는 여러 인자 중 첫 번째 NULL이 아닌 값을 반환함
    COALESCE(NULL, 5, 10) = 5
    COALESCE(2, NULL, 9) = 2
    COALESCE(NULL, NULL) IS NULL
    

psql을 더 유용하게 쓰기

  • 출력 가독성 개선

    • 컬럼이 많거나 값이 긴 테이블을 조회했을 때 출력이 읽기 어렵다면 pager가 꺼져 있을 수 있음
    • 터미널 pager는 큰 텍스트나 psql 테이블을 viewport로 스크롤해 볼 수 있게 해줌
    • 컬럼이 많은 테이블은 \pset expanded 또는 \xexpanded mode를 켤 수 있음
    • 기본값으로 쓰고 싶다면 홈 디렉터리의 ~/.psqlrc\x를 추가하면 됨
  • NULL 출력 명확히 하기

    • 기본 설정은 출력에서 NULL 여부를 명확히 보여주지 않음
    • psql에서 NULL 표시 문자열을 지정할 수 있음
    \pset null '[NULL]'
    
    • Unicode 문자열도 가능하며, 기본값으로 쓰려면 ~/.psqlrc에 같은 명령을 추가하면 됨
  • 자동완성과 백슬래시 명령 활용

    • psql은 대화형 콘솔처럼 자동완성을 지원함
    • 키워드나 테이블 이름 일부를 입력한 뒤 Tab을 누르면 나머지를 채울 수 있음
    • 유용한 백슬래시 명령은 다음과 같음
      • \?: 모든 shortcut 목록
      • \d: relation, 즉 테이블과 시퀀스 목록 및 소유자 표시
      • \d+: \d에 크기와 일부 메타데이터 추가
      • \d table_name: 테이블 스키마, 컬럼 타입, nullable 여부, 기본값, 인덱스, 외래 키 제약 표시
      • \e: $EDITOR 환경 변수에 설정된 기본 편집기에서 쿼리 편집
      • \h SQL_KEYWORD: 해당 SQL 키워드 문법과 문서 링크 표시
  • CSV 내보내기와 SELECT 별칭

    • \copy로 쿼리 결과를 CSV로 저장할 수 있음
    \copy (select * from some_table) to 'my_file.csv' CSV
    
    • 컬럼명을 첫 줄에 포함하려면 HEADER 옵션을 추가함
    \copy (select * from some_table) to 'my_file.csv' CSV HEADER
    
    • \copy는 더 표준적인 COPY 문에 필요한 상승 권한을 피할 수 있음
    • SELECT 출력 컬럼은 AS로 별칭을 붙일 수 있음
    SELECT vendor, COUNT(*) AS number_of_backpacks
    FROM backpacks
    GROUP BY vendor
    ORDER BY number_of_backpacks DESC;
    
    • GROUP BYORDER BY에서는 SELECT 뒤에 등장한 컬럼 번호를 참조할 수 있음
    SELECT vendor, COUNT(*) AS number_of_backpacks
    FROM backpacks
    GROUP BY 1
    ORDER BY 2 DESC;
    
    • 이 축약형은 유용하지만, 프로덕션에 배포되는 쿼리에는 넣지 않는 편이 좋음

인덱스는 추가한다고 항상 쓰이지 않음

  • 인덱스와 쿼리 계획

    • 인덱스는 테이블 행을 특정 필드 기준으로 찾기 위한 바로가기 디렉터리 역할을 하는 데이터 구조임
    • 가장 흔한 인덱스는 B-tree이며, WHERE a = 3 같은 정확한 동등 조건과 WHERE a > 5 같은 범위 조건에 동작함
    • Postgres에 특정 인덱스를 쓰라고 직접 지시할 수는 없음
    • Postgres는 각 테이블에 대해 유지하는 통계를 바탕으로, 인덱스가 테이블을 처음부터 끝까지 읽는 sequential scan보다 빠를지 예측함
    • SELECT ... FROM ... 앞에 EXPLAIN을 붙이면 Postgres가 쿼리를 어떻게 실행할지에 대한 쿼리 계획을 볼 수 있음
    • 쿼리 계획을 읽을 때는 thoughtbot의 EXPLAIN ANALYZE 가이드, pganalyze 문서, 공식 문서, explain.depesz.com을 참고할 수 있음
  • 작은 테이블과 다중 컬럼 인덱스

    • 로컬 개발 DB처럼 행이 적은 테이블에서는 인덱스가 큰 도움이 되지 않을 수 있음
    • 100행 정도라면 Postgres가 인덱스보다 sequential scan이 빠르다고 판단할 수 있음
    • Postgres는 다중 컬럼 인덱스를 지원함
    CREATE INDEX CONCURRENTLY ON tbl (a, b);
    
    • WHERE a = 1 AND b = 2 같은 조건은 ab에 각각 별도 인덱스를 둔 경우보다 빠를 수 있음
    • 하나의 B-tree를 순회하면서 검색 조건을 효율적으로 결합할 수 있기 때문임
    • (a, b) 인덱스는 a만 필터링하는 쿼리도 a 단독 인덱스만큼 빠르게 함
    • WHERE b = 5 같은 쿼리는 빨라질 수도 있지만 최선은 아닐 수 있음
      • 인덱스가 먼저 a, 그다음 b로 키가 잡혀 있어 모든 a 값을 거쳐 b 값을 찾아야 함
    • 여러 컬럼 조합으로 쿼리해야 한다면 (a, b)b 단독 인덱스를 함께 두는 경우가 많음
    • 필요에 따라 a, b 각각의 단독 인덱스에 의존할 수도 있음
  • prefix match에는 text_pattern_ops 사용

    • materialized path 방식으로 계층형 디렉터리를 저장하고, 특정 prefix로 시작하는 모든 descendant를 찾아야 할 수 있음
    SELECT * FROM directories WHERE path LIKE '/1/2/3/%'
    
    • path 컬럼에 기본 B-tree 인덱스를 만들어도 이 쿼리에 사용되지 않을 수 있음
    CREATE INDEX CONCURRENTLY ON directories (path);
    
    • prefix match나 pattern match에 필요한 문자 단위 정렬을 가능하게 하려면 operator class를 지정해야 함
    CREATE INDEX CONCURRENTLY ON directories (path text_pattern_ops);
    

락과 트랜잭션이 만드는 운영 문제

  • Postgres의 락

    • 락(lock) 또는 mutex는 위험한 작업을 한 번에 한 클라이언트만 수행하게 하는 장치임
    • 데이터베이스에서 row, table, view 같은 개체 업데이트는 전체가 성공하거나 전체가 실패해야 하며, 동시 작업으로 일부만 성공하는 상황을 막기 위해 관련 개체에 락을 획득함
    • Postgres의 테이블 락 수준은 덜 제한적인 것부터 더 제한적인 것까지 여러 단계가 있음
      • ACCESS SHARE: SELECT
      • ROW SHARE: SELECT ... FOR UPDATE
      • ROW EXCLUSIVE: UPDATE, DELETE, INSERT
      • SHARE UPDATE EXCLUSIVE: CREATE INDEX CONCURRENTLY
      • SHARE: CREATE INDEX, 단 CONCURRENTLY 아님
      • ACCESS EXCLUSIVE: 많은 형태의 ALTER TABLE, ALTER INDEX
    • 한 테이블에서 다음 동작은 가능하거나 대기해야 함
      • UPDATESELECT: 가능
      • UPDATECREATE INDEX CONCURRENTLY: 가능
      • SELECTCREATE INDEX: 가능
      • SELECTALTER TABLE: 일반적으로 대기
      • ALTER TABLESELECT: 일반적으로 대기
    • 일부 ALTER TABLE 형태는 더 약한 락을 요구할 수 있으며, 전체 정보는 공식 명시적 락 문서operation별 락 충돌 가이드에서 확인할 수 있음
  • 느린 ALTER TABLE과 락 대기열

    • ALTER TABLE이 오래 걸리면 같은 테이블을 읽는 SELECT도 막힐 수 있음
    • 웹 앱의 모든 요청이 참조하는 users 같은 핵심 테이블이면 요청이 대기하다 timeout되고 503을 반환할 수 있음
    • 느린 ALTER TABLE의 흔한 원인은 다음과 같음
      • non-constant default가 있는 컬럼 추가
      • 컬럼 타입 변경
      • uniqueness constraint 추가
    • Postgres 11 이후에는 컬럼 추가 시 모든 default가 느리게 만드는 문제는 수정됐으며, non-constant default가 문제가 될 수 있음
    • ALTER TABLE 자체가 빠른 작업이어도 락을 얻기 전까지는 실행되지 않음
      • 예전부터 있던 내부 대시보드의 느린 SELECT가 실행 중이면 ALTER TABLE이 기다려야 함
    • Postgres 락은 대기열을 만들기 때문에, 대기 중인 ALTER TABLE 뒤에 들어온 같은 테이블의 후속 쿼리들도 기다릴 수 있음
    • 같은 시나리오는 Migrations and exclusive locks에서 더 살펴볼 수 있음
  • 장기 트랜잭션도 위험함

    • 트랜잭션은 여러 데이터베이스 문을 all-or-nothing으로 묶는 방식이며, BEGIN으로 시작하고 COMMIT으로 끝냄
    • 트랜잭션 중인 변경은 다른 클라이언트에 보이지 않으며, COMMIT 시 데이터베이스에 공개됨
    • 송금처럼 한 계좌 잔액 감소와 다른 계좌 잔액 증가가 함께 성공하거나 함께 취소돼야 하는 작업에 적합함
    • 트랜잭션이 락을 얻으면 COMMIT까지 락을 유지함
    • BEGIN 후 특정 row를 UPDATE하고 자리를 비우면, 다른 클라이언트의 해당 row DELETE는 트랜잭션이 commit될 때까지 멈춰 있음
    • 필요 이상으로 오래 열린 트랜잭션은 다른 클라이언트의 쿼리나 업데이트를 막을 수 있음

JSONB는 날카로운 도구

  • JSONB의 성능과 스키마 문제

    • JSONB는 유연하지만 잘못 쓰면 단점이 큼
    • Postgres는 JSONB 컬럼의 통계를 추적하지 않으므로, 단일 JSONB 컬럼에 대한 동등한 쿼리가 일반 컬럼 집합에 대한 쿼리보다 훨씬 느릴 수 있음
    • 한 사례에서는 JSONB 때문에 2000배 느려지는 예를 볼 수 있음
    • JSONB 컬럼에는 사실상 무엇이든 들어갈 수 있어 강력하지만, 구조에 대한 보장은 적음
    • 일반 테이블은 스키마를 보고 쿼리 결과를 예측할 수 있지만, JSONB는 key 이름이 camelCase인지 snake_case인지, 상태가 boolean인지 enum인지 확실하지 않음
    • 일반 Postgres 데이터가 가지는 정적 타입 특성이 JSONB에는 같은 방식으로 적용되지 않음
  • JSONB 타입 비교의 어색함

    • backpacks 테이블의 JSONB 컬럼 data에서 brand 필드가 JanSport인 행을 찾으려 할 때 다음 쿼리는 동작하지 않음
    select * from backpacks where data['brand'] = 'JanSport';
    
    • Postgres는 비교의 오른쪽 타입이 왼쪽 타입과 맞기를 기대하며, 오른쪽은 올바른 JSON 문서여야 함
    • JSON 문서는 객체, 배열, 문자열, 숫자, boolean, null이어야 하므로 JanSport 단독은 유효한 JSON이 아님
    • 올바른 쿼리는 JSON 문자열로 비교하거나, 왼쪽을 Postgres text로 변환하는 방식임
    select * from backpacks where data['brand'] = '"JanSport"';
    
    select * from backpacks where data['brand'] = '"JanSport"'::jsonb;
    
    select * from backpacks where data->>'brand' = 'JanSport';
    
    • SQL의 NULL과 JSONB의 null은 다르게 동작함
      • 'null'::jsonb = 'null'::jsonbtrue지만, NULL = NULLNULL
    • JSONB에는 전용 연산자와 함수가 많아 한 번에 기억하기 어려움
    • Postgres에는 JSON 값을 텍스트로 저장하는 JSON과 효율적인 바이너리 형식으로 변환하는 JSONB가 모두 있음
    • JSONB는 인덱싱 가능 같은 장점이 있으며, JSON 형식은 특수한 경우로 볼 수 있음
GeekNews Weekly에 포함된 글입니다. 에디터 코멘트 보기

댓글과 토론

하지 말아야할 것 언젠가 한번 읽어봐야겠어요

Hacker News 의견들
  • PostgreSQL은 대체로 대소문자 구분이 있지만, SQL 키워드를 대문자로 쓰는 건 보통 시각적 패턴 매칭으로 가독성을 높이려는 노력임
    꼭 필요한 건 아니지만, 남의 쿼리를 디버깅해야 한다면 prettifier에 넣어서 사소한 문법 모양에 걸리지 않고 정의를 빠르게 훑을 것 같음
    다른 언어의 코드 정리와 마찬가지로 일관된 들여쓰기 같은 시각 구조는 뻔한 부분을 이해하는 시간을 줄여주고 중요한 데 집중하게 해줌
    다만 actuallyUsingCaseInIdentifiers처럼 식별자에 실제로 대소문자를 섞는 건 정말 싫고, CLI에서 확인하려고 큰따옴표가 필요한 컬럼은 보고 싶지 않음

    • 대문자 식별자는 서로 바꿔 끼워도 될 블록처럼 보여서, 소문자가 가진 단어 형태보다 읽는 속도를 늦춤
    • SQL을 대화식으로 다룰 때 이 구분을 아는 게 꽤 유용함
      아무도 보지 않을 임시 쿼리를 빠르게 치고 버릴 거라면 대소문자를 신경 쓰지 않지만, 저장소에 커밋되는 SQL은 명령을 ALL CAPS로 씀
    • 대문자는 흑백 화면에서의 문법 강조 역할이었다고 이해하고 있음
      컬러가 있는 지금은 더 이상 필요 없지만, 오래된 기억이라 근거 자료는 없음
    • PostgreSQL은 식별자를 소문자로 접는 반면 표준은 대문자로 접기 때문에, 대소문자 처리에서는 표준을 어김
      그래도 따옴표 친 식별자와 안 친 식별자를 섞으면 안 되고, 내부 구조 조회도 대체로 표준화되어 있지 않아서 큰 의미는 없음
    • SQL용 prettifier나 린터 추천이 궁금함
  • PostgreSQL 위키의 “don’t do this” 항목은 처음 봤는데 꽤 유용함: https://wiki.postgresql.org/wiki/Don%27t_Do_This

    • 이런 기능들이 그렇게 쉬운 함정이라면 왜 폐기 예정으로 만들지 않는지 궁금함
      예를 들어 새 스키마에서는 테이블 상속 같은 기능을 비활성화하고, 다시 켜려면 일부러 복잡한 설정을 요구하는 편이 맞아 보임
    • SQL Anti-patterns가 떠오르며, 데이터베이스를 다루는 사람이라면 모두 읽어야 할 책이라고 봄
    • MySQL 쪽에서 익힌 몇 가지 습관을 다시 생각하게 됨
  • 여기 나온 많은 내용은 PostgreSQL에만 해당하지 않음
    NULL의 이상한 동작, 인덱스 컬럼 순서 같은 것들이 그렇고, 특히 NULL과 인덱스/유니크 제약의 상호작용은 MySQL에서도 직관적이지 않음
    예를 들어 email은 NULL 불가, username은 NULL 가능인 사용자 테이블에 (email, username) 유니크 제약을 걸면, username이 NULL인 동일한 email을 여러 번 넣을 수 있음. NULL은 다른 NULL과 같지 않기 때문임

  • “좋은 이유가 없으면 데이터를 정규화하라”는 식으로만 말하고 넘어가면 곤란함
    저자가 링크한 페이지에도 정규화 형태가 비정규형까지 포함해 11가지나 나오는데, 대부분은 그게 뭔지도 모르고 그중 7가지는 쓸 일도 없음
    더 높은 정규형을 찾아 헤매게 만들면 안 됨

    • 그래도 저자는 대체로 무슨 뜻인지 설명하는 문단을 붙였고, 그 방향은 맞다고 봄
      최근 옮겨 간 프로젝트에서도 이런 문제를 몇 개 고쳐야 했는데, 데이터를 중복할 이유는 거의 없음
    • 이 글이 초보자를 대상으로 한다면, 확신이 없을 때의 답은 거의 항상 제3정규형
    • 일반 규칙은 최대한 정규화한 뒤, 필요한 성능이 나올 때까지 비정규화하는 것임
  • 첫 번째 팁은 매일 VACUUM하라는 것임
    처음 시작할 때 이걸 몰라서 reddit 데이터베이스에 VACUUM을 전혀 안 했고, 어느 날 어쩔 수 없이 실행했다가 끝나길 기다리는 동안 reddit이 거의 하루 동안 내려갔음

    • autovacuum이 없었나 봄
      reddit 규모라면 트랜잭션 ID가 먼저 고갈되지 않은 게 놀라움
  • 개발자들이 정규화를 더 신경 쓰고, 모든 걸 JSONB 컬럼에 밀어 넣는 일을 그만했으면 좋겠음

    • 데이터베이스가 구조화된 JSON을 저장할 수 있기 훨씬 전에도 주니어 개발자들은 적절한 정규화 정도를 두고 격렬하게 탁상공론을 벌였음
      더 경험 많은 개발자들은 정답이 키를 제외하면 아무것도 중복하지 않고, 정말 마지못해 비정규화하는 것임을 알고 있었음
      이후 Mongo 같은 데이터베이스가 등장해 정규화가 어렵거나 무의미한 “데이터베이스 비슷한 것”을 제공하면서 그런 주니어들을 부추겼고, 그 결과 끔찍한 데이터베이스 설계와 유지보수 불가능한 쓰레기 탑이 잠깐 번성함
      이제 진자는 돌아와 정규화된 데이터베이스의 장점을 다시 발견했지만, JSON 컬럼은 여전히 나쁜 관행이 자랄 수 있는 탈출구가 됨
    • JSONB 컬럼을 쓰는 이유는 두 가지가 있음
      첫째, JSON을 저장하기 위해서임. 웹서버가 서드파티 API를 호출할 때 원본 API 응답을 JSONB 컬럼에 저장한 뒤 거기서 처리하면, 해당 API에서 온 문제를 디버깅할 때 감사 가능한 기록이 남음
      둘째, 합 타입(sum type)을 저장하기 위해서임. SQL이 합 타입을 지원하지 않는 건 SQL 데이터베이스에서 데이터를 모델링할 때 가장 큰 결함이라고 볼 수 있음
      우회책은 여러 가지가 있고 “그냥 JSONB 컬럼에 넣고 애플리케이션에서 검증하기”도 그중 하나지만, 어떤 우회책도 특별히 훌륭하진 않음
    • 정규화를 신경 써도 결국 잡동사니 JSONB 서랍이 생기는 경우가 많음
      JSONB 안의 값을 별도 컬럼으로 끌어올리지 않고 그 안에 나쁜 쿼리를 쓰는 게 아니라면, 그 자체가 큰 문제라고 보진 않음
    • 요즘 이런 도구를 쓰는 개발자 대부분은 사실상 자기만의 데이터베이스 관리 시스템을 만들고, 영속성만 다른 DBMS에 맡기고 있음
      영속성 요구만 성공적으로 만족하면 좋은 설계를 고민해야 할 강한 압박이 없기 때문임
      DBMS 위에 또 DBMS를 만드는 게 맞는지는 의문이지만, 어쨌든 현재 상태는 그렇다
    • 이 방식이 제대로 되려면 스키마 변경을 되돌릴 수 있는 능력까지 포함한 스키마 마이그레이션 절차가 필요함
      새 컬럼이 성능을 망치거나 문제를 일으키면 되돌릴 수 있어야 함
      CLI 도구가 관련되어 있다면 어느 정도 다운타임을 감수할 수 있는지, 회사 전체의 동기화된 버전 업데이트가 가능한지, 아니면 한동안 구·신 스키마를 모두 지원할지까지 처리해야 함
      데이터베이스가 팀의 주력 제품 일부가 아니라면 이런 것들이 전부 빠져 있을 수 있음
  • 초보자를 돕기 위해 이 글을 썼음: https://tomcam.github.io/postgres/

  • 글이 정말 좋고, PostgreSQL 문서가 3200페이지나 된다는 건 몰랐음
    한동안 써오면서 필요할 때마다 배우고 있는데 공식 문서도 꽤 좋아하고, 특정 주제가 필요해질 때 관련 글을 읽는 것도 좋아함
    저자가 https://challahscript.com/what_i_wish_someone_told_me_about_...(b, a) 컬럼 인덱스는 b만으로 조회할 때 잘 동작한다고 덧붙이면 독자에게 도움이 될 것 같음
    a만으로 조회하는 이야기를 할 때 어느 정도 암시되어 있지만, 더 명시해도 나쁘지 않음
    JSON/JSONB 부분은 거의 쓰지 않아서 많이 보진 않았음

  • 실제 현장에서 본 우스꽝스러운 SQL들을 떠올리면, Codd 논문을 읽고 관계형 모델이 무엇인지 이해하려는 것부터 시작하면 좋겠음
    11페이지밖에 안 되며, 그걸 읽는 것만으로도 이 세상의 고통이 줄어들 것임

  • 이 글의 거의 전부는 MySQL 같은 다른 MVCC 데이터베이스에도 적용됨
    세부 사항은 다를 수 있지만, MySQL도 긴 트랜잭션에 시달리고 ALTER 중 메타데이터 잠금을 잡는 등 비슷한 재미난 문제들이 있음