다음 예시는 구조화된 JSON 데이터와 반정형 JSON 데이터를 로드하는 아주 간단한 방법을 보여줍니다. 중첩 구조를 포함한 더 복잡한 JSON은 JSON 스키마 설계 가이드를 참조하십시오.
구조화된 JSON 로딩
이 섹션에서는 JSON 데이터가 NDJSON (줄바꿈으로 구분된 JSON) 포맷, 즉 ClickHouse에서 JSONEachRow로 알려진 포맷으로 제공되고, 컬럼 이름과 타입이 고정된 구조화된 형태임을 가정합니다. NDJSON은 간결하고 공간 효율이 높아 JSON 로딩에 권장되는 포맷이지만, 입력 및 출력 모두에서 다른 포맷도 지원됩니다.
다음은 Python PyPI 데이터셋의 행을 나타내는 JSON 샘플입니다:
{
"date": "2022-11-15",
"country_code": "ES",
"project": "clickhouse-connect",
"type": "bdist_wheel",
"installer": "pip",
"python_minor": "3.9",
"system": "Linux",
"version": "0.3.0"
}이 JSON 객체를 ClickHouse에 로드하려면 테이블 스키마(schema)를 정의해야 합니다.
이 간단한 예시에서는 구조가 정적이고, 컬럼 이름도 알려져 있으며, 타입도 명확하게 정의되어 있습니다.
ClickHouse는 JSON 타입을 통해 반정형 데이터를 지원하여 키 이름과 타입을 동적으로 변경할 수 있지만, 여기서는 그럴 필요가 없습니다.
이에 대한 간단한 스키마는 아래와 같으며, JSON 키가 컬럼 이름에 매핑됩니다:
CREATE TABLE pypi (
`date` Date,
`country_code` String,
`project` String,
`type` String,
`installer` String,
`python_minor` String,
`system` String,
`version` String
)
ENGINE = MergeTree
ORDER BY (project, date)ClickHouse는 확장자와 내용을 기반으로 유형을 자동으로 추론하여 여러 포맷의 JSON 데이터를 로드할 수 있습니다. 위 테이블의 JSON 파일은 s3 함수를 사용하여 읽을 수 있습니다:
SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz', NOSIGN)
LIMIT 1┌───────date─┬─country_code─┬─project────────────┬─type────────┬─installer────┬─python_minor─┬─system─┬─version─┐
│ 2022-11-15 │ CN │ clickhouse-connect │ bdist_wheel │ bandersnatch │ │ │ 0.2.8 │
└────────────┴──────────────┴────────────────────┴─────────────┴──────────────┴──────────────┴────────┴─────────┘
1 row in set. Elapsed: 1.232 sec.파일 포맷을 별도로 지정하지 않아도 된다는 점에 주목하십시오. 대신 glob 패턴을 사용하여 버킷 내의 모든 *.json.gz 파일을 읽습니다. ClickHouse는 파일 확장자와 내용을 바탕으로 포맷이 JSONEachRow (ndjson)임을 자동으로 추론합니다. ClickHouse가 포맷을 감지하지 못하는 경우, 매개변수 함수를 통해 포맷을 수동으로 지정할 수 있습니다.
SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz', NOSIGN, JSONEachRow)이 파일의 행을 불러오려면 INSERT INTO SELECT를 사용할 수 있습니다:
INSERT INTO pypi SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz', NOSIGN)Ok.
0 rows in set. Elapsed: 10.445 sec. Processed 19.49 million rows, 35.71 MB (1.87 million rows/s., 3.42 MB/s.)SELECT * FROM pypi LIMIT 2┌───────date─┬─country_code─┬─project────────────┬─type──┬─installer────┬─python_minor─┬─system─┬─version─┐
│ 2022-05-26 │ CN │ clickhouse-connect │ sdist │ bandersnatch │ │ │ 0.0.7 │
│ 2022-05-26 │ CN │ clickhouse-connect │ sdist │ bandersnatch │ │ │ 0.0.7 │
└────────────┴──────────────┴────────────────────┴───────┴──────────────┴──────────────┴────────┴─────────┘
2 rows in set. Elapsed: 0.005 sec. Processed 8.19 thousand rows, 908.03 KB (1.63 million rows/s., 180.38 MB/s.)행은 FORMAT 절을 사용해 문장 내에서 직접 로드할 수도 있습니다. 예:
INSERT INTO pypi
FORMAT JSONEachRow
{"date":"2022-11-15","country_code":"CN","project":"clickhouse-connect","type":"bdist_wheel","installer":"bandersnatch","python_minor":"","system":"","version":"0.2.8"}이 예시에서는 JSONEachRow 포맷을 사용한다고 가정합니다. 널리 사용되는 다른 JSON 포맷도 지원하며, 이러한 포맷을 로드하는 예시는 여기에서 확인할 수 있습니다.
반정형 JSON 로드하기
이전 예시에서는 키 이름과 타입이 고정되어 있고 잘 알려진 JSON을 로드했습니다. 하지만 실제로는 그렇지 않은 경우가 많습니다. 키가 추가되거나 타입이 변경될 수 있습니다. 이는 관측성 데이터와 같은 사용 사례에서 흔히 발생합니다.
ClickHouse는 전용 JSON 타입으로 이를 처리합니다.
다음은 위의 Python PyPI 데이터셋을 확장한 예시입니다. 여기서는 임의의 키-값 쌍으로 이루어진 tags 컬럼을 추가했습니다.
{
"date": "2022-09-22",
"country_code": "IN",
"project": "clickhouse-connect",
"type": "bdist_wheel",
"installer": "bandersnatch",
"python_minor": "",
"system": "",
"version": "0.2.8",
"tags": {
"5gTux": "f3to*PMvaTYZsz!*rtzX1",
"nD8CV": "value"
}
}여기서 tags 컬럼은 예측할 수 없으므로 모델링할 수 없습니다. 이 데이터를 로드하려면 이전에 사용한 스키마를 그대로 사용하되, JSON 타입의 tags 컬럼을 추가로 제공하면 됩니다:
SET enable_json_type = 1;
CREATE TABLE pypi_with_tags
(
`date` Date,
`country_code` String,
`project` String,
`type` String,
`installer` String,
`python_minor` String,
`system` String,
`version` String,
`tags` JSON
)
ENGINE = MergeTree
ORDER BY (project, date);원본 데이터셋과 동일한 방식으로 테이블을 채웁니다:
INSERT INTO pypi_with_tags SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/pypi_with_tags/sample.json.gz', NOSIGN)INSERT INTO pypi_with_tags SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/pypi_with_tags/sample.json.gz', NOSIGN)Ok.
0 rows in set. Elapsed: 255.679 sec. Processed 1.00 million rows, 29.00 MB (3.91 thousand rows/s., 113.43 KB/s.)
Peak memory usage: 2.00 GiB.SELECT *
FROM pypi_with_tags
LIMIT 2┌───────date─┬─country_code─┬─project────────────┬─type──┬─installer────┬─python_minor─┬─system─┬─version─┬─tags─────────────────────────────────────────────────────┐
│ 2022-05-26 │ CN │ clickhouse-connect │ sdist │ bandersnatch │ │ │ 0.0.7 │ {"nsBM":"5194603446944555691"} │
│ 2022-05-26 │ CN │ clickhouse-connect │ sdist │ bandersnatch │ │ │ 0.0.7 │ {"4zD5MYQz4JkP1QqsJIS":"0","name":"8881321089124243208"} │
└────────────┴──────────────┴────────────────────┴───────┴──────────────┴──────────────┴────────┴─────────┴──────────────────────────────────────────────────────────┘
2 rows in set. Elapsed: 0.149 sec.여기서는 데이터를 로드할 때의 성능 차이에 주목하십시오. JSON 컬럼은 삽입 시점에 타입 추론이 필요하며, 두 개 이상의 타입을 갖는 컬럼이 있으면 추가 저장 공간도 필요합니다. JSON 타입은 명시적으로 컬럼을 선언한 것과 동등한 성능을 내도록 구성할 수 있지만(JSON 스키마 설계 참조), 기본적으로는 의도적으로 유연하게 설계되어 있습니다. 하지만 이러한 유연성에는 비용이 따릅니다.
JSON 타입을 사용해야 하는 경우
데이터가 다음과 같은 경우 JSON 타입을 사용합니다.
- 시간에 따라 변경될 수 있는 예측 불가능한 키가 있습니다.
- 타입이 다양한 값을 포함합니다(예: 경로에 문자열이 들어갈 때도 있고 숫자가 들어갈 때도 있습니다).
- 엄격한 타입 지정이 적합하지 않아 스키마 유연성이 필요합니다.
데이터 구조를 알고 있고 일관적이라면, 데이터가 JSON 포맷이더라도 JSON 타입이 필요한 경우는 거의 없습니다. 구체적으로 데이터에 다음과 같은 특성이 있으면 다음을 사용하십시오.
- 알려진 키를 가진 평면 구조:
String과 같은 표준 컬럼 타입을 사용합니다. - 예측 가능한 중첩 구조: 이러한 구조에는 Tuple, 배열, 또는 Nested 타입을 사용합니다.
- 구조는 예측 가능하지만 타입은 달라질 수 있음: 이 경우에는 Dynamic 또는 Variant 타입을 고려하십시오.
위 예시처럼 접근 방식을 혼합할 수도 있습니다. 예측 가능한 최상위 키에는 정적 컬럼을 사용하고, payload의 동적 섹션에는 단일 JSON 컬럼을 사용할 수 있습니다.