This page contains information related to upcoming products, features, and functionality. It is important to note that the information presented is for informational purposes only. Please do not rely on this information for purchasing or planning purposes. As with all projects, the items mentioned on this page are subject to change or delay. The development, release, and timing of any products, features, or functionality remain at the sole discretion of GitLab Inc.
Status Authors Coach DRIs Owning Stage Created
proposed -

읽기 전용 데이터

이 문서에서는 데이터베이스 확장성 작업 그룹에서 소개된 읽기 전용 패턴에 대해 설명합니다. 읽기 전용 데이터의 특성을 논의하고 GitLab 개발에 대해 이 문맥에서 고려해야 할 모베스트 프랙티스를 제안합니다.

읽기 전용 데이터의 특성

이름에서 이미 알 수 있듯이 읽기 전용 데이터는 업데이트보다 훨씬 더 자주 읽히는 데이터에 관한 것입니다. 이 데이터를 업데이트, 삽입 또는 삭제를 통해 쓰는 일은 데이터를 읽는 것에 비해 매우 드문 일입니다.

또한, 이 문맥에서의 읽기 전용 데이터는 일반적으로 작은 데이터 세트입니다. 여기서는 대형 데이터 세트를 다루지 않지만, 대형 데이터 세트도 “한 번 쓰고 자주 읽는” 특성을 가지고 있습니다.

예: 라이센스 데이터

대표적인 예를 들어보겠습니다: GitLab의 라이센스 데이터. GitLab 인스턴스에는 GitLab 엔터프라이즈 기능을 사용하기 위한 라이센스가 첨부될 수 있습니다. 이 라이센스 데이터는 인스턴스 전역에 유지되며 일반적으로 몇 가지 관련 레코드만 존재합니다. 이 정보는 매우 작은 licenses 테이블에 유지됩니다.

위에서 소개한 특성을 따르기 때문에 이를 읽기 전용 데이터로 간주합니다.

  • 드문한 쓰기: 라이센스 데이터는 라이센스를 삽입한 후에 매우 드뭅니다.
  • 빈번한 읽기: 라이센스 데이터는 엔터프라이즈 기능을 사용할 수 있는지 확인하기 위해 매우 자주 읽힙니다.
  • 작은 크기: 이 데이터 세트는 매우 작습니다. GitLab.com에서는 총 관계 크기가 50KB미만인 5개 레코드가 있습니다.

규모에 따른 읽기 전용 데이터의 영향

이 데이터 세트가 작고 매우 자주 읽힌다는 점을 고려하면 데이터가 거의 항상 데이터베이스 캐시나 디스크 캐시에 남아있을 것으로 예상할 수 있습니다. 따라서 읽기 전용 데이터에 대한 우려는 일반적으로 디스크에서 데이터를 읽지 않기 때문에 데이터베이스 I/O 오버헤드 주변에 집중되는 경우가 많습니다.

그러나 높은 빈도의 읽기를 고려할 때, 이는 데이터베이스 CPU 부하와 데이터베이스 컨텍스트 전환 관점에서 오버헤드를 발생시킬 가능성이 있습니다. 또한, 이러한 빈도가 높은 쿼리는 전체 데이터베이스 스택을 통해 실행됩니다. 그들은 또한 데이터베이스 연결 멀티플렉싱 구성요소와 로드 밸런서에 오버헤드를 발생시킵니다. 또한, 애플리케이션은 정보를 검색하기 위해 쿼리를 준비하고 보내고, 결과를 역직렬화하고, 새 객체를 할당하는 데에 주기를 보내는 과정에서 CPU 사이클을 소비합니다.

위에서 소개한 라이센스 데이터 예시에서 라이센스 데이터를 읽는 쿼리는 쿼리 빈도 측면에서 돋보였습니다. 실제로 피크 시간에 클러스터에서 초당 약 6,000개의 쿼리(QPS)를 보았습니다. 당시 클러스터 크기로는 각 복제본에서 약 1,000개의 QPS, 기본 데이터베이스에서는 피크 시간에 400개 미만의 QPS를 보았습니다. 이 차이는 순수한 읽기 전용 트랜잭션에 대해 복제본을 선호하는 저희의 스케일링을 위한 데이터베이스 로드 밸런싱 때문입니다.

라이센스 호출

당시 기본 데이터베이스의 전반적인 트랜잭션 처리량은 초당 50,000에서 70,000개의 트랜잭션(TPS) 사이로 변동했습니다. 비교적, 이 쿼리 빈도는 전체 쿼리 빈도에서 작은 부분을 차지했습니다. 그러나 이것이 여전히 데이터베이스 컨텍스트 전환 측면에서 상당한 오버헤드를 가질 것으로 기대합니다. 가능하다면 이 오버헤드를 없애는 것이 가치가 있는 일입니다.

읽기 전용 데이터를 인식하는 방법

예제에서처럼 명확한 경우 외에는 읽기 전용 데이터를 인식하는 것이 어려울 수 있습니다.

하나의 접근 방법은 예를 들어, 기본에서 읽기/쓰기 비율과 통계를 살펴보는 것입니다. 여기서 우리는 60분 동안의 TOP20 테이블을 읽기/쓰기 비율로 살펴봅니다(피크 트래픽 시간에 측정됨):

bottomk(20,
avg by (relname, fqdn) (
  (
      rate(pg_stat_user_tables_seq_tup_read{env="gprd"}[1h])
      +
      rate(pg_stat_user_tables_idx_tup_fetch{env="gprd"}[1h])
  ) /
  (
      rate(pg_stat_user_tables_seq_tup_read{env="gprd"}[1h])
      + rate(pg_stat_user_tables_idx_tup_fetch{env="gprd"}[1h])
      + rate(pg_stat_user_tables_n_tup_ins{env="gprd"}[1h])
      + rate(pg_stat_user_tables_n_tup_upd{env="gprd"}[1h])
      + rate(pg_stat_user_tables_n_tup_del{env="gprd"}[1h])
  )
) and on (fqdn) (pg_replication_is_replica == 0)
)

이는 어떤 테이블이 읽기보다 쓰기가 훨씬 더 자주 발생하는지에 대한 좋은 인상을 줍니다(기본 데이터베이스 기준):

읽기 쓰기 비율 TOP20

여기서부터 확대해서 예를 들어 gitlab_subscriptions에서 인덱스 읽기가 전반적으로 초당 10,000개의 튜플을 피크로치는 것을 알 수 있습니다(순차 스캔은 없음):

구독: 읽기

우리는 이 테이블에 매우 드물게 쓰기를 합니다(순차 스캔은 없음):

구독: 쓰기

게다가, 이 테이블의 크기는 400MB에 불과하므로 이 또한 이 패턴에서 고려해볼 수 있는 후보일 수 있습니다(참고: #327483).

대규모 읽기 중심 데이터 처리를 위한 최상의 방법

읽기 중심 데이터 캐시

데이터베이스 오버헤드를 줄이기 위해 데이터에 대한 캐시를 구현하여 데이터베이스 측의 쿼리 빈도를 크게 줄입니다. 가능한 다양한 캐싱 범위가 있습니다.

위의 예시를 계속하면, 우리는 RequestStore를 사용하여 요청당 라이센스 정보를 캐시했습니다. 그러나 여전히 요청당 한 번의 쿼리를 유발합니다. 우리가 라이센스 정보를 1초 동안 프로세스 내 인메모리 캐시를 사용하여 캐시하기 시작하자, 쿼리 빈도가 극적으로 감소했습니다:

Licenses Calls - Fixed

여기서 캐싱의 선택은 대상 데이터의 특성에 크게 의존합니다. 거의 결코 업데이트되지 않는 작은 데이터 집합인 라이센스 데이터는 인메모리 캐싱에 적합한 후보입니다. 여기서 프로세스 단위 캐시가 유리합니다. 왜냐하면 이렇게 하면 캐시 갱신 속도가 들어오는 요청 속도와 연동되지 않기 때문입니다.

이때 주의할 점은 현재 우리의 Redis 설정이 Redis의 보조를 사용하고 있지 않으며 캐싱에 단일 노드에 의존하고 있다는 것입니다. 이는 Redis가 압력 증가로 인해 다운되는 것을 피하기 위해 균형을 맞추어야 한다는 점입니다. 비교적, PostgreSQL 자복본에서 데이터를 읽는 것은 여러 읽기 전용 자복본에 분산될 수 있습니다. 데이터베이스로부터 데이터를 읽는 것이 더 비용이 들 수 있더라도 부하가 여러 노드에 고르게 분산됩니다.

읽기 중심 데이터를 복제본에서 읽기

캐싱을 구현했는지 여부에 관계없이 가능하면 데이터베이스 복제본에서 데이터를 읽도록 해야 합니다. 이렇게 하면 많은 데이터베이스 복제본에 읽기를 분산시키고 데이터베이스 주 서버로부터 불필요한 작업 부하를 줄일 수 있습니다.

GitLab의 읽기용 데이터베이스 부하 균형은 처음 쓰기 후나 명시적 트랜잭션을 열었을 때에는 주 서버에 고정됩니다. 읽기 중심 데이터에 대한 맥락에서 해당 데이터를 트랜잭션 범위 외부에서 읽도록 하고 어떠한 쓰기도 하기 전에 우리의 노력을 지원합니다. 이는 이 데이터가 드물게 업데이트되기 때문에 (따라서 우리는 종종 약간 오래된 데이터를 읽는 데 문제를 겪지 않습니다) 이것은 종종 가능합니다. 그러나 지난 쓰기나 트랜잭션으로 인해 해당 쿼리를 복제본에 보내지 못할 수 있다는 점은 명확하지 않을 수 있습니다. 따라서 읽기 중심 데이터를 만났을 때, 이 데이터가 복제본에서 읽을 수 있는지를 확인하고 확실해져야 하는 것이 바람직한 실천입니다.