paint-brush
Apache Doris의 자동화 및 유연성을 향한 데이터 샤딩의 진화~에 의해@frankzzz
새로운 역사

Apache Doris의 자동화 및 유연성을 향한 데이터 샤딩의 진화

~에 의해 Frank Z29m2024/07/20
Read on Terminal Reader

너무 오래; 읽다

Apache Doris는 V2.1.0에서 자동 파티션을 도입합니다. Apache Doris 2.1.0부터 DDL 및 파티션 관리가 단순화되었습니다. 대규모 데이터 처리에 유용하며 사용자가 다른 데이터베이스 시스템에서 Apache Doris로 쉽게 마이그레이션할 수 있습니다.
featured image - Apache Doris의 자동화 및 유연성을 향한 데이터 샤딩의 진화
Frank Z HackerNoon profile picture


대규모 데이터 세트를 처리하기 위해 분산 데이터베이스는 파티셔닝 및 버킷팅과 같은 전략을 도입합니다. 데이터는 특정 규칙에 따라 더 작은 단위로 분할되고 여러 노드에 분산되므로 데이터베이스는 더 높은 성능과 데이터 관리 유연성을 위해 병렬 처리를 수행할 수 있습니다.


많은 데이터베이스와 마찬가지로 Apache Doris는 데이터를 파티션으로 분할한 다음 파티션을 버킷으로 더 나눕니다. 파티션은 일반적으로 시간 또는 기타 연속 값으로 정의됩니다. 이를 통해 쿼리 엔진은 관련 없는 데이터 범위를 정리하여 쿼리 중에 대상 데이터를 빠르게 찾을 수 있습니다.


반면에 버킷팅은 하나 이상의 열의 해시 값을 기반으로 데이터를 배포하므로 데이터 왜곡을 방지합니다.


버전 2.1.0 이전에는 Apache Doris에서 데이터 파티션을 생성할 수 있는 두 가지 방법이 있었습니다.


  • 수동 파티션 : 사용자가 테이블 생성문에서 파티션을 지정하거나 이후 DDL문을 통해 수정합니다.


  • 동적 파티션 : 시스템은 데이터 수집 시간을 기준으로 미리 정의된 범위 내에서 파티션을 자동으로 유지합니다.


Apache Doris 2.1.0에서는 자동 파티션을 도입했습니다. RANGE 또는 LIST별 데이터 파티셔닝을 지원하며 자동 파티셔닝 외에도 유연성을 더욱 향상시킵니다.

Doris의 파티셔닝 전략의 진화

파티션 열과 파티션 간격의 선택은 실제 데이터 배포 패턴에 크게 좌우되고 좋은 파티션 디자인은 테이블의 쿼리 및 저장 효율성을 크게 향상시킬 수 있기 때문에 데이터 배포 설계에서는 파티션 계획에 더 중점을 둡니다.


Doris에서는 데이터 테이블이 계층적 방식으로 파티션과 버킷으로 구분됩니다. 그런 다음 동일한 버킷 내의 데이터는 데이터 복제, 클러스터 간 데이터 예약 및 로드 밸런싱을 위한 Doris의 최소 물리적 저장 장치인 데이터 태블릿을 형성합니다.


수동 파티션

Doris를 사용하면 사용자가 RANGE 및 LIST별로 데이터 파티션을 수동으로 생성할 수 있습니다.


로그 및 트랜잭션 기록과 같은 타임스탬프가 있는 데이터의 경우 사용자는 일반적으로 시간 차원을 기반으로 파티션을 생성합니다. 다음은 CREATE TABLE 문의 예입니다.


 CREATE TABLE IF NOT EXISTS example_range_tbl ( `user_id` LARGEINT NOT NULL COMMENT "User ID", `date` DATE NOT NULL COMMENT "Data import date", `timestamp` DATETIME NOT NULL COMMENT "Data import timestamp", `city` VARCHAR(20) COMMENT "Location of user", `age` SMALLINT COMMENT "Age of user", `sex` TINYINT COMMENT "Sex of user", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "Last visit date of user", `cost` BIGINT SUM DEFAULT "0" COMMENT "User consumption", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "Maximum dwell time of user", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "Minimum dwell time of user" ) ENGINE=OLAP AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01"), PARTITION `p2018` VALUES [("2018-01-01"), ("2019-01-01")) ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "1" );


테이블은 데이터 가져오기 date 를 기준으로 파티셔닝되어 있으며 4개의 파티션이 미리 생성되어 있습니다. 각 파티션 내에서 데이터는 user_id 의 해시 값을 기준으로 16개의 버킷으로 추가로 나뉩니다.


이러한 파티셔닝 및 버킷팅 설계를 사용하면 2018년 이후의 데이터를 쿼리할 때 시스템은 p2018 파티션만 스캔하면 됩니다. 쿼리 SQL은 다음과 같습니다.


 mysql> desc select count() from example_range_tbl where date >= '20180101'; +--------------------------------------------------------------------------------------+ | Explain String(Nereids Planner) | +--------------------------------------------------------------------------------------+ | PLAN FRAGMENT 0 | | OUTPUT EXPRS: | | count(*)[#11] | | PARTITION: UNPARTITIONED | | | | ...... | | | | 0:VOlapScanNode(193) | | TABLE: test.example_range_tbl(example_range_tbl), PREAGGREGATION: OFF. | | PREDICATES: (date[#1] >= '2018-01-01') | | partitions=1/4 (p2018), tablets=16/16, tabletList=561490,561492,561494 ... | | cardinality=0, avgRowSize=0.0, numNodes=1 | | pushAggOp=NONE | | | +--------------------------------------------------------------------------------------+


데이터가 파티션 전체에 고르지 않게 분산된 경우 해시 기반 버킷팅 메커니즘은 user_id 기준으로 데이터를 추가로 나눌 수 있습니다. 이는 쿼리 및 저장 중에 일부 시스템의 로드 불균형을 방지하는 데 도움이 됩니다.


그러나 실제 비즈니스 시나리오에서는 하나의 클러스터에 수만 개의 테이블이 있을 수 있으므로 수동으로 관리하는 것이 불가능합니다.


 CREATE TABLE `DAILY_TRADE_VALUE` ( `TRADE_DATE` datev2 NOT NULL COMMENT 'Trade date', `TRADE_ID` varchar(40) NOT NULL COMMENT 'Trade ID', ...... ) UNIQUE KEY(`TRADE_DATE`, `TRADE_ID`) PARTITION BY RANGE(`TRADE_DATE`) ( PARTITION p_200001 VALUES [('2000-01-01'), ('2000-02-01')), PARTITION p_200002 VALUES [('2000-02-01'), ('2000-03-01')), PARTITION p_200003 VALUES [('2000-03-01'), ('2000-04-01')), PARTITION p_200004 VALUES [('2000-04-01'), ('2000-05-01')), PARTITION p_200005 VALUES [('2000-05-01'), ('2000-06-01')), PARTITION p_200006 VALUES [('2000-06-01'), ('2000-07-01')), PARTITION p_200007 VALUES [('2000-07-01'), ('2000-08-01')), PARTITION p_200008 VALUES [('2000-08-01'), ('2000-09-01')), PARTITION p_200009 VALUES [('2000-09-01'), ('2000-10-01')), PARTITION p_200010 VALUES [('2000-10-01'), ('2000-11-01')), PARTITION p_200011 VALUES [('2000-11-01'), ('2000-12-01')), PARTITION p_200012 VALUES [('2000-12-01'), ('2001-01-01')), PARTITION p_200101 VALUES [('2001-01-01'), ('2001-02-01')), ...... ) DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( ...... );


위의 예에서 데이터는 월 단위로 분할됩니다. 이를 위해서는 데이터베이스 관리자(DBA)가 매달 수동으로 새 파티션을 추가하고 테이블 스키마를 정기적으로 유지 관리해야 합니다. 매일 또는 심지어 매시간 파티션을 생성해야 하는 실시간 데이터 처리의 경우를 상상해 보십시오. 수동으로 수행하는 것은 더 이상 선택 사항이 아닙니다. 이것이 바로 우리가 동적 파티션을 도입한 이유입니다.

동적 파티션

동적 파티션을 통해 Doris는 사용자가 파티션 단위, 기록 파티션 수 및 향후 파티션 수를 지정하는 한 자동으로 데이터 파티션을 생성하고 회수합니다. 이 기능은 Doris Frontend의 고정 스레드에 의존합니다. 생성할 새 파티션이나 회수할 기존 파티션을 지속적으로 폴링 및 확인하고 테이블의 파티션 스키마를 업데이트합니다.


이것은 날짜별로 분할된 테이블에 대한 CREATE TABLE 문의 예입니다. startend 매개변수는 각각 -73 으로 설정됩니다. 이는 다음 3일 동안의 데이터 파티션이 사전 생성되고 7일보다 오래된 기록 파티션이 회수됨을 의미합니다.


 CREATE TABLE `DAILY_TRADE_VALUE` ( `TRADE_DATE` datev2 NOT NULL COMMENT 'Trade date', `TRADE_ID` varchar(40) NOT NULL COMMENT 'Trade ID', ...... ) UNIQUE KEY(`TRADE_DATE`, `TRADE_ID`) PARTITION BY RANGE(`TRADE_DATE`) () DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( "dynamic_partition.enable" = "true", "dynamic_partition.time_unit" = "DAY", "dynamic_partition.start" = "-7", "dynamic_partition.end" = "3", "dynamic_partition.prefix" = "p", "dynamic_partition.buckets" = "10" );


시간이 지남에 따라 테이블은 항상 [current date - 7, current date + 3] 범위 내에서 파티션을 유지합니다. 동적 파티션은 ODS(Operational Data Store) 계층이 Kafka와 같은 외부 소스로부터 데이터를 직접 수신하는 경우와 같은 실시간 데이터 수집 시나리오에 특히 유용합니다.


startend 매개변수는 파티션의 고정 범위를 정의하므로 사용자는 이 범위 내에서만 파티션을 관리할 수 있습니다. 그러나 사용자가 더 많은 기록 데이터를 포함해야 하는 경우 start 값을 높여야 하며 이로 인해 클러스터에 불필요한 메타데이터 오버헤드가 발생할 수 있습니다.


따라서 동적 파티션을 적용할 경우 메타데이터 관리의 편의성과 효율성 사이에 트레이드오프(trade-off)가 발생합니다.

개발자의 말

비즈니스가 복잡해지면 다음과 같은 이유로 동적 파티션이 부적절해집니다.


  • RANGE에 의한 파티셔닝만 지원하고 LIST에 의한 파티셔닝은 지원하지 않습니다.
  • 현재 실제 타임스탬프에만 적용할 수 있습니다.
  • 단일 연속 파티션 범위만 지원하며 해당 범위 밖의 파티션은 수용할 수 없습니다.


이러한 기능적 제한을 고려하여 우리는 파티션 관리를 자동화하고 데이터 테이블 유지 관리를 단순화할 수 있는 새로운 파티셔닝 메커니즘을 계획하기 시작했습니다.


우리는 이상적인 파티셔닝 구현이 다음과 같아야 한다는 것을 알아냈습니다.


  • 테이블 생성 후 수동으로 파티션을 생성할 필요가 없습니다.
  • 수집된 모든 데이터를 해당 파티션에 수용할 수 있습니다.


전자는 자동화를 의미하고 후자는 유연성을 의미합니다. 두 가지를 모두 실현하는 핵심은 파티션 생성을 실제 데이터와 연결하는 것입니다.


그런 다음 우리는 다음과 같은 생각을 하기 시작했습니다. 테이블 생성 중이나 정기적인 폴링을 통해 파티션을 생성하는 대신 데이터가 수집될 때까지 파티션 생성을 보류하면 어떻게 될까요? 파티션 분포를 미리 구성하는 대신 데이터 도착 후 파티션이 생성되도록 "데이터-파티션" 매핑 규칙을 정의할 수 있습니다.


수동 파티션과 비교하면 이 전체 프로세스가 완전히 자동화되어 사람이 유지 관리할 필요가 없습니다. 동적 파티션과 비교하면 사용되지 않는 파티션이나 필요하지만 존재하지 않는 파티션이 발생하지 않습니다.

자동 파티션

Apache Doris 2.1.0을 통해 우리는 위의 계획을 실현합니다. 데이터를 수집하는 동안 Doris는 구성된 규칙에 따라 데이터 파티션을 생성합니다. 데이터 처리 및 배포를 담당하는 Doris 백엔드 노드는 실행 계획의 DataSink 연산자에서 각 데이터 행에 대한 적절한 파티션을 찾으려고 시도합니다. 더 이상 기존 파티션에 맞지 않는 데이터를 필터링하거나 그러한 상황에 대한 오류를 보고하지 않지만 수집된 모든 데이터에 대한 파티션을 자동으로 생성합니다.

RANGE별 자동 파티션

Auto Partition by RANGE는 시간 차원에 따라 최적화된 파티셔닝 솔루션을 제공합니다. 매개변수 구성 측면에서 동적 파티션보다 더 유연합니다. 이에 대한 구문은 다음과 같습니다.


 AUTO PARTITION BY RANGE (FUNC_CALL_EXPR) () FUNC_CALL_EXPR ::= DATE_TRUNC ( <partition_column>, '<interval>' )


위의 <partition_column> 은 파티션 열(파티셔닝의 기반이 되는 열)입니다. <interval> 각 파티션의 원하는 너비인 파티션 단위를 지정합니다.


예를 들어 파티션 열이 k0 이고 월별로 파티션을 나누려는 경우 파티션 문은 AUTO PARTITION BY RANGE (DATE_TRUNC(k0, 'month')) 입니다. 가져온 모든 데이터에 대해 시스템은 DATE_TRUNC(k0, 'month') 호출하여 파티션의 왼쪽 끝점을 계산한 다음 하나의 interval 추가하여 오른쪽 끝점을 계산합니다.


이제 이전 동적 파티션 섹션에서 소개한 DAILY_TRADE_VALUE 테이블에 자동 파티션을 적용할 수 있습니다.


 CREATE TABLE DAILY_TRADE_VALUE ( `TRADE_DATE` DATEV2 NOT NULL COMMENT 'Trade Date', `TRADE_ID` VARCHAR(40) NOT NULL COMMENT 'Trade ID', ...... ) AUTO PARTITION BY RANGE (DATE_TRUNC(`TRADE_DATE`, 'month')) () DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 PROPERTIES ( ...... );


일부 데이터를 가져온 후 얻는 파티션은 다음과 같습니다.


 mysql> show partitions from DAILY_TRADE_VALUE; Empty set (0.10 sec) mysql> insert into DAILY_TRADE_VALUE values ('2015-01-01', 1), ('2020-01-01', 2), ('2024-03-05', 10000), ('2024-03-06', 10001); Query OK, 4 rows affected (0.24 sec) {'label':'label_2a7353a3f991400e_ae731988fa2bc568', 'status':'VISIBLE', 'txnId':'85097'} mysql> show partitions from DAILY_TRADE_VALUE; +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 588395 | p20150101000000 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2015-01-01]; ..types: [DATEV2]; keys: [2015-02-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 588437 | p20200101000000 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2020-01-01]; ..types: [DATEV2]; keys: [2020-02-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 588416 | p20240301000000 | 2 | 2024-06-01 19:02:40 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-01]; ..types: [DATEV2]; keys: [2024-04-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.09 sec)


그림과 같이 가져온 데이터에 대해 자동으로 파티션이 생성되며, 기존 데이터의 범위를 벗어나는 파티션은 생성되지 않습니다.

LIST별 자동 파티션

LIST에 의한 자동 파티션은 regiondepartment 와 같은 시간 기반이 아닌 차원을 기반으로 데이터를 분할하는 것입니다. 이는 LIST에 의한 데이터 파티셔닝을 지원하지 않는 동적 파티션의 격차를 메웁니다.


Auto Partition by RANGE는 시간 차원에 따라 최적화된 파티셔닝 솔루션을 제공합니다. 매개변수 구성 측면에서 동적 파티션보다 더 유연합니다. 이에 대한 구문은 다음과 같습니다.


 AUTO PARTITION BY LIST (`partition_col`) ()


다음은 city 파티션 열로 사용하는 LIST별 자동 파티션의 예입니다.


 mysql> CREATE TABLE `str_table` ( -> `city` VARCHAR NOT NULL, -> ...... -> ) -> DUPLICATE KEY(`city`) -> AUTO PARTITION BY LIST (`city`) -> () -> DISTRIBUTED BY HASH(`city`) BUCKETS 10 -> PROPERTIES ( -> ...... -> ); Query OK, 0 rows affected (0.09 sec) mysql> insert into str_table values ("Denver"), ("Boston"), ("Los_Angeles"); Query OK, 3 rows affected (0.25 sec) mysql> show partitions from str_table; +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 589685 | pDenver7 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Denver]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 589643 | pLos5fAngeles11 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Los_Angeles]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 589664 | pBoston8 | 2 | 2024-06-01 20:12:37 | NORMAL | city | [types: [VARCHAR]; keys: [Boston]; ] | city | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+-------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.10 sec)


덴버, 보스턴, 로스앤젤레스 도시에 대한 데이터를 삽입한 후 시스템은 도시 이름을 기반으로 해당 파티션을 자동으로 생성했습니다. 이전에는 이러한 유형의 사용자 정의 파티셔닝이 수동 DDL 문을 통해서만 가능했습니다. 이것이 바로 LIST별 자동 파티션이 데이터베이스 유지 관리를 단순화하는 방법입니다.

팁과 참고사항

기록 파티션을 수동으로 조정

실시간 데이터와 가끔 기록 업데이트를 모두 수신하는 테이블의 경우 자동 파티션은 기록 파티션을 자동으로 회수하지 않으므로 다음 두 가지 옵션을 권장합니다.


  • 가끔 기록 데이터 업데이트를 위해 자동으로 파티션을 생성하는 자동 파티션을 사용합니다.

  • 자동 파티션을 사용하고 기록 업데이트를 수용하기 위해 LESS THAN 파티션을 수동으로 만듭니다. 이를 통해 과거 데이터와 실시간 데이터를 보다 명확하게 구분할 수 있으며 데이터 관리가 더 쉬워집니다.


 mysql> CREATE TABLE DAILY_TRADE_VALUE -> ( -> `TRADE_DATE` DATEV2 NOT NULL COMMENT 'Trade Date', -> `TRADE_ID` VARCHAR(40) NOT NULL COMMENT 'Trade ID' -> ) -> AUTO PARTITION BY RANGE (DATE_TRUNC(`TRADE_DATE`, 'DAY')) -> ( -> PARTITION `pHistory` VALUES LESS THAN ("2024-01-01") -> ) -> DISTRIBUTED BY HASH(`TRADE_DATE`) BUCKETS 10 -> PROPERTIES -> ( -> "replication_num" = "1" -> ); Query OK, 0 rows affected (0.11 sec) mysql> insert into DAILY_TRADE_VALUE values ('2015-01-01', 1), ('2020-01-01', 2), ('2024-03-05', 10000), ('2024-03-06', 10001); Query OK, 4 rows affected (0.25 sec) {'label':'label_96dc3d20c6974f4a_946bc1a674d24733', 'status':'VISIBLE', 'txnId':'85092'} mysql> show partitions from DAILY_TRADE_VALUE; +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 577871 | pHistory | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [0000-01-01]; ..types: [DATEV2]; keys: [2024-01-01]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577940 | p20240305000000 | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-05]; ..types: [DATEV2]; keys: [2024-03-06]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577919 | p20240306000000 | 2 | 2024-06-01 08:53:49 | NORMAL | TRADE_DATE | [types: [DATEV2]; keys: [2024-03-06]; ..types: [DATEV2]; keys: [2024-03-07]; ) | TRADE_DATE | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+-----------------+----------------+---------------------+--------+--------------+--------------------------------------------------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.10 sec)


NULL 파티션

LIST에 의한 자동 파티션을 통해 Doris는 NULL 파티션에 NULL 값을 저장하는 것을 지원합니다. 예를 들어:


 mysql> CREATE TABLE list_nullable -> ( -> `str` varchar NULL -> ) -> AUTO PARTITION BY LIST (`str`) -> () -> DISTRIBUTED BY HASH(`str`) BUCKETS auto -> PROPERTIES -> ( -> "replication_num" = "1" -> ); Query OK, 0 rows affected (0.10 sec) mysql> insert into list_nullable values ('123'), (''), (NULL); Query OK, 3 rows affected (0.24 sec) {'label':'label_f5489769c2f04f0d_bfb65510f9737fff', 'status':'VISIBLE', 'txnId':'85089'} mysql> show partitions from list_nullable; +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | PartitionId | PartitionName | VisibleVersion | VisibleVersionTime | State | PartitionKey | Range | DistributionKey | Buckets | ReplicationNum | StorageMedium | CooldownTime | RemoteStoragePolicy | LastConsistencyCheckTime | DataSize | IsInMemory | ReplicaAllocation | IsMutable | SyncWithBaseTables | UnsyncTables | +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ | 577297 | pX | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: [NULL]; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577276 | p0 | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: []; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | | 577255 | p1233 | 2 | 2024-06-01 08:19:21 | NORMAL | str | [types: [VARCHAR]; keys: [123]; ] | str | 10 | 1 | HDD | 9999-12-31 23:59:59 | | NULL | 0.000 | false | tag.location.default: 1 | true | true | NULL | +-------------+---------------+----------------+---------------------+--------+--------------+------------------------------------+-----------------+---------+----------------+---------------+---------------------+---------------------+--------------------------+----------+------------+-------------------------+-----------+--------------------+--------------+ 3 rows in set (0.11 sec)


그러나 RANGE에 의한 자동 파티션은 NULL 파티션을 지원하지 않습니다. NULL 값은 가장 작은 LESS THAN 파티션에 저장되고 이에 대한 적절한 범위를 안정적으로 결정하는 것이 불가능하기 때문입니다. 자동 파티션이 (-INFINITY, MIN_VALUE) 범위의 NULL 파티션을 생성하는 경우 MIN_VALUE 경계가 의도한 비즈니스 논리를 정확하게 나타내지 않을 수 있으므로 프로덕션에서 이 파티션이 실수로 삭제될 위험이 있습니다.

요약

자동 파티션은 동적 파티션의 대부분의 사용 사례를 포괄하는 동시에 사전 파티션 규칙 정의의 이점을 도입합니다. 규칙이 정의되면 DBA 대신 Doris가 대량의 파티션 생성 작업을 자동으로 처리합니다.


자동 파티션을 활용하기 전에 관련 제한 사항을 이해하는 것이 중요합니다.


  1. LIST에 의한 자동 파티션은 여러 열을 기반으로 한 파티셔닝을 지원하지만 자동으로 생성된 각 파티션에는 단일 값만 포함되며 파티션 이름 길이는 50자를 초과할 수 없습니다. 파티션 이름은 메타데이터 관리에 특별한 영향을 미치는 특정 명명 규칙을 따릅니다. 이는 50자 공간 중 일부를 사용자가 사용할 수 없다는 의미입니다.


  2. RANGE에 의한 자동 파티션은 DATE 또는 DATETIME 유형이어야 하는 단일 파티션 열만 지원합니다.


  3. LIST에 의한 자동 파티션은 NULLABLE 파티션 열과 NULL 값 삽입을 지원합니다. RANGE별 자동 파티션은 NULLABLE 파티션 열을 지원하지 않습니다.


  4. Apache Doris 2.1.3 이후에는 동적 파티션과 함께 자동 파티션을 사용하지 않는 것이 좋습니다.

성능 비교

자동 파티션과 동적 파티션의 주요 기능적 차이점은 파티션 생성 및 삭제, 지원되는 파티션 유형, 가져오기 성능에 미치는 영향에 있습니다.


동적 파티션은 고정 스레드를 사용하여 주기적으로 파티션을 생성하고 회수합니다. RANGE별 파티셔닝만 지원합니다. 이와 대조적으로 자동 파티션은 RANGE 및 LIST별 파티셔닝을 모두 지원합니다. 데이터 수집 중 특정 규칙에 따라 필요에 따라 자동으로 파티션을 생성하여 더 높은 수준의 자동화와 유연성을 제공합니다.


동적 파티션은 데이터 수집 속도를 늦추지 않는 반면, 자동 파티션은 먼저 기존 파티션을 확인한 다음 필요에 따라 새 파티션을 생성하기 때문에 특정 시간 오버헤드를 발생시킵니다. 성능 테스트 결과를 알려드리겠습니다.



자동 파티션: 수집 워크플로

Auto Partition 메커니즘을 통해 데이터 수집을 구현하는 방법에 대한 부분으로, Stream Load를 예로 들어보겠습니다. Doris가 데이터 가져오기를 시작하면 Doris 백엔드 노드 중 하나가 코디네이터 역할을 맡습니다. 초기 데이터 처리 작업을 담당한 다음 실행을 위해 실행자라고 하는 적절한 BE 노드에 데이터를 디스패치합니다.



코디네이터 실행 파이프라인의 최종 Datasink 노드에서 데이터는 성공적으로 전송 및 저장되기 전에 올바른 파티션, 버킷 및 Doris 백엔드 노드 위치로 라우팅되어야 합니다.


이 데이터 전송을 활성화하기 위해 코디네이터 및 실행자 노드는 통신 채널을 설정합니다.


  • 송신단을 노드 채널이라고 합니다.
  • 수신단을 태블릿 채널이라고 합니다.


데이터에 대한 올바른 파티션을 결정하는 과정에서 자동 파티션이 작동하는 방식은 다음과 같습니다.



이전에는 자동 파티션이 없으면 테이블에 필요한 파티션이 없을 때 Doris의 동작은 DATA_QUALITY_ERROR 가 보고될 때까지 BE 노드에서 오류를 누적하는 것이었습니다. 이제 자동 파티션이 활성화되면 필요한 파티션을 즉시 생성하라는 요청이 Doris 프런트엔드에 시작됩니다. 파티션 생성 트랜잭션이 완료된 후 Doris Frontend는 코디네이터에 응답하고 코디네이터는 해당 통신 채널(노드 채널 및 태블릿 채널)을 열어 데이터 수집 프로세스를 계속합니다. 이는 사용자에게 원활한 경험입니다.


실제 클러스터 환경에서는 코디네이터가 Doris 프런트엔드가 파티션 생성을 완료할 때까지 기다리는 데 소요되는 시간으로 인해 큰 오버헤드가 발생할 수 있습니다. 이는 Thrift RPC 호출의 고유한 대기 시간과 로드가 높은 조건에서 프런트엔드의 잠금 경합 때문입니다.


자동 파티션의 데이터 수집 효율성을 향상시키기 위해 Doris는 일괄 처리를 구현하여 FE에 대한 RPC 호출 수를 크게 줄였습니다. 이는 데이터 쓰기 작업의 성능을 눈에 띄게 향상시킵니다.


FE 마스터가 파티션 생성 트랜잭션을 완료하면 새 파티션이 즉시 표시됩니다. 그러나 가져오기 프로세스가 최종적으로 실패하거나 취소되면 생성된 파티션이 자동으로 회수되지 않습니다.

자동 파티션 성능

우리는 다양한 사용 사례를 다루면서 Doris에서 자동 파티션의 성능과 안정성을 테스트했습니다.


사례 1 : 프런트엔드 1개 + 백엔드 3개; 무작위로 생성된 6개의 데이터세트(각각 1억 개의 행과 2,000개의 파티션 포함) 6개의 데이터세트를 6개의 테이블에 동시에 수집


  • 목표 : 높은 압력에서 자동 파티션의 성능을 평가하고 성능 저하가 있는지 확인합니다.


  • 결과 : 자동 파티션은 모든 가져오기 트랜잭션이 안정적으로 실행되면서 평균 성능 손실이 5% 미만입니다 .



사례 2 : 프런트엔드 1개 + 백엔드 3개; 루틴 로드를 통해 Flink에서 초당 100개 행을 수집합니다. 각각 1, 10, 20개의 동시 트랜잭션(테이블)으로 테스트


  • 목표 : 다양한 동시성 수준에서 자동 파티션으로 인해 발생할 수 있는 잠재적인 데이터 백로그 문제를 식별합니다.


  • 결과 : 자동 파티션 활성화 여부에 관계없이 CPU 사용률이 100%에 가까워졌을 때 20개의 동시 트랜잭션에서도 테스트된 모든 동시성 수준에서 역압 문제 없이 데이터 수집이 성공적이었습니다.



이러한 테스트 결과를 결론적으로 말하면, 자동 파티션 활성화가 데이터 수집 성능에 미치는 영향은 최소화됩니다.

결론 및 향후 계획

자동 파티션은 Apache Doris 2.1.0부터 DDL 및 파티션 관리를 단순화했습니다. 대규모 데이터 처리에 유용하며 사용자가 다른 데이터베이스 시스템에서 Apache Doris로 쉽게 마이그레이션할 수 있도록 해줍니다.


또한, 우리는 더 복잡한 데이터 유형을 지원하기 위해 자동 파티션 기능을 확장하기 위해 최선을 다하고 있습니다.


RANGE별 자동 파티션 계획:


  • 숫자 값을 지원합니다.


  • 사용자가 파티션 범위의 왼쪽 및 오른쪽 경계를 지정할 수 있습니다.


LIST별 자동 파티션 계획:


  • 특정 규칙에 따라 여러 값을 동일한 파티션에 병합할 수 있습니다.