Compressed Big Decimal
Compressed Big Decimal (압축 빅데시멀)
druid-compressed-bigdecimal 확장은 정밀도를 잃지 않으면서도 메모리를 재할당하지 않고 값을 누적할 수 있는 Mutable 빅데시멀 타입을 제공해요. 금융 앱이나 통화 기반 트랜잭션처럼 높은 정확도가 필요한 곳에서 반올림 문제로 돈이 유실되는 상황을 피하고 싶을 때 유용해요.
출처: 문서
본문
Overview
Compressed Big Decimal은 정밀도를 잃지 않고 값을 누적(accumulate)할 수 있으며, 메모리를 재할당하지 않아도 되는 Mutable 빅데시멀 값을 지원하는 확장이에요. 이 타입은 금융 애플리케이션, 통화 기반 트랜잭션처럼 더 높은 정확도가 요구되는 애플리케이션에서 큰 숫자에 대한 절대 정밀도 산술을 도와줘요. 잠재적으로 큰 금액이 유실될 수 있는 반올림 문제를 피하는 데 유용하죠.
누적에는 두 숫자가 같은 scale을 가져야 하지만, 같은 크기일 필요는 없어요. 누적되는 값이 이 값(결과)보다 더 큰 내부 배열을 가진다면, long을 int에 더한 뒤 결과를 int에 저장할 때처럼 높은 자리 비트(higher order bits)가 버려져요. Compressed big decimal은 내장 배열(embedded array)로 데이터를 보관해요.
Compressed big decimal은 Java의 big decimal에 기반한 절대 숫자 기반 복합 타입이에요. Java Big Decimal이 지원하는 모든 기능을 지원하죠. Java Big Decimal은 큰 가비지 컬렉션 문제를 피하려고 mutable하지 않아요. 그래서 누적기(accumulator) 안에서 값을 변경하려면 Compressed big decimal이 필요해요.
이 확장이 제공하는 주요 개선점:
- 기능(Functionality): 더 높은 정밀도로 big decimal 타입을 변경(mutate).
- 정확도(Accuracy): 소수점 산술에서 더 높은 정확도 제공.
Operations
이 확장을 사용하려면 설정 파일에 druid-compressed-bigdecimal을 로드해 주세요.
druid-compressed-bigdecimal
Configuration
현재 Compressed Big Decimal에 특화된 설정 속성은 없어요.
Limitations
누적되는 값이 이 값(결과)보다 더 큰 내부 배열을 가진다면, Compressed Big Decimal은 올바른 결과를 주지 못해요. 이 경우 long을 int에 더하고 결과를 int에 저장할 때처럼 높은 자리 비트가 버려지거든요.
Ingestion Spec
Ingest spec의 대부분의 속성은 Ingestion Spec / Data Formats에서 파생돼요.
| 속성 | 설명 | 필수 |
|---|---|---|
metricsSpec |
Metrics Specification. 메트릭 상세(예: name, type)를 지정할 때 type을 compressedBigDecimal로 지정해야 해요. |
Yes |
Query spec
query spec의 대부분의 속성은 groupBy query / timeseries에서 파생돼요. 이들 질의 타입에 대한 문서를 참고하세요.
| 속성 | 설명 | 필수 |
|---|---|---|
queryType |
이 String은 항상 "groupBy" 또는 "timeseries"여야 해요. Druid가 질의를 어떻게 해석할지 가장 먼저 확인하는 값이에요. |
yes |
dataSource |
질의할 데이터 소스를 정의하는 String 또는 Object. 관계형 DB의 테이블과 매우 유사해요. DataSource에서 자세히 볼 수 있어요. | yes |
dimensions |
DimensionSpec의 JSON 리스트 (이 속성은 선택 항목이에요). | no |
limitSpec |
LimitSpec 참고. | no |
having |
Having 참고. | no |
granularity |
period granularity. Period Granularities 참고. | yes |
filter |
Filters 참고. | no |
aggregations |
Aggregations는 Averagers의 입력이 돼요. Aggregations 참고. compressedBigDecimal 타입의 경우 type, scale, size를 다음과 같이 지정해야 해요. |
Yes |
postAggregations |
입력으로 aggregations만 지원해요. Post Aggregations 참고. | no |
intervals |
ISO-8601 Intervals를 나타내는 JSON Object. 질의를 실행할 시간 범위를 정의해요. | yes |
context |
특정 플래그를 지정할 수 있는 추가 JSON Object. | no |
compressedBigDecimal 타입의 aggregations 예시:
"aggregations": [{"type": "compressedBigDecimal","name": "..","fieldName": "..","scale": [Numeric],"size": [Numeric]}
Examples 섹션의 질의 예시를 참고해 주세요.
Examples
다음과 같은 데이터를 생각해 볼게요.
| Date | Item | SaleAmount |
|---|---|---|
| 20201208 | ItemA | 0.0 |
| 20201208 | ItemB | 10.000000000 |
| 20201208 | ItemA | -1.000000000 |
| 20201208 | ItemC | 9999999999.000000000 |
| 20201208 | ItemB | 5000000000.000000005 |
| 20201208 | ItemA | 2.0 |
| 20201208 | ItemD | 0.0 |
IngestionSpec 문법:
{
"type": "index_parallel",
"spec": {
"dataSchema": {
"dataSource": "invoices",
"timestampSpec": {
"column": "timestamp",
"format": "yyyyMMdd"
},
"dimensionsSpec": {
"dimensions": [{
"type": "string",
"name": "itemName"
}]
},
"metricsSpec": [{
"name": "saleAmount",
"type": "compressedBigDecimalSum",
"fieldName": "saleAmount"
}],
"transformSpec": {
"filter": null,
"transforms": []
},
"granularitySpec": {
"type": "uniform",
"rollup": false,
"segmentGranularity": "DAY",
"queryGranularity": "none",
"intervals": ["2020-12-08/2020-12-09"]
}
},
"ioConfig": {
"type": "index_parallel",
"inputSource": {
"type": "local",
"baseDir": "/home/user/sales/data/staging/invoice-data",
"filter": "invoice-001.20201208.txt"
},
"inputFormat": {
"type": "tsv",
"delimiter": ",",
"skipHeaderRows": 0,
"columns": [
"timestamp",
"itemName",
"saleAmount"
]
}
},
"tuningConfig": {
"type": "index_parallel"
}
}
}
SQL 기반 수집 샘플 질의:
REPLACE INTO "bigdecimal" OVERWRITE ALL
WITH "ext" AS (
SELECT *
FROM TABLE(
EXTERN(
'{"type":"local","baseDir":"/home/user/sales/data/staging/invoice-data","filter":"invoice-001.20201208.txt"}',
'{"type":"csv","findColumnsFromHeader":false,"columns":["timestamp","itemName","saleAmount"]}',
'[{"name":"timestamp","type":"string"},{"name":"itemName","type":"string"},{"name":"saleAmount","type":"double"}]'
)
)
)
SELECT
TIME_PARSE(TRIM("timestamp")) AS "__time",
"itemName",
BIG_SUM("saleAmount") as amount
FROM "ext"
group by TIME_PARSE(TRIM("timestamp")) , itemName
PARTITIONED BY DAY
Group By Query 예시
모든 판매를 groupBy로 집계해 봅시다.
질의 문법:
{
"queryType": "groupBy",
"dataSource": "invoices",
"granularity": "ALL",
"dimensions": [
],
"aggregations": [
{
"type": "compressedBigDecimalSum",
"name": "saleAmount",
"fieldName": "saleAmount",
"scale": 9,
"size": 3
}
],
"intervals": [
"2020-01-08T00:00:00.000Z/P1D"
]
}
결과:
[ {
"version" : "v1",
"timestamp" : "2020-12-08T00:00:00.000Z",
"event" : {
"revenue" : 15000000010.000000005
}
} ]
만약 compressedBigDecimalSum 대신 doubleSum을 사용했다면 결과는 다음과 같았을 거예요.
[ {
"timestamp" : "2020-12-08T00:00:00.000Z",
"result" : {
"revenue" : 1.500000001E10
}
} ]
위에서 보듯이 정밀도가 유실되고, 그로 인해 돈이 손실될 수 있어요.
TimeSeries Query 예시
질의 문법:
{
"queryType": "timeseries",
"dataSource": "invoices",
"granularity": "ALL",
"aggregations": [
{
"type": "compressedBigDecimalSum",
"name": "revenue",
"fieldName": "revenue",
"scale": 9,
"size": 3
}
],
"filter": {
"type": "not",
"field": {
"type": "selector",
"dimension": "itemName",
"value": "ItemD"
}
},
"intervals": [
"2020-12-08T00:00:00.000Z/P1D"
]
}
결과:
[ {
"timestamp" : "2020-12-08T00:00:00.000Z",
"result" : {
"revenue" : 15000000010.000000005
}
} ]
Supported Query Functions
Native 집계 함수:
compressedBigDecimalSum
compressedBigDecimalMin
compressedBigDecimalMax
SQL 집계 함수:
big_sum()
big_min()
big_max()
더 알아보기 (Learn more)
- 그룹바이 질의와 타임시리즈 질의 문서에서 이 확장이 기반하는 질의 타입을 살펴볼 수 있어요.
- Aggregations 문서에서 Druid 집계 전반을 확인해 보세요.