Status | Authors | Coach | DRIs | Owning Stage | Created |
---|---|---|---|---|---|
proposed | - |
시간 감쇠 데이터
이 문서는 데이터베이스 확장성 워킹 그룹에서 소개된 시간 감쇠 패턴에 대해 설명합니다. 시간 감쇠 데이터의 특성에 대해 논의하고, GitLab 개발을 위해 고려해야 할 모범 관행를 제안합니다.
일부 데이터 세트는 최근 데이터가 오래된 데이터보다 훨씬 자주 액세스되는 강력한 시간 감쇠 효과가 발생할 수 있습니다. 시간 감쇠의 다른 측면은 시간이 지남에 따라 일부 유형의 데이터가 중요성이 떨어지는 것입니다. 이는 오래된 데이터를 약간 덜 견고한(덜 이용 가능한) 리포지터리로 이동하거나 극단적인 경우에는 데이터를 삭제할 수도 있다는 것을 의미합니다.
이러한 효과들은 일반적으로 제품이나 응용프로그램의 의미론적과 관련이 있습니다. 오래된 데이터가 얼마나 자주 액세스되는지, 오래된 데이터가 사용자나 응용프로그램에게 얼마나 유용하거나 필요한지에 따라 다양할 수 있습니다.
먼저 데이터의 속성에 시간과 관련된 bais가 없는 엔터티를 고려해 보겠습니다.
사용자나 프로젝트에 대한 레코드는 생성된 시점과 관계없이 동일하게 중요하고 빈번하게 액세스될 수 있습니다. 우리는 사용자의 id
나 created_at
을 사용하여 해당 레코드가 얼마나 자주 액세스되거나 업데이트되는지를 예측할 수 없습니다.
반면, 로그 및 시계열 데이터와 같이 극단적인 시간 감쇠 효과를 보이는 데이터 세트의 좋은 예는 사용자 조작을 기록하는 이벤트를 기록하는 경우입니다.
대부분의 경우, 이 유형의 데이터는 며칠 또는 몇 주 후에는 비즈니스적으로 사용되지 않을 수 있으며, 데이터 분석적인 관점에서도 빠르게 중요성을 잃을 수 있습니다. 이러한 데이터는 빠르게 현재의 응용프로그램 상태와 비교해서 덜 중요해집니다. 일정 시점에서는 실제 가치가 없어집니다.
이 두 극단 사이에서, 우리는 유용한 정보가 포함된 데이터 세트를 찾을 수 있으며 초기(짧은) 시간 이후에는 거의 액세스되지 않는 오래된 레코드를 유지하려는 경우가 있습니다.
시간 감쇠 데이터의 특성
다음과 같은 특성을 보이는 데이터에 관심이 있습니다:
- 데이터 세트의 크기: 상당히 큽니다.
- 액세스 방법: 데이터에 액세스하는 대부분의 쿼리를 시간과 관련된 차원이나 시간 감쇠 효과가 있는 범주형 차원으로 필터링할 수 있습니다.
- 변함없음: 시간 감쇠 상태는 변하지 않습니다.
- 유지: 우리가 오래된 데이터를 유지할 것인지 아닌지, 또는 오래된 데이터가 응용프로그램을 통해 사용자에게 계속 액세스 가능해야 하는지 여부.
데이터 세트의 크기
변수 크기의 데이터 세트에 강력한 시간 감쇠 효과가 있을 수 있지만, 이 청사진의 맥락에서는 상당히 큰 데이터 세트에 중점을 두기로 했습니다.
작은 데이터 세트는 데이터베이스 관련 리소스 사용에 중요한 기여를 하지 않으며, 쿼리에 상당한 성능 저하를 가져오지 않습니다.
그러나 약 5000만 개의 레코드 또는 100GB 이상인 큰 데이터 세트는 실제로는 데이터의 매우 작은 부분만 계속해서 액세스하는 상당한 부담을 줍니다. 이러한 경우 우리는 시간 감쇠 효과를 우리 이점으로 활용하여 활발하게 액세스되는 데이터 세트를 줄이고 싶어할 것입니다.
데이터 액세스 방법
시간 감쇠 데이터의 두 번째로 가장 중요한 특성은 대부분의 시간, 우리가 암시적 또는 명시적으로 날짜 필터를 사용하여 데이터에 액세스할 수 있습니다. 시간과 관련된 차원을 기반으로 결과를 제한하는 방식으로 데이터를 액세스할 수 있습니다.
이러한 차원은 많을 수 있지만, 우리는 가장 일반적으로 사용되는 창조 날짜를 중점적으로 다루고 있으며 우리가 제어하고 최적화할 수 있는 차원입니다. 이것은:
- 불변적임.
- 레코드를 생성할 때 설정됨.
- 레코드를 물리적으로 클러스터링할 수 있도록 연결될 수 있습니다.
애플리케이션이 기본적으로 시간 감쇠 데이터를 이러한 방식으로 액세스하지 않더라도, 대부분의 쿼리가 명시적으로 데이터를 이러한 방식으로 필터링하게 할 수 있습니다. 시간 감쇠 관련 액세스 방법이 없는 시간 감쇠 데이터는 최적화 관점에서는 쓸모가 없기 때문에 낙관적 최적화(cage optimizarion)에는 이러한 방식으로 설정된 쿼리의 상당수가 필터링되게 할 수 있습니다.
우리는 데이터가 항상 시간 감쇠 관련 액세스 방법을 사용하여 이러한 방식으로 액세스되는 것으로 제한하는 정의를 시간 감쇠 데이터에 반드시 제안하지는 않습니다. 이처럼 동작하는 몇 가지 이상값 연산이 있을 수 있습니다. 필수적인 경우이며, 접근 방법 중 대부분이 확장될 수 있습니다. 예를 들면: 특정 유형의 모든 지난 이벤트에 액세스하는 관리자는 나머지 모든 작업이 최대 한 달 이벤트에만 액세스하므로 꼭 6개월 이전으로 제한될 수 있습니다.
불변성
시간 감쇠 데이터의 세 번째 특성은 그들의 시간 감쇠 상태가 변하지 않는다는 것입니다. 그들이 “오래된”으로 간주되면 다시 “새로운”이나 중요한 상태로 되돌아갈 수 없습니다.
이 정의는 단순한 것처럼 들릴 수 있지만, 우리는 “오래된”데이터에 대해 작업시 비용을 더 많이 들일 수 있다는 것을 걱정할 필요 없이(예를 들어, 아카이빙하거나 비싼 리포지터리로 이동하는 방식)하려면 중요합니다.
시간 감쇠 데이터 액세스 패턴에 대한 반례로 문제를 보여주는 것으로 예시할 수 있는 것은 문제가 업데이트된 시점에 따라 제시하는 애플리케이션 뷰입니다. 또한 “업데이트” 관점에서 가장 최근 데이터에 관심이 있지만, 이 정의는 불안정하고 실행 가능하지 않습니다.
보존
마지막으로, 시간 감쇠 데이터를 조금 다른 접근 방법으로 세부 카테고리로 구분하는 특성은 오래된 데이터를 유지할 것인지 아니면(예를 들어 보존 정책) 및/또는 응용프로그램을 통해 오래된 데이터에 대해 사용자가 계속 액세스 할 수 있는지(예: 선택 사항) 여부입니다.
(선택 사항) 시간 감쇠 데이터의 확장된 정의
이전에 언급된 정의를 클러스터링 속성을 기반으로 정의된 데이터에 대한 액세스 패턴에 제한하는 방식으로 확장하면, 많은 다른 유형의 데이터에 대해 시간 감쇠 스케일링 패턴을 사용할 수 있습니다.
예를 들어, 완료되지 않은 할 일디렉터리, Merge되지 않은 Merge Request을 위한 파이프라인(또는 비슷한 시간 기반 제약 조건)과 같이 활성 상태로 레이블이 지정된 시간 동안에만 액세스된 데이터로 정의할 수 있습니다. 이 경우, 감도(그러나 제한된 값 세트를 사용하는) 분류적 차원을 사용하여 관심 대상의 하위 집합을 정의합니다. 해당 하위 집합이 데이터 세트의 전체 크기에 비해 작을 경우, 동일한 접근 방식을 사용할 수 있습니다.
비슷하게, 시간과 추가적인 상태 속성에 기반하여 데이터를 오래된 데이터로 정의할 수 있습니다. 예를 들어, 6개월 전에 실패한 CI 파이프라인과 같은 상태 속성을 고려합니다.
시간 감쇠 데이터 전략
파티션 테이블
이것은 순수한 데이터베이스 관점에서 시간 감쇠 데이터를 처리하기 위한 허용되는 모범 관행입니다. PostgreSQL에 대한 테이블 파티셔닝에 대한 문서 페이지에서 자세한 정보를 찾을 수 있습니다.
날짜 간격(예: 월, 년)으로 파티션을 생성함으로써 우리는 어떤 애플리케이션 관련 작업에도 가장 최근의 파티션에만 액세스할 수 있도록 할 수 있습니다.
우리는 데이터의 액세스 기간이 얼마나 되는지에 따라 파티셔닝 키를 설정해야 합니다. 다음 두 가지 요소에 따라 달라질 수 있습니다:
-
얼마나 오래된 데이터에 액세스해야 하는지? 데이터에 영월 작업을 하려면 주별로 파티셔닝하는 것은 유용하지 않습니다. 왜냐하면 매번 52개의 파티션(테이블)에 대한 쿼리를 실행해야 하기 때문입니다. 이에 대한 예제로는 GitLab 사용자의 프로파일에서의 활동 피드를 고려했습니다.
반대로, 생성된 레코드의 지난 7일만 액세스하려면 연별로 파티셔닝하는 것이 적합하지 않습니다. 각 파티션에서 불필요한 레코드가 너무 많이 포함됩니다.
web_hook_logs
와 같은 경우가 해당됩니다. -
생성된 파티션의 크기는 얼마인가? 파티셔닝의 주요 목적은 가능한 작은 테이블에 액세스하는 것입니다. 그들이 너무 커지면 쿼리의 성능이 떨어집니다. 더 작은 파티션으로 다시 파티셔닝(분할)해야 할 수도 있습니다.
완벽한 파티셔닝 체계는 데이터 세트에 대한 모든 쿼리를 거의 항상 단일 파티션, 일부 케이스는 두 개의 파티션과 드물게 여러 파티션을 거치는 것으로 유지하며, 이는 상당한 균형이 될 수 있습니다. 또한, 거의 가능한 작은 파티션(최대 5-10M 레코드 또는 10GB 이하)을 대상으로 해야 합니다.
파티셔닝은 이전 파티션을 가지고 하던 방식을 계속 유지하려면, 이전 데이터를 잘라내거나 리포지터리 내부의 저렴한 리포지터리로 이동하거나 데이터베이스의 외부로 이동할 수 있는 다른 종류의 리포지터리 엔진을 사용할 수 있는 다른 전략과 결합될 수 있습니다.
우리가 과거 레코드를 유지하려는 의도가 없고 파티셔닝을 사용한다면, 과거 데이터를 잘라내는 것이 엄청나게 혹은 실질적인 절대로 금방 떨어질 비용이 be null이다. 우리는 오래된 파티션이 그 데이터가 보존 정책 기간 내에서 떨어질 때마다 일정한, 실질적으로 0비용의 배경 작업자가 필요합니다.
예를 들어, 우리가 6개월 이상의 기록을 유지하기를 원치 않고, 우리가 월별로 파티셔닝을 사용하고 있다면, 우리는 언제든지 가장 최근 7개의 파티션(현재 월의 파티션과 지난 6개월)을 안전하게 유지할 수 있을 것입니다. 즉, 매월 초에 가장 오래된 파티션을 잘라내는 worker가 있어야 합니다.
PostgreSQL에서 동일한 데이터베이스 내부의 저렴하고 느린 디스크를 사용하여 파티션 이동은 상대적으로 간편합니다. 테이블스페이스를 사용하여 각 파티션별로 테이블스페이스와 리포지터리 매개 변수를 별도로 지정할 수 있다. 따라서 이러한 접근 방식은 다음과 같습니다:
1.
오래된 데이터 정리
어떤 형태로든 오래된 데이터를 보관하고 싶지 않다면, 가지치기 전략을 구현하여 오래된 데이터를 삭제할 수 있습니다.
그것은 가장 단순한 구현 전략으로, 가치관을 가진 작업자를 사용하여 지난 데이터를 삭제합니다. 아래에서 자세히 분석할 예로, 90일 이전의 오래된 web_hook_logs
를 가지치기하고 있습니다.
큰 비분할된 테이블에 비해 이러한 솔루션이 가진 단점은, 이제 더 이상 관련이 없는 레코드를 수작업으로 액세스하고 삭제해야 한다는 것입니다. 이것은 PostgreSQL의 다중 버전 동시성 제어 때문에 매우 비용이 많이 드는 작업이며, 쓰기 중심적인 레코드 속도가 임계 값을 초과할 경우 새 레코드를 따라잡지 못할 수도 있습니다. 바로 이 문제가 이 문서를 작성하는 시점에 web_hook_logs
로 발생 중인 것입니다.
위에서 언급한 이유로 데이터 유지 정책의 구현을 분할 기반으로 하는 것을 제안합니다. 특별한 이유가 없는 한 모두 데이터의 경우, 분할로 데이터를 보관하기를 권장합니다.
데이터베이스 외부로 오래된 데이터 이동
대부분의 경우 오래된 데이터는 가치가 있기 때문에 우리는 그것들을 가지치기하고 싶어하지 않습니다. 동시에, 이 데이터들은 데이터베이스 관련 작업에 필요하지 않을 경우(예를 들어, 직접 액세스되거나 조인 및 기타 유형의 쿼리에서 사용되지 않을 경우) 데이터베이스 외부로 이동할 수 있습니다.
이는 사용자들이 애플리케이션을 통해 직접 액세스할 수 없다는 의미는 아닙니다. 데이터를 데이터베이스 외부로 옮겨서 메타데이터 처럼 사용할 수 있게 하는 것이며, 오래된 데이터의 경우에만 해당합니다.
가장 간단한 사용 사례에서는 최근 데이터에 빠르고 직접적인 액세스를 제공할 수 있으며, 사용자가 오래된 데이터가 포함된 아카이브를 다운로드할 수 있게 할 수 있습니다. 이것은 audit_events
사용 사례에서 평가된 옵션입니다. 국가 및 산업에 따라, 감사 이벤트의 보관 기간이 아주 길 수 있지만 오래된 데이터는 GitLab 인터페이스를 통해 활발하게 액세스되는 과거 수 개월의 데이터만 해당 사항으로 될 수 있습니다.
추가 사용 사례에는 데이터를 데이터 웨어하우스나 다른 유형의 데이터 리포지터리로 내보내거나 다른 유형의 데이터 리포지터리에로드하는 것이 포함될 수 있으며, 이러한 작업이 해당 데이터 유형 처리에 더 적합할 수도 있습니다. 예를 들어 테이블에 때로는 JSON 로그를 저장하는 경우가 있습니다. 이러한 데이터를 BigQuery나 Redshift와 같은 컬럼 리포지터리에 로드하는 것이 데이터를 분석/쿼리하는 데 더 효과적일 수 있습니다.
데이터베이스 외부로 데이터를 옮기기 위한 다양한 전략을 고려할 수 있습니다:
- 이 유형의 데이터를 로그에 스트리밍하고 이를 보조 저장 옵션으로 이동하거나 다른 유형의 데이터 리포지터리로 직접 로드합니다(예: CSV/JSON 데이터).
- 데이터를 CSV로 내보내는 ETL 프로세스를 생성하고, 객체 리포지터리에 업로드한 후 데이터를 데이터베이스에서 삭제하고, CSV를 다른 데이터 리포지터리로 로드합니다.
- 데이터 리포지터리에서 제공하는 API를 사용하여 백그라운드에서 데이터를 로드합니다.
대규모 데이터 세트에 대해서는 이러한 방법이 적합하지 않을 수 있지만, 파일을 사용한 대량 업로드가 가능한 한경우, API 호출보다 우선적이게 될 것입니다.
사용 사례
웹 후크 로그
관련 에픽: 분할: web_hook_logs
테이블
web_hook_logs
의 중요한 특성은 다음과 같습니다:
-
데이터 집합 크기: 실제로 매우 큰 테이블입니다. (
2021-03-01
부터) 약 5억 2700만 개의 레코드가 있었으며 전체 크기는 약 1 TB 였습니다.- 테이블:
web_hook_logs
- 레코드: 약 5억 2700만 개
- 총 크기: 1.02 TiB (10.46%)
- 테이블 크기: 713.02 GiB (13.37%)
- 인덱스 크기: 42.26 GiB (1.10%)
- TOAST 크기: 279.01 GiB (38.56%)
- 테이블:
- 액세스 방법: 최대 최근 7일치 로그를 항상 요청합니다.
- 변경 불가성:
created_at
로 분할할 수 있으며 이 속성은 변경되지 않습니다. - 유지 기간: 90일 임산 규정이 설정되어 있습니다.
뿐만 아니라, 주요 문제로 백그라운드 작업자(PruneWebHookLogsWorker
)를 사용하여 데이터를 가지치기하려고 했지만 삽입 속도에 기반해 더 많은 레코드가 생성되어 점점 커지고 있었습니다.
결과적으로, 2021년 3월에는 여전히 2020년 7월 이후 삭제되지 않은 레코드가 존재했고, 테이블 크기가 매일 약 200만 개의 레코드 이상으로 늘어나는 상태였습니다.
마지막으로, 삽입 속도는 2021년 3월 이후 매달 170GB 이상으로 증가하고 있었으며, 오래된 데이터를 가지치기하기 위한 유일한 해결책은 분할로 이루어졌습니다.
우리의 접근 방식은 데이터 유지 정책에 부합되는 방법으로 매월 테이블을 분할하는 것입니다.
다음과 같은 과정이 필요합니다:
- 분할 키를 결정합니다
-
분할 키를 결정한 후, 파티션을 작성하고 백필합니다(기존 테이블에서 데이터를 복사합니다). 기존 테이블을 분할할 수는 없습니다. 새로운 분할 테이블을 만들어야 합니다.
따라서 우리는 분할된 테이블과 관련된 모든 분할을 작성하고, 모든 데이터를 복사하고, 싱크 트리거를 추가하여 새로운 분할 테이블로 모든 새 데이터 또는 기존 데이터의 업데이트/삭제를 반영할 수 있도록 해야합니다.
테이블을 분할하는 데 필요한 모든 세부 정보가 있는 MR
이 과정에는 15일 7시간이 소요되었습니다.
-
초기 분할 후에 백그라운드 마이그레이션 사용에 대한 정리를 시작하여 완료 작업 및 실패한 작업을 다시 시도하는 마지막 단계입니다.
-
외래 키 및 중요한 인덱스를 분할된 테이블에 추가합니다. 이 단계에서 쓰기 작업에 오버헤드를 추가하기 때문에 처음에 추가하지 않았습니다. 이 작업은 테이블의 초기 백필 작업을 느리게 할 수 있습니다. (이 경우 50억 개 데이터가 넘게 되면서 이는 상당히 늘어날 수 있기 때문입니다). 우리는 테이블의 가볍고 평범한 버전을 만들고 모든 데이터를 복사한 후 추가로 필요한 인덱스와 외래 키를 추가합니다.
-
기본 테이블을 분할된 복사본으로 교체하는 것: 이것은 분할된 테이블이 애플리케이션에서 활발하게 사용되기 시작하는 시점입니다.
원본 테이블 삭제는 파괴적인 동작이며, 이 과정에서 문제가 없는지 확인하기 위해 오래된 비분할 테이블을 유지하려고 합니다. 또한 싱크 트리거를 반대 방향으로 전환하여 비분할 테이블이 분할 테이블에서 발생하는 모든 작업과 동기화되도록 유지합니다. 이를 통해 테이블의 돌아가기가 필요한 경우를 대비할 수 있습니다.
-
마지막 단계로 스왑한 후의 마지막 이행: 비분할 테이블 삭제하기
-
비분할 테이블을 삭제한 후, 가지치기 전략을 구현하는 작업자를 추가합니다.
이 경우, 작업자는 항상 4개의 파티션이 활성화되었는지 확인하고 (유지 기간이 90일인 관계로) 4개월 이전의 파티션을 삭제합니다. 현재 달이 활성화되어 있을 때는 4개월치의 파티션을 유지해야 하는데, 이는 90일 전으로 이동하면 네 번째로 오래된 파티션에 도달하게 됩니다.
감사 이벤트
관련 에픽: 파티션: 감사 이벤트용 파티셔닝 전략 설계 및 구현
audit_events
테이블은 이전 하위 섹션에서 논의된 web_hook_logs
테이블과 많은 특성을 공유하므로, 두 테이블이 다른 점에 초점을 맞출 것입니다.
합의된 바에 따르면 파티셔닝이 성능 이슈의 대부분을 해결할 수 있다고 합니다.
대부분의 대형 테이블과 대조적으로, 이 테이블은 주요 충돌 접근 패턴이 없습니다: 우리는 액세스 패턴을 월별로 맞출 수 있습니다. 반면에 다른 테이블들은 이와 달리 네임스페이스 등을 기준으로 파티셔닝 접근을 정당화할 수 있는 경우라 할지라도 많은 충돌하는 액세스 패턴을 가지고 있습니다.
게다가 audit_events
는 매우 적은 쿼리를 통한 읽기(queries)가 있는 쓰기 중심의 테이블이며, 다른 데이터베이스와 연결되어있지 않은 매우 간단한 스키마를 가지고 있으며 오직 두 개의 인덱스만이 정의되어 있습니다.
나중에 외래키(FK) 제약이 없다는 것이 PostgreSQL 11에서 여전히 이동할 수 있었다는 점은 중요했습니다. PostgreSQL 12로 이동한 지금은 web_hook_logs
사용 사례에서 확인할 수 있듯이 더는 걱정할 필요가 없습니다.
audit_events
의 파티셔닝을 위해 필요한 마이그레이션 및 단계는 web_hook_logs
에 대한 이전 하위 섹션에서 설명된 것과 유사합니다. 현재 audit_events
에 대해 정의된 보관 전략이 없으므로 해당 테이블에 구현된 가지치기 전략도 없지만, 향후 아카이빙 솔루션을 구현할 수 있습니다.
audit_events
의 경우 흥미로운 점은 파티셔닝된 쿼리의 최적화를 촉진하기 위해 필요한 UI/UX 변경 사항을 구현하기 위해 따라야 했던 필요한 단계에 대한 논의입니다. 필요한 변경 사항의 시작점으로 활용될 수 있으며, 특정 시간 감소 관련 액세스 방법과 모든 액세스 패턴을 맞추기 위한 응용 프로그램 수준의 변경이 필요한 변경 사항에 관한 논의입니다.