시간 감쇠 데이터

본 문서는 Database Scalability Working Group에서 소개된 시간 감쇠 패턴에 대해 설명합니다. 시간 감쇠 데이터의 특성 및 GitLab 개발을 위해 이 문맥에서 고려해야 할 모베스트 프랙티스에 대해 논의합니다.

일부 데이터셋은 최근 데이터가 오래된 데이터보다 훨씬 자주 액세스되는 강력한 시간 감쇠 효과에 영향을 받습니다. 시간 감쇠의 또 다른 측면은 시간이 흐름에 따라 일부 유형의 데이터가 덜 중요해진다는 것입니다. 이는 우리가 오래된 데이터를 적게 힘든(사용 가능성이 떨어지는) 저장소로 이동하거나 극단적인 경우에는 데이터를 삭제할 수도 있다는 것을 의미합니다.

이러한 효과들은 일반적으로 제품 또는 애플리케이션 의미론에 기인합니다. 이러한 효과들은 오래된 데이터가 얼마나 자주 액세스되는지, 과거 데이터가 사용자 또는 애플리케이션에 얼마나 유용하거나 필요한지에 따라 다양할 수 있습니다.

먼저, 데이터에 시간과 관련이 없는 엔티티를 고려해 봅시다.

사용자 또는 프로젝트의 레코드는 만들어진 시간과는 관계 없이 동일하게 중요하고 자주 액세스될 수 있습니다. 우리는 사용자의 idcreated_at을 사용하여 관련 레코드가 얼마나 자주 액세스되거나 업데이트되는지 예측할 수 없습니다.

반면, 로그 및 시계열 데이터와 같은 시간 시계열 데이터 집합에 대한 극단적인 시간 감쇠 효과가 있는 데이터셋의 좋은 예시는 사용자 조치 기록을 기록하는 이벤트입니다.

그러한 유형의 데이터는 대부분의 경우, 몇 일 또는 몇 주 후에는 비즈니스적으로 더 이상 사용되지 않을 수 있으며 데이터 분석적인 측면에서도 빨리 중요성을 잃게 됩니다. 그들은 현재 애플리케이션의 상태와 점점 덜 연관성이 있는 스냅샷을 나타냅니다. 그리고 어느 순간 이러한 데이터는 실질적인 가치가 없어집니다.

이 두 가지 극단 사이에서, 오래된 레코드가 생성된 후 초기(짧은) 시간 측정 후에는 거의 접근되지 않는 유용한 정보를 포함하는 데이터셋을 찾을 수 있습니다.

시간 감쇠 데이터의 특성

다음과 같은 특성을 나타내는 데이터셋에 관심이 있습니다:

  • 데이터셋의 크기: 상당히 큽니다.
  • 접근 방법: 데이터셋에 접근하는 쿼리의 매우 큰 부분을 시간 관련 차원이나 시간 감쇠 효과가 있는 범주적 차원으로 필터링할 수 있습니다.
  • 불변성: 시간 감쇠 상태는 변경되지 않습니다.
  • 보존: 우리가 이전 데이터를 유지할 것인지 여부 또는 이전 데이터가 응용 프로그램을 통해 사용자에게 계속 액세스 가능해야 하는지 여부.

데이터셋의 크기

이러한 시간 감쇠 효과를 보이는 다양한 크기의 데이터셋이 있을 수 있지만, 본 청사진의 문맥에서는 상당히 큰 데이터셋에 중점을 두고자 합니다.

더 작은 데이터셋은 데이터베이스 관련 자원 사용에 중요한 기여를 하지 않으며, 쿼리에 상당한 성능 저하를 초래하지 않습니다.

반면, 5,000만 레코드 또는 100GB 이상의 크기인 큰 데이터셋은 데이터의 아주 작은 부분에 지속적으로 접근하는 중요한 부담을 지니다. 이러한 경우, 우리는 시간 감쇠 효과를 우리 이득으로 활용하고 활발하게 액세스되는 데이터셋을 줄이고 싶어할 것입니다.

데이터 액세스 방법

시간 감쇠 데이터의 두 번째로 가장 중요한 특성은 대부분의 경우, 우리가 묵시적이거나 명시적으로 데이터에 시간 필터를 사용하여 데이터에 접근할 수 있는 것입니다. 시간 관련 차원을 기반으로 결과를 제한하는 방식으로 결과를 제한할 수 있습니다.

이러한 차원이 많을 수 있지만, 여기서는 그 중에서도 가장 일반적으로 사용되는 생성일에 중점을 둡니다. 그것은:

  • 불변함.
  • 레코드가 생성될 때 설정됩니다.
  • 이동하지 않고 레코드를 물리적으로 클러스터링에 연결할 수 있습니다.

애플리케이션이 기본적으로 시간 감쇠 데이터를 이러한 방식으로 액세스하지 않더라도, 앞으로 대부분의 쿼리를 명시적으로 데이터를 필터링하는 방식으로 필터링할 수 있습니다. 시간 감쇠 관련 액세스 방법이 없는 시간 감쇠 데이터는 최적화 관점에서 유용하지 않습니다. 확장 패턴을 설정하고 따르는 방법이 없기 때문에입니다.

애플리케이션이 항상 시간 감쇠 관련 액세스 방법을 사용하여 데이터에 액세스하는 정의를 제한하지는 않습니다. 이상치 작업이 있을 수도 있으므로 우리는 나머지 액세스 방법이 확장 가능하다면 이러한 작업을 승인할 수 있습니다. 예를 들어, 특정 유형의 모든 이벤트의 이전 이벤트에 관한 모든 이전 이벤트에 액세스하는 관리자가 있는 반면, 다른 모든 작업은 6개월 이전으로 제한된 이벤트의 최대치만 액세스할 수 있습니다.

불변성

시간 감쇠 데이터의 세 번째 특성은 그들의 시간 감쇠 상태가 변경되지 않는다는 것입니다. ‘old’로 고려될 때, 다시 ‘new’나 중요해진 것으로 돌아갈 수 없습니다.

이 정의는 당연해 보일 수 있지만, “old” 데이터에 대한 작업을 걱정할 필요없이(예: 보관 및 보관하지 않거나 덜 비싼 저장소로 이동) “old” 데이터에서 연관작용이 중요한 애플리케이션 작업을 유발하지 않을 수 있도록 해야 합니다.

갱신된 시각적으로 보기를 제시하는 애플리케이션 뷰와 같이 시간 감쇠 데이터 액세스 패턴의 반례를 고려해야 할 것입니다. 또한 ‘업데이트’ 관점에서 가장 최근의 데이터에에 대한 관심이 있지만, 그 정의는 변화가 있는 것이기 때문에 실질적으로 실행할 수 있는 것이 아닙니다.

보존

마지막으로, 시간 감쇠 데이터를 조금 다른 접근법으로 하위 카테고리로 구분 짓게 해주는 특성은 이전 데이터를 유지할지 여부(보존 정책) 및/또는 이전 데이터가 응용 프로그램을 통해 사용자에게 계속 액세스 가능한지에 관한 것입니다.

(선택사항) 시간 감쇠 데이터의 확장된 정의

종류를 구분하는 클러스터링 속성에 따라 데이터의 잘 정의된 하위 집합으로의 액세스 패턴을 확장한다면, 시간 감쇠 스케일링 패턴을 다양한 유형의 데이터에 사용할 수 있습니다.

예를 들어, 완료되지 않은 할 일, 병합되지 않은 병합 요청을 위한 파이프라인(또는 비슷한 시간 기반 제약 조건)과 같이 활성으로 레이블이 지정되는 데이터를 고려해 보겠습니다. 이 경우, 우리는 시간 차원을 사용하여 감소를 정의하는 대신, 관심 있는 하위 집합을 정의하는 범주적 차원(즉, 유한한 값 집합을 사용하는 차원)을 사용합니다. 그 하위 집합이 데이터셋의 전체 크기와 비교했을 때 작으면 동일한 접근법을 사용할 수 있습니다.

유사하게, 시간 차원과 추가 상태 속성에 기반하여 데이터를 오래된 것들로 정의할 수 있으며, 6개월 전에 실패한 CI 파이프라인과 같은 경우도 있습니다.

시간 감쇠 데이터 전략

파티션 테이블

이것은 순수한 데이터베이스 관점에서 시간 감쇠 데이터를 처리하는 데에 대한 적절한 모범 사례입니다. 포스트그레SQL의 테이블 파티셔닝에 대한 자세한 정보는 테이블 파티셔닝 페이지의 문서에서 찾을 수 있습니다.

날짜 간격(예: 월, 년)별로 파티셔닝하는 것은 어플리케이션 관련 작업에서 가장 최근의 파티션만 액세스할 수 있도록 매우 작은 테이블(파티션)을 생성할 수 있게 합니다.

우리는 관심 있는 날짜 간격에 따라 파티션 키를 설정해야 합니다. 이는 두 가지 요소에 따라 달라질 수 있습니다.

  1. 얼마나 오래된 데이터에 액세스해야 하는가? 사용할 데이터를 항상 1년 전부터 액세스한다면 주 단위로 파티셔닝하는 것은 쓸모가 없습니다. 예를 들어, GitLab 사용자 프로필의 활동 피드처럼 말입니다.

    반면, 생성된 레코드의 마지막 7일치에만 액세스하려면 연 단위로 파티셔닝하는 것은 각 파티션에 너무 많은 불필요한 레코드가 포함될 수 있습니다. 이는 web_hook_logs의 경우입니다.

  2. 생성된 파티션이 얼마나 큰가? 파티셔닝의 주요 목적은 가능한 작은 테이블에 액세스하는 것입니다. 그 테이블 자체가 너무 크면 쿼리 성능이 저하됩니다. 더 작은 파티션으로 재파티셔닝(분할)해야 할 수도 있습니다.

완벽한 파티셔닝 방식은 데이터셋에 대한 모든 쿼리가 거의 항상 단일 파티션을 거치도록 유지하며, 일부 경우에는 두 개의 파티션을 거치거나 드물게 복수의 파티션을 거치는 것이 허용 가능한 균형을 이루어야 합니다. 또한, 가능한 작은 파티션, 5-10M 레코드 및/또는 각각 최대 10GB를 목표로 해야 합니다.

파티셔닝은 다른 전략과 결합하여 오래된 파티션을 가지치기(삭제), 데이터베이스 내에서 더 싸거나 외부로 이동(아카이브 또는 다른 종류의 저장 엔진 사용)할 수 있습니다.

우리가 오래된 레코드를 유지하고 싶지 않을 때 파티셔닝을 사용하면, 오래된 데이터를 삭제하는 것과 비교하여 가치 지니지 않는 상수 비용이 듭니다. 파티션 내의 모든 데이터가 보존 정책 기간을 벗어나면 이전 파티션을 삭제하기 위해 백그라운드 워커가 필요합니다.

예를 들어, 우리가 데이터를 6개월 이상 유지하고 싶지 않다면 월별로 파티셔닝하고 항상 가장 최근 7개의 파티션을 안전하게 유지할 수 있습니다(현재 월 및 6개월 이전까지). 이는 매월 시작할 때 8번째 오래된 파티션을 삭제할 수 있는 워커가 필요함을 의미합니다.

파티션을 데이터베이스 내에서 싸고 느린 디스크에 이동하는 것은 포스트그레SQL에서 비교적 간단합니다. 각 파티션에 대한 테이블 스페이스와 저장 매개변수를 개별적으로 지정할 수 있으므로, 이 경우의 접근 방식은:

  1. 더 싸고 느린 디스크에 새로운 테이블 스페이스를 생성합니다.
  2. PostgreSQL 옵티마이저가 디스크가 더 느리다는 것을 인지할 수 있도록 그 새로운 테이블 스페이스에 대해 저장 매개변수를 높게 설정합니다.
  3. 백그라운드 워커를 사용하여 예전 파티션을 자동으로 느린 테이블 스페이스로 이동시킵니다.

마지막으로, 데이터베이스 외부로 파티션 이동은 데이터베이스 아카이빙이나 다른 저장 엔진에 대한 수동 내보내기를 통해 가능합니다(전용 하위-섹션의 자세한 내용 참조).

오래된 데이터 가지치기

우리가 어떤 형식으로든 오래된 데이터를 유지하고 싶지 않다면, 가지치기 전략을 구현하고 데이터를 삭제할 수 있습니다.

이것은 가장 간단하게 구현할 수 있는 전략으로, 가지치기 워커를 사용하여 과거 데이터를 삭제합니다. 아래에서 자세히 분석하는 예를 들어, 우리는 90일보다 오래된 이전 web_hook_logs을 가지치고 있습니다.

이러한 해결책의 단점은 대규모이고 파티션이 아닌 테이블과 비교하여 더 이상 관련이 없는 모든 레코드에 수동으로 액세스하고 삭제해야 한다는 것입니다. 이는 PostgreSQL의 다중 버전 동시성 제어 때문에 매우 비용이 많이 드는 작업입니다. 또한, 쓰기 속도가 임계값을 초과하면 가지치기 워커가 새로 생성되는 레코드에 따라 따라잡지 못할 수 있습니다. 이는 이 문서 작성 시점의 web_hook_logs의 경우입니다.

앞서 언급한 이유로, 어떠한 데이터 보존 전략의 구현은 강한 이유가 없는 한 파티셔닝을 기반으로 하는 것이 좋습니다.

데이터베이스 외부로 오래된 데이터 이동

대부분의 경우에 오래된 데이터는 가치 있는 것으로 간주되므로 그것들을 가지치기하고 싶지 않습니다. 동시에, 해당 데이터가 데이터베이스 관련 작업에 필요하지 않다면(예: 직접 액세스하거나 조인 및 다른 유형의 쿼리에서 사용됨), 그것들을 데이터베이스 외부로 이동할 수 있습니다.

이는 데이터베이스 외부로 이동되었다는 것이 아니라, 데이터에 대한 빠른 및 직접적인 액세스를 제공할 수 있으므로 사용자가 응용 프로그램을 통해 직접 액세스하지 못할 수 있습니다. 이는 메타데이터를 언로드하는 것과 유사하지만, 오래된 데이터의 경우에만 해당됩니다.

가장 간단한 사용 사례에서는 최근 데이터에 빠르고 직접적인 액세스를 제공하는 동시에 사용자가 오래된 데이터를 아카이브로 다운로드할 수 있게 합니다. 이것은 audit_events 사용 사례에서 검토되는 옵션입니다. 나라와 산업에 따라, 감사 이벤트는 매우 오래 보존 기간이있을 수 있지만, 데이터가 GitLab 인터페이스를 통해 활동적으로 액세스되는 것은 지난 몇 달 동안의 데이터뿐입니다.

추가 사용 사례는 데이터를 데이터 웨어하우스나 다른 유형의 데이터 저장소로 내보내거나 로드하는 것이 더 적합할 수 있기 때문에, 여러 종류의 데이터 저장소로 데이터를 내보내는 것입니다. 때로는 테이블에 저장하는 JSON 로그를 예로들 수 있습니다. 그러한 데이터를 BigQuery나 Redshift와 같은 열 지향 저장소로 로드하는 것이 데이터를 분석하거나 쿼리하는 데 더 적합할 수 있습니다.

데이터베이스 외부로 데이터를 이동하는 몇 가지 전략을 고려할 수 있습니다:

  1. 이 유형의 데이터를 로그로 스트리밍하고 그런 다음 이를 보조 저장 옵션으로 이동하거나(데이터 형식으로 CSV/JSON 데이터를) 직접 다른 유형의 데이터 저장소로로드합니다.
  2. ETL 프로세스를 만들어 데이터를 CSV로 내보내고, 객체 저장소에 업로드하고, 데이터베이스에서 이 데이터를 삭제한 뒤 CSV를 다른 데이터 저장소에 로드합니다.
  3. 데이터 저장소에서 제공하는 API를 사용하여 백그라운드로 데이터를 로드합니다.

대규모 데이터 세트에 대해 비현실적인 해결책일 수 있습니다. 파일을 사용하여 대량 업로드가 가능한 한, 파일을 사용한 대량 업로드가 API 호출보다 우위를 차지할 것입니다.

사용 사례

웹 훅 로그

관련 이픽: 파티셔닝: web_hook_logs 테이블

web_hook_logs의 중요한 특징은 다음과 같습니다:

  1. 데이터셋 크기: 매우 큰 테이블입니다. 우리는 파티션을 만들기로 결정한 당시, 대략 527M의 레코드와 총 크기가 대략 1TB였습니다.
    • 테이블: web_hook_logs
    • 행: 약 527M 개
    • 총 크기: 1.02 TiB (10.46%)
    • 테이블 크기: 713.02 GiB (13.37%)
    • 인덱스 크기: 42.26 GiB (1.10%)
    • TOAST 크기: 279.01 GiB (38.56%)
  2. 액세스 방법: 일주일 이내의 로그를 항상 요청합니다.
  3. 변경 불가능성: created_at에 따라 파티션으로 나눌 수 있습니다. 이 속성은 변경되지 않습니다.
  4. 저보 보존: 이에 대한 90일 보존 정책이 있습니다.

또한, 우리는 당시에 백그라운드 워커(PruneWebHookLogsWorker)를 사용하여 데이터를 가지치기하려고 했지만, 이는 데이터 삽입 속도를 따라잡지 못했습니다.

결과적으로, 2021년 3월에는 2020년 7월 이후에도 아직 삭제되지 않은 레코드가 있었으며, 테이블의 크기가 상대적으로 안정적인 상태에 머물러야 하는 대신 하루에 200만 레코드 이상이 증가하는 상황이었습니다.

마지막으로, 2021년 3월까지 삽입 속도가 매월 170GB 이상으로 증가했고, 지속적으로 증가하고 있어서 오래된 데이터를 가지치기하기 위한 유일한 해결책은 파티셔닝이었습니다.

우리의 접근 방식은 90일 보존 정책과 일치하는 월별 테이블 파티셔닝이었습니다.

필요한 프로세스는 다음과 같습니다:

  1. 파티셔닝 키 결정 이 경우 created_at 열 사용은 명확합니다. 보존 정책이 존재하고 충돌하는 액세스 패턴이 없을 때 자연스러운 파티셔닝 키입니다.

  2. 파티셔닝 키를 결정한 후, 파티션을 생성하고 백필을 수행합니다(기존 테이블에서 데이터를 복사합니다). 기존 테이블을 간단히 파티션으로 만들 수 없습니다. 새로운 파티션 테이블을 만들고 모든 관련 파티션을 시작하여 모든 데이터를 복사한 후, 새로운 파티션 테이블에 대한 싱크 트리거를 추가해야 합니다.

    테이블 파티셔닝을 시작하는 데 필요한 모든 세부 정보가 포함된 MR

    이 프로세스를 완료하는 데 15일 7.6시간이 소요되었습니다.

  3. 초기 파티션화 시작 후 마일스톤 단계에서, 백피드 및 이후 실행되는 작업, 실패한 작업 재시도 등 배경 마이그레이션을 정리합니다.

    모든 필요한 세부 정보가 포함된 MR

  4. 남은 외래 키와 보조 인덱스를 파티션된 테이블에 추가합니다. 다음 마일스톤에서 테이블 교환하기 전에 기존 비파티션 테이블과 스키마를 동일하게 만듭니다.

    레코드 삽입당 과부하를 추가하지 않도록 하기 위해 처음에는 추가하지 않습니다. 초기 테이블 백필을 느리게 만들기 때문입니다(이 경우 50억 개 이상 레코드의 경우 응당 눈에 띄게 느려질 수 있음). 그래서 가벼운 보통 버전의 테이블을 만들고 모든 데이터를 복사한 후 남은 인덱스와 외래 키를 추가해야 합니다.

  5. 기본 테이블을 파티션된 사본으로 교체: 이것이 파티션된 테이블이 애플리케이션에서 활동적으로 사용되기 시작하는 시점입니다.

    원본 테이블을 삭제하는 것은 파괴적인 조치이므로 프로세스 중 문제가 없었는지를 확인해야 합니다. 이제 필요하다면 원래의 비파티션 테이블을 유지합니다. 또한 비-파티션 테이블이 파티션 테이블에서 발생하는 모든 작업에 대해 여전히 최신 상태를 유지하도록 동기화 트리거를 반대로 전환합니다. 이는 필요한 경우 테이블을 다시 교체할 수 있도록 해줍니다.

    모든 필요한 세부 정보가 포함된 MR

  6. 마지막 단계로 교체 후 마일스톤: 비-파티션 테이블 삭제

    모든 필요한 세부 정보가 포함된 문제

  7. 비-파티션 테이블이 삭제된 후, 과거 파티션 삭제를 통해 가지치기 전략을 구현하기 위해 워커를 추가할 수 있습니다.

    이 경우, 워커는 항상 4개의 파티션만 활성화되도록 하고 (보존 정책이 90일이기 때문), 4개월 이전의 파티션은 삭제합니다. 현재 달이 아직 활성 상태일 때 4개월 전까지 돌아가기 때문에 현재의 4개월 파티션을 유지해야 합니다.

감사 이벤트

관련 이픽: 파티셔닝: 감사 이벤트를 위한 파티션화 전략 설계 및 구현

audit_events 테이블은 이전 하위 섹션에서 논의된 web_hook_logs 테이블과 많은 공통 특성을 공유하므로 두 테이블이 다른 점에 주목합니다.

합의된 바는 파티셔닝이 대부분의 성능 문제를 해결할 수 있다는 것입니다.

대부분의 다른 대형 테이블과 달리, 이 테이블에는 주요 충돌하는 액세스 패턴이 없습니다. 액세스 패턴을 파티셔닝에 맞게 변경할 수 있었습니다. 예를 들어 다른 테이블들은 네임스페이스별로 파티셔닝 접근을 정당화할 수 있으나 다수의 충돌하는 액세스 패턴이 존재합니다.

또한 audit_events는 매우 적은 수의 읽기(쿼리)와 매우 많은 수의 쓰기를 하는 테이블이며, 매우 간단한 스키마로, 데이터베이스의 나머지 부분과 연결되지 않으며(입력 또는 출력 FK 제약 없음), 정의된 인덱스가 2개뿐입니다.

나중에 audit_events에 대해 보존 전략이 정의되어 있지 않으므로 현재 시점에서 보존 전략이 구현되지 않지만, 앞으로 아카이빙 솔루션을 구현할 수 있습니다.

audit_events의 사례에서 흥미로운 점은 파티셔닝된 특정 시간 감소 관련 액세스 방법과 모든 액세스 패턴을 일치시키기 위해 응용 프로그램 수준에서 필요한 변경 사항을 실행하기 위한 UI/UX 변경 구현에 대한 필요한 단계에 관한 토론입니다.

CI 테이블

note
CI 테이블 사용 사례의 요구 사항 및 분석: 아직 진행 중인 작업입니다. 분석이 전개되면 더 많은 세부 정보를 추가할 계획입니다.