ClickHouse 官方文档:处理 JSON 数据 (1/2)

图片

本文字数:27902;估计阅读时间:70分钟

作者: ClickHouse team

本文在公众号【ClickHouseInc】首发

概述

ClickHouse 提供了多种处理 JSON 的方法,每种方法都有其优缺点和适用场景。在本指南中,我们将介绍如何加载 JSON 并优化架构设计。本指南包括以下内容:

  • 加载 JSON:在 ClickHouse 中使用简单架构加载和查询 JSON(尤其是 NDJSON)。

  • JSON 架构推断:通过 JSON 架构推断查询 JSON 并生成表结构。

  • 设计 JSON 架构:设计并优化 JSON 架构的步骤。

  • 导出 JSON:JSON 的导出方法。

  • 处理其他 JSON 格式:处理非 NDJSON 格式 JSON 的技巧。

  • 其他 JSON 建模方法:高级 JSON 建模方法(不推荐使用)。

重要:全新 JSON 类型已进入 Beta 阶段  

本指南涵盖现有的 JSON 处理技术。值得注意的是,ClickHouse 已推出一种新的 JSON 类型,目前处于 Beta 阶段。详情请参见这里。【https://clickhouse.com/docs/en/sql-reference/data-types/newjson】

加载 JSON 数据  

本节假设 JSON 数据采用 NDJSON(换行分隔的 JSON,Newline delimited JSON)格式,在 ClickHouse 中称为 JSONEachRow。这种格式因其简洁性和高效的空间利用率而成为加载 JSON 的首选,但 ClickHouse 同样支持其他格式的输入和输出。

以下示例展示了一行来自 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 中,需要先定义表架构。以下是一个简单的架构示例,其中 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)

排序键  

我们使用 ORDER BY 子句定义了一个排序键。有关排序键的详细信息以及如何选择,请参考相关文档。【https://clickhouse.com/docs/en/data-modeling/schema-design#choosing-an-ordering-key】

ClickHouse 支持以多种格式加载 JSON 数据,并能根据文件扩展名和内容自动推断其类型。以下示例中,我们通过 S3 函数读取了与上述表对应的 JSON 文件:

SELECT *
FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz')
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.

注意,这里无需显式指定文件格式。我们通过通配模式读取桶中的所有 *.json.gz 文件,ClickHouse 会根据文件扩展名和内容自动识别格式为 JSONEachRow(ndjson)。如果格式无法自动识别,可以通过参数函数手动指定。

SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz', JSONEachRow)

压缩文件 

上述文件均已压缩,ClickHouse 能够自动检测并处理这些压缩文件。

要加载这些文件中的数据行,可以使用 INSERT INTO SELECT:

INSERT INTO pypi SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/pypi/json/*.json.gz')
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────────────┐
│ 2022-05-26 │ CN       	│ clickhouse-connect │
│ 2022-05-26 │ CN       	│ clickhouse-connect │
└────────────┴──────────────┴────────────────────┘

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 格式。ClickHouse 还支持其他常见的 JSON 格式,其加载方法请参考相关示例。【https://clickhouse.com/docs/en/integrations/data-formats/json/other-formats】

上述内容展示了加载 JSON 数据的基础方法。对于更复杂的 JSON 数据结构(如嵌套结构),请参考“设计 JSON 架构”指南。【https://clickhouse.com/docs/en/integrations/data-formats/json/schema】

JSON 架构自动推断  

ClickHouse 支持自动推断 JSON 数据的结构。这一功能允许直接查询 JSON 数据,例如使用 clickhouse-local 查询磁盘数据,或查询存储在 S3 存储桶中的数据。此外,还可以在数据加载到 ClickHouse 之前自动生成表架构。

适用场景

  • 结构一致:用于类型推断的数据需包含所有目标列。如果推断完成后数据增加了额外的列,这些列可能无法被查询。

  • 类型一致:特定列的类型需要相互兼容。

注意事项 

如果 JSON 数据具有动态结构,例如频繁新增键但未能及时更新架构(如日志中的 Kubernetes 标签),建议参考“设计 JSON 架构”指南。

类型检测

在之前的示例中,我们使用了 NDJSON 格式的 Python PyPI 数据集的简单版本。本节将探索一个更复杂的数据集——arXiv 数据集。该数据集包含约 250 万篇学术论文,以 NDJSON 格式分发,每一行代表一篇已发表的学术论文。以下为其中一行示例:

{
  "id": "2101.11408",
  "submitter": "Daniel Lemire",
  "authors": "Daniel Lemire",
  "title": "Number Parsing at a Gigabyte per Second",
  "comments": "Software at https://github.com/fastfloat/fast_float and\n  https://github.com/lemire/simple_fastfloat_benchmark/",
  "journal-ref": "Software: Practice and Experience 51 (8), 2021",
  "doi": "10.1002/spe.2984",
  "report-no": null,
  "categories": "cs.DS cs.MS",
  "license": "http://creativecommons.org/licenses/by/4.0/",
  "abstract": "With disks and networks providing gigabytes per second ....\n",
  "versions": [
    {
      "created": "Mon, 11 Jan 2021 20:31:27 GMT",
      "version": "v1"
    },
    {
      "created": "Sat, 30 Jan 2021 23:57:29 GMT",
      "version": "v2"
    }
  ],
  "update_date": "2022-11-07",
  "authors_parsed": [
    [
      "Lemire",
      "Daniel",
      ""
    ]
  ]
}

该数据集需要更复杂的架构设计。以下将概述定义此架构的过程,并介绍如 Tuple 和 Array 等复杂类型。

数据集存储于公共 S3 存储桶,路径为 s3://datasets-documentation/arxiv/arxiv.json.gz。

如示例所示,该数据集包含嵌套的 JSON 对象。尽管用户通常需要设计并版本化架构,但通过类型推断功能,可直接从数据中推断出类型。此功能允许自动生成架构的 DDL,避免手动创建架构,加速开发流程。

自动格式检测

除了自动推断架构,JSON 架构推断还会根据文件扩展名和内容检测数据格式。上述文件会被系统自动识别为 NDJSON 格式。

通过结合 s3 函数与 DESCRIBE 命令,可查看推断出的数据类型。

DESCRIBE TABLE s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/arxiv/arxiv.json.gz')
SETTINGS describe_compact_output = 1
┌─name───────────┬─type────────────────────────────────────────────────────────────────────┐
│ id             │ Nullable(String)                                                        │
│ submitter      │ Nullable(String)                                                        │
│ authors        │ Nullable(String)                                                        │
│ title          │ Nullable(String)                                                        │
│ comments       │ Nullable(String)                                                        │
│ journal-ref    │ Nullable(String)                                                        │
│ doi            │ Nullable(String)                                                        │
│ report-no      │ Nullable(String)                                                        │
│ categories     │ Nullable(String)                                                        │
│ license        │ Nullable(String)                                                        │
│ abstract       │ Nullable(String)                                                        │
│ versions       │ Array(Tuple(created Nullable(String),version Nullable(String)))         │
│ update_date    │ Null
<think>我们正在排查Spark流式任务写入ClickHouse时的权限问题,特别是与`/var/lib/clickhouse`目录权限和用户身份相关的问题。根据之前的交互,我们已经观察到以下关键信息:1.在ClickHouse容器内执行`id`命令,显示当前用户为root(uid=0):```uid=0(root)gid=0(root)groups=0(root)``` 2.在Spark容器(spark-master)内执行`id`命令,同样显示为root(uid=0)。3.错误信息显示在重命名操作(rename)时出现"Permission denied"。###问题分析`Permissiondenied`错误通常发生在文件系统操作时,进程对目标目录没有足够的权限。在Spark写入ClickHouse的过程中,重命名操作需要:-对源文件所在目录有**写和执行**权限-对目标目录有**写和执行**权限虽然两个容器内的进程都以root身份运行,但实际文件权限可能受以下因素影响:1. **挂载卷的宿主权限**:如果`/var/lib/clickhouse`目录是通过卷挂载的,宿主机的目录权限会映射到容器内。2.**容器内目录的权限设置**:即使以root运行,如果目录权限设置不允许其他用户写入,也可能导致问题。 ###排查步骤####步骤1:检查ClickHouse数据目录权限在ClickHouse容器内执行: ```bashstat/var/lib/clickhouse``` 或者更详细地: ```bashls -ld /var/lib/clickhouse```期望的输出应该是目录的所有者和组都是root(因为容器内root的uid=0),并且有写权限。例如: ```drwxr-xr-x5root root4096 Jun1010:00/var/lib/clickhouse```如果输出中所有者为非root用户(例如UID为101clickhouse用户),那么即使容器内进程以root运行,如果该目录的权限不允许其他用户写入,也会导致问题。 ####步骤2:检查具体表的存储路径权限如果数据目录权限正常,进一步检查具体表的存储路径: ```bashls -ld /var/lib/clickhouse/store/<database>/<table>```####步骤3:验证挂载卷的宿主权限如果`/var/lib/clickhouse`是挂载卷,需要查看宿主机上该目录的权限。使用以下命令查看挂载点映射: ```bashdocker inspectclickhouse-server --format='{{.HostConfig.Binds}}'```然后在宿主机上检查对应目录的权限。例如,如果挂载点是`/host/data:/var/lib/clickhouse`,则在宿主机上执行:```bashls-ld/host/data``` ####步骤4:模拟重命名操作测试在Spark容器内模拟重命名操作,验证权限:```bashdockerexec spark-masterbash -c "touch /var/lib/clickhouse/test.txt &&mv /var/lib/clickhouse/test.txt /var/lib/clickhouse/test2.txt"```如果上述命令出现`Permissiondenied`,则确认是目录权限问题。 ###解决方案####方案1:调整目录权限(推荐)在ClickHouse容器内执行: ```bashchown-R0:0 /var/lib/clickhouse#将所有者改为root(uid=0) chmod -R775 /var/lib/clickhouse#确保目录可写可执行``` ####方案2:确保Spark和ClickHouse使用相同用户如果希望使用非root用户,可以在启动容器时指定相同的用户UID。例如,在启动Spark容器时指定用户UID为101ClickHouse默认用户): ```bashdocker run-u101spark-master``` ####方案3:使用ACL添加额外权限(如果文件系统支持) ```bashsetfacl -R-mu:0:rwx/var/lib/clickhouse#为root用户添加权限```###技术原理-**Linux文件权限**:重命名操作(`rename`系统调用)需要源目录和目标目录的写和执行权限(`W+X`)[^1]。 -**Docker用户映射**:容器内的root用户(uid=0)默认映射到宿主机的当前用户(非特权),因此宿主机文件权限可能限制容器内操作[^2]。 ###相关问题1.如何查看Docker卷在宿主机上的实际路径和权限?2.如何在不重启容器的情况下修改目录权限?3.ClickHouse数据目录的最佳权限设置是什么?4.在Kubernetes中如何配置Pod的安全上下文以避免此类问题?5.如何确认ClickHouse服务进程的用户身份?>附:常用命令速查> ```bash> #检查目录权限> dockerexec clickhouse-serverstat -c "%A %u %g"/var/lib/clickhouse>>#查看挂载卷>docker inspectclickhouse-server --format='{{json.Mounts}}'| jq> ```[^1]:Linux `rename(2)`系统调用手册[^2]:Docker用户命名空间文档</think>### Spark写入ClickHouse权限问题排查分析 #### 1. **核心问题定位** 根据您提供的信息,Spark流式任务在写入ClickHouse时出现`Permission denied`错误,特别是在重命名操作(rename)阶段。关键矛盾点在于: ```mermaid graph LR A[Spark进程] -->|uid=0 root| B[操作ClickHouse目录] B -->|目录权限不匹配| C[Permission denied] ``` 虽然容器内显示root身份(uid=0),但实际权限取决于: 1. 宿主机挂载点的权限设置 2. ClickHouse数据目录的所有权配置 3. ClickHouse服务进程的运行身份 #### 2. **关键验证步骤** 在ClickHouse容器内执行以下命令: ```bash # 检查数据目录权限 docker exec clickhouse-server stat -c "%A %u %g" /var/lib/clickhouse # 检查具体存储路径 docker exec clickhouse-server ls -ld /var/lib/clickhouse/store # 验证ClickHouse进程身份 docker exec clickhouse-server ps -o user,pid,cmd -C clickhouse-server ``` **预期问题场景**: ``` drwxr-x--- 5 101 clickhouse 4096 Jun 10 10:00 /var/lib/clickhouse ``` 这里: - 所有者UID=101clickhouse用户) - 权限`drwxr-x---`表示其他用户无写权限 - Spark的root用户(uid=0)无法修改此目录 #### 3. **权限冲突原理** $$ \text{rename()系统调用权限要求} = W_{\text{src\_dir}} + X_{\text{src\_dir}} + W_{\text{dst\_dir}} + X_{\text{dst\_dir}} $$ 当Spark以root身份尝试重命名文件到ClickHouse目录时: 1. 需要目标目录的写+执行权限 2. 若目录属主是clickhouse用户(UID≠0) 3. 且目录权限未开放给其他用户 4. 则触发Permission denied[^1] #### 4. **解决方案** **方案1:统一用户身份(推荐)** ```bash # 启动Spark容器时指定ClickHouse用户UID docker run -u 101:101 spark-master # 验证身份 docker exec spark-master id # 应输出:uid=101(clickhouse) gid=101(clickhouse) ``` **方案2:调整目录权限** ```bash # 在ClickHouse容器内执行 chown -R 0:0 /var/lib/clickhouse # 变更所有者 find /var/lib/clickhouse -type d -exec chmod 775 {} \; # 添加组写权限 ``` **方案3:配置用户映射** ```dockerfile # ClickHouse Dockerfile添加 RUN usermod -u 0 clickhouse && \ groupmod -g 0 clickhouse ``` #### 5. **验证测试** ```bash # 模拟Spark写入操作 docker exec spark-master bash -c 'touch /var/lib/clickhouse/test.tmp && mv /var/lib/clickhouse/test.tmp /var/lib/clickhouse/test.final' # 检查结果 docker exec clickhouse-server ls -l /var/lib/clickhouse/test.final ``` ### 关键技术原理 1. **Docker用户映射**: ```mermaid graph TB 宿主机用户 -->|映射| 容器内UID 容器内root --> 实际对应宿主机非特权用户 ``` 2. **ClickHouse存储引擎**:使用重命名操作实现原子写入[^2] - 先写临时文件 - 重命名为正式文件 - 需要目标目录写+执行权限 ### 相关问题 1. 如何查看Docker卷在宿主机上的实际权限? 2. 在Kubernetes中如何配置Pod安全上下文避免此类问题? 3. ClickHouse的分布式表写入权限需要额外配置吗? 4. 如何在不改变用户的情况下添加ACL权限? 5. 使用chown会影响ClickHouse性能吗? > 附:权限检查速查命令 > ```bash > # 检查inode权限 > docker exec clickhouse-server stat -c "%A %u %g" /var/lib/clickhouse/store > > # 查看挂载点映射 > docker inspect clickhouse-server --format='{{range .Mounts}}{{.Source}} -> {{.Destination}}: {{.RW}} {{end}}' > ``` [^1]: Linux文件权限模型与rename系统调用 [^2]: ClickHouse原子写入实现原理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值