博客 / 工程

使用 ClickHouse 构建单页应用程序

author avatar
Dale McDiarmid
2024 年 10 月 10 日 - 28 分钟阅读

简介

在构建具有丰富 UI 的实时应用程序时,我们经常需要向数据库发出大量并发请求,以填充页面的各个部分。为了不在 Web 浏览器中暴露数据库凭据,常见的做法是在数据库和客户端之间放置一个 Web 服务器。

对于某些应用程序来说,增加这种复杂性是必要的,但当构建单页应用程序、概念验证和用户需要快速迭代的演示时,这可能就显得过犹不及了。开发人员可以考虑采用“仅客户端”架构,其中浏览器直接查询数据库。在这篇文章中,我们重点介绍采用“仅客户端”架构的关键数据库注意事项。

单页应用程序通过在客户端处理所有渲染和逻辑,在单个网页上动态更新内容,而无需完全页面重新加载,这种应用程序特别适用于这种方法,目的是提供无缝的用户体验。

我们将展示如何使用 ClickHouse 实现这一点,同时确保数据库保持安全且不会被请求淹没。这需要我们利用几个简单但强大的功能,并重用一些配置方案,这些方案可以用于在几分钟内安全地将分析添加到现有应用程序中,只需少量 Javascript 代码即可。

我们对许多公共演示都成功地使用了这种“仅客户端”方法,包括 ClickPyCryptoHouseadsb.exposed,它们也是单页应用程序。但是,我们也意识到一些用户需要额外的安全级别,并希望通过添加 API 层来最大限度地减少攻击面。为此,ClickHouse Cloud 提供了查询端点。

背景

传统上,Web 应用程序遵循传统的客户端-服务器架构。在这种模型中,前端通过 API 与后端服务器通信,服务器处理数据库交互。

server-only.png

这种设置要求开发人员构建和维护复杂的后端基础设施,从而增加了开发过程的复杂性并减慢了迭代周期。这种架构主要流行是由于担心从前端提供直接数据库访问以及将数据库暴露于公共互联网的安全问题,因此需要安全的 API 通信。然而,这种架构具有固有的复杂性,并带来了额外的可扩展性挑战 - 后端服务器需要仔细管理和手动调优才能有效地处理增长。总而言之,这使得 Web 应用程序的开发和维护资源密集且效率较低。

client-only.png

近年来,我们看到越来越多地采用允许从浏览器中的客户端代码直接访问数据库的方式。Firebase 推广了这种构建 Web 应用程序的方法,特别是通过其 Firebase Realtime Database,该数据库引入了基于浏览器的访问概念,其安全规则可以通过令牌(包括匿名身份验证令牌)进行管理。

Firebase 在前端驱动开发社区中的普及确立了这种实践,Supabase 等其他服务也采用并改编了这种实践,用于基于 PostgreSQL 的数据库。这简化了开发,并通过减少对复杂后端基础设施的需求和简化可扩展性(将其委托给数据库)实现了更快的迭代。

然而,这些数据库服务通常针对事务性工作负载进行了优化 - 非常适合处理您的应用程序状态,但不太适合提供基于大型数据集的分析和丰富的可视化效果。幸运的是,只需稍作配置,即可在此架构中部署 ClickHouse。

以下建议适用于希望采用基于 ClickHouse 的仅客户端架构的用户。对于寻求更简单体验的用户,我们推荐 ClickHouse Cloud 中的查询端点,它可以抽象掉大部分复杂性,并允许通过可配置的 REST 端点使用 ClickHouse 快速的分析查询功能。

将 ClickHouse 用于单页应用程序

ClickHouse 具有多个关键功能,使其可以在仅客户端架构中使用

  • HTTP 接口和 REST API - 使从 Javascript 中使用 SQL 查询 ClickHouse 变得非常简单。默认情况下,ClickHouse 监听端口 8123 或 8443(如果使用 SSL),后者在 ClickHouse Cloud 中公开。此接口包括对 HTTP 压缩和会话的支持。
  • 输出格式 - 支持 70 多种输出数据格式,包括 20 种 JSON 子格式,允许使用 Javascript 轻松解析。
  • 查询参数 - 允许查询模板化,并对 SQL 注入保持稳健。
  • 基于角色的访问控制 - 允许管理员限制对特定表和行的访问。
  • 查询复杂性限制 - 限制用户为只读,并限制查询复杂性和可用资源。
  • 配额 - 限制来自任何特定客户端的查询数量,从而防止恶意或恶意客户端淹没数据库。

例如,下面,我们查询我们的 ClickPy 实例,以获取过去 30 天内下载次数最多的 Python 包。请注意使用了 play 用户和 FORMAT JSONEachRow (默认 TabSeparated) 参数,请求将数据作为漂亮打印的 JSON 返回。

echo 'SELECT project, sum(count) as c FROM pypi.pypi_downloads GROUP BY project ORDER BY c DESC LIMIT 3 FORMAT JSONEachRow' | curl -u play: 'https://clickpy-clickhouse.clickhouse.com' --data-binary @-

{"project":"boto3","c":"27234697969"}
{"project":"urllib3","c":"17015345004"}
{"project":"botocore","c":"15812406924"}

虽然这些功能提供了必要的构建块,以确保可以从浏览器查询 ClickHouse,但必须仔细配置和应用它们 - 尤其是在将实例暴露于公共互联网时。

在描述这些最佳实践时,我们只关注读取请求。这些通常是开发面向公众的仅客户端应用程序时唯一合适的请求。我们还假设所有客户端(实际用户)都使用相同的用户名发出查询。但是,这些原则可以轻松扩展到多个 ClickHouse 用户。

仅限 HTTPS

对于面向公众的应用程序,大多数查询使用的用户凭据将在浏览器中可用,并且对检查网络请求的任何开发人员都可见。虽然这些凭据不应被视为敏感信息,但应用程序可能允许用户修改用于请求的用户名和密码。这些凭据应受到保护,因此只能通过安全连接传输。有关如何为开源 ClickHouse 配置 TLS 并公开 HTTPS 接口的信息,请参阅此处

例如,想象一个类似于 ClickHouse play 环境的应用程序,用户可以在创建帐户时修改用户名和密码。该用户可能具有更高的权限或更高的配额。

ClickHouse Cloud 仅在端口 8443 上公开安全的 HTTP 接口。

允许跨域请求

为了确保浏览器可以从托管在不同域的应用程序针对 ClickHouse 运行查询,应在 ClickHouse 中启用跨域请求。在 ClickHouse Cloud 中,默认情况下已启用此功能

curl -X OPTIONS -I https://clickpy-clickhouse.clickhouse.com -H "Origin: localhost:3000"

HTTP/1.1 204 No Content
Date: Fri, 27 Sep 2024 13:30:36 GMT
Connection: Close
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: origin, x-requested-with, x-clickhouse-format, x-clickhouse-user, x-clickhouse-key, Authorization
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Max-Age: 86400

OSS 用户可以通过修改 config.xml 中的相应设置来启用此功能。根据正常最佳实践配置此功能,考虑是否要将访问权限限制为仅限您的应用程序域,还是允许用户在更广泛的上下文中使用您的 ClickHouse 数据。

<!-- It is off by default. Next headers are obligate for CORS.-->
<http_options_response>
   <header>
       <name>Access-Control-Allow-Origin</name>
       <value>*</value>
   </header>
   <header>
       <name>Access-Control-Allow-Headers</name>
       <value>origin, x-requested-with</value>
   </header>
   <header>
       <name>Access-Control-Allow-Methods</name>
       <value>POST, GET, OPTIONS</value>
   </header>
   <header>
       <name>Access-Control-Max-Age</name>
       <value>86400</value>
   </header>
</http_options_response>

通常为 JSON 格式

凭借 Javascript 中的原生支持,JSON 是 Web 开发的首选数据交换格式。ClickHouse 支持 20 多种 JSON 格式,每种格式都有其细微的差别。一般来说,JSON 格式提供了最结构化和完整的响应,其中包含有关列及其类型、数据和查询统计信息的信息。我们可以使用 fetch API 对其进行测试

const credentials = btoa('play:');
const response = await fetch('https://clickpy-clickhouse.clickhouse.com', {
	method: 'POST',
	body: 'SELECT project, sum(count) as c FROM pypi.pypi_downloads GROUP BY project ORDER BY c DESC LIMIT 3 FORMAT JSON',
	headers: {
  	'Authorization': `Basic ${credentials}`,
  	'Content-Type': 'application/x-www-form-urlencoded'
	}
  });

const data = await response.json();
console.log(JSON.stringify(data, null, 2));
{
  "meta": [
    {
      "name": "project",
      "type": "String"
    },
    {
      "name": "c",
      "type": "Int64"
    }
  ],
  "data": [
    {
      "project": "boto3",
      "c": "27234697969"
    },
    {
      "project": "urllib3",
      "c": "17015345004"
    },
    {
      "project": "botocore",
      "c": "15812406924"
    }
  ],
  "rows": 3,
  "rows_before_limit_at_least": 695858,
  "statistics": {
    "elapsed": 0.057031395,
    "rows_read": 1046002,
    "bytes_read": 32165070
  }
}

此格式有多种变体,例如 JSONObjectEachRowJSONColumnsWithMetadata,用户发现这些变体更容易解析以满足其用例。这些格式都返回外部 JSON 对象中的响应,因此需要解析整个有效负载并将其加载到内存中。对于较小的响应,这很少引起关注。对于较大的格式,用户可能希望考虑 EachRow 系列中的格式,该格式更易于使用 Streams API 进行解析,如 此简单示例 中所示。这些格式(例如 JSONEachRowJSONCompactEachRowJSONEachRowWithProgress)并非严格意义上的格式良好的 JSON - 更像是 NDJSON - 可以阅读更多内容。

其他格式是 TSV 和 CSV 的变体,允许轻松下载数据。对于需要大型数据卷的高性能的用户,例如,在 Web Assembly 库(如 Perspective)中渲染,ClickHouse 还支持 Arrow 和 ArrowStream 格式。有关示例,请参阅 此处

查询统计信息、会话和错误处理

ClickHouse HTTP 接口允许将查询统计信息作为响应标头发送,描述查询的进度。这些信息可能难以读取,通常,我们建议使用格式 JSONEachRowWithProgress 来获取有关正在运行的查询进度的统计信息。

用户还可以读取 X-ClickHouse-Summary 标头,其中汇总了读取的行数、字节数和执行时间。

echo 'SELECT project, sum(count) as c FROM pypi.pypi_downloads GROUP BY project ORDER BY c DESC LIMIT 3 FORMAT JSONEachRow' | curl -i -u play: 'https://clickpy-clickhouse.clickhouse.com' --data-binary @-

HTTP/1.1 200 OK
Date: Fri, 27 Sep 2024 15:02:22 GMT
Connection: Keep-Alive
Content-Type: application/x-ndjson; charset=UTF-8
X-ClickHouse-Server-Display-Name: clickhouse-cloud
Transfer-Encoding: chunked
X-ClickHouse-Query-Id: f05b0e25-8b9d-4d28-ad79-fe31e34acfbf
X-ClickHouse-Format: JSONEachRow
X-ClickHouse-Timezone: UTC
Keep-Alive: timeout=10
X-ClickHouse-Summary: {"read_rows":"1046002","read_bytes":"32165070","written_rows":"0","written_bytes":"0","total_rows_to_read":"1046002","result_rows":"0","result_bytes":"0","elapsed_ns":"45896728"}

{"project":"boto3","c":"27234697969"}
{"project":"urllib3","c":"17015345004"}
{"project":"botocore","c":"15812406924"}

请注意,除非请求中包含查询参数 wait_end_of_query=1,否则摘要统计信息可能无法代表整个查询执行过程。如果没有此设置,响应将与在查询完成之前返回的标头值一起流式传输。包含此设置会导致响应仅在查询完成后返回,并带有准确的统计信息。请注意,这会导致响应在服务器端缓冲(可能会消耗大量内存),并将延迟响应的提供,因此不适用于读取大量行的情况。

echo 'SELECT project, sum(count) as c FROM pypi.pypi_downloads GROUP BY project ORDER BY c DESC LIMIT 3 FORMAT JSONEachRow' | curl -i -u play: 'https://clickpy-clickhouse.clickhouse.com' --data-binary @-

HTTP/1.1 200 OK
Date: Fri, 27 Sep 2024 15:02:22 GMT
Connection: Keep-Alive
Content-Type: application/x-ndjson; charset=UTF-8
X-ClickHouse-Server-Display-Name: clickhouse-cloud
Transfer-Encoding: chunked
X-ClickHouse-Query-Id: f05b0e25-8b9d-4d28-ad79-fe31e34acfbf
X-ClickHouse-Format: JSONEachRow
X-ClickHouse-Timezone: UTC
Keep-Alive: timeout=10
X-ClickHouse-Summary: {"read_rows":"1046002","read_bytes":"32165070","written_rows":"0","written_bytes":"0","total_rows_to_read":"1046002","result_rows":"0","result_bytes":"0","elapsed_ns":"45896728"}

{"project":"boto3","c":"27234697969"}
{"project":"urllib3","c":"17015345004"}
{"project":"botocore","c":"15812406924"}

我们还建议用户熟悉错误处理。在收到语法正确的查询时,ClickHouse 将在可能的情况下发送结果(除非 wait_end_of_query=1),并返回响应代码 200。如果在查询执行的后期(可能在数小时后)发生错误,则流可能会终止,有效负载包含错误 - 请考虑 此示例。这留给用户处理。

只能通过 wait_end_of_query=1 部分缓解。因此,如果手动处理响应,我们始终建议处理响应并确保其正确。

只需使用客户端库!

对于除最简单的用例外的任何情况,我们建议用户仅使用官方 Web 客户端。此客户端支持大多数常见请求格式,并确保正确处理会话、压缩、错误和长时间运行查询的策略。流式选择在简单的接口 中公开,该接口已得到优化,并具有提供类型化接口的额外优势。

import { createClient } from '@clickhouse/client-web';

void (async () => {
 const client = createClient( {
     url: 'https://clickpy-clickhouse.clickhouse.com',
     username: 'play'
   }
 );
 const rows = await client.query({
   query:
     'SELECT project, sum(count) as c FROM pypi.pypi_downloads GROUP BY project ORDER BY c DESC LIMIT 3',
   format: 'JSONEachRow',
 });
 const result = await rows.json();
 result.map(row => console.log(row));
 await client.close();
})();

使用查询参数

查询参数允许查询模板化,避免了将 SQL 语句作为字符串进行操作的需要,例如当接口中的过滤器值更改时。用户应始终首选查询参数而不是字符串操作,ClickHouse 确保前者对 SQL 注入攻击具有鲁棒性。Web 客户端提供了利用此功能的简洁接口。

import { createClient } from '@clickhouse/client-web';

void (async () => {
 const client = createClient({
   url: 'https://clickpy-clickhouse.clickhouse.com',
   username: 'play',
 });
 const rows = await client.query({
   query:
     'SELECT sum(count) as c FROM pypi.pypi_downloads WHERE project = {project:String}',
   format: 'JSONEachRow',
   query_params: {
     project: 'clickhouse-connect',
   }
 });
 const result = await rows.json();
 result.map(row => console.log(row));
 await client.close();
})();

最小权限原则

在授予用户访问表的权限时,始终采用最小权限原则 - ClickHouse 遵循此原则,新创建的用户没有任何权限(除非分配了默认角色)。

提示:如果创建将用于仅客户端请求的用户,则通常创建密码没有任何意义,因为凭据对应用程序用户可见。要创建没有密码的用户

-- oss
CREATE USER play IDENTIFIED WITH no_password;
-- clickhouse cloud
CREATE USER play IDENTIFIED WITH double_sha1_hash BY 'BE1BDEC0AA74B4DCB079943E70528096CCA985F8';

通常,我们建议创建一个角色,以防需要更多用户(目前我们假设所有客户端都使用相同的用户),并授予其以下权限。

最初,确定 Web 用户应能够访问的表和列 - 相应地授予 SELECT 权限。例如,以下内容允许任何具有 play_role 的用户读取 pypi 数据库中的所有表(和列)。具有此角色的用户被限制为读取 github 数据库中 events 表的 event_typeactor_login 列(必须显式完成,即不允许使用 * )。

请注意,我们还将角色授予了我们之前的 play 用户。

-- create the role
CREATE ROLE play_role;
-- limit read on the columns event_type, actor_login for the github.events table
GRANT SELECT(event_type, actor_login ) ON github.events TO play_role;
-- allow select on all tables in the pypi database
GRANT SELECT ON pypi.* TO play_role;
-- grant the role to the user
GRANT play_role TO play;

还可以应用基于行的策略,允许用户仅查看给定表的特定行。有关更多详细信息,请参阅此处

只读和常用限制

以上操作创建了一个具有 SELECT 权限的受限用户。我们可以通过创建设置配置文件并将此配置文件应用于角色来应用更多约束。

此配置文件应进一步将用户限制为只读操作,为用户的角色设置 read_only=1。这会将用户限制为只读查询,同时防止修改会话上下文或更改设置的查询。虽然这给用户施加了只读限制,但它不会限制他们执行复杂查询的能力。为此,我们可以向配置文件添加查询复杂性限制

这些限制允许对执行的各个方面进行精细控制。此处的设置数量非常多,但至少,我们建议以下设置

例如,考虑 play.clickhouse.com 中用于 play 用户的以下设置配置文件,其中包含合理的默认值。

CREATE SETTINGS PROFILE `play` SETTINGS readonly = 1, max_execution_time = 60, max_rows_to_read = 10000000000, max_result_rows = 1000, max_bytes_to_read = 1000000000000, max_result_bytes = 10000000, max_network_bandwidth = 25000000, max_memory_usage = 20000000000, max_bytes_before_external_group_by = 10000000000, enable_http_compression = true
--assign settings profile to the role
ALTER USER play SETTINGS PROFILE play_role

重要的是,所有这些设置都是按查询进行的。我们需要配额(见下文)来限制查询数量。

默认情况下,如果查询超出这些限制,则会引发错误。在应用程序具有一组受限查询的情况下,这种行为是可以接受的,其主要目的是阻止滥用用户。但是,在某些情况下,您可能希望允许用户在达到限制时接收部分响应,例如,读取的行数。例如,在 CryptoHouse 中就是这种情况,用户可以运行任意选择查询,我们会返回当前结果。

为了实现此目的,请将配置文件中受限设置的溢出模式设置为 break。例如,设置 result_overflow_mode = 'break' 将在达到 max_result_rows 设置的限制时中断执行。

请注意,返回的行数将大于 max_result_rows,并且将是 max_block_sizemax_threads 的倍数,因为执行是在块级别中断的。

通常,我们发现设置 read_overflow_mode=break 也很有用,如果读取了过多的行并返回了当前结果,则会导致执行中断。用户可以通过检查摘要统计信息中的 read_rows 与已知限制来检测此中断(并显示警告)。

如果需要,公开设置

配置为只读的用户(通过 readonly=1)无法在查询时更改设置。虽然这通常是可取的,但在某些用例中,这可能是有意义的。例如,max_execution_time 需要由 Grafana 为只读用户修改。此外,您可能希望用户能够根据预期的结果集大小选择性地使用响应压缩(这需要 enable_http_compression=1 )。

我们建议应用设置约束,而不是使用允许用户更改所有设置的 readonly=2。这些约束允许用户在指定的约束范围内更改指定的设置。

这些约束允许通过 minmax 为数值设置指定可接受的范围。接受值枚举的设置可以通过 changeable_in_readonly 约束定义为可更改的。即使 readonly 设置为 1,这些约束也允许在定义的最小/最大范围内调整指定的设置。否则,当 readonly=1 时,设置无法更改。

仅当在 clickhouse.xml 配置中启用 settings_constraints_replace_previous 时,changeable_in_readonly 才可用。

<access_control_improvements>
 <settings_constraints_replace_previous>true</settings_constraints_replace_previous>
</access_control_improvements>

例如,考虑以下配置文件

CREATE SETTINGS PROFILE `play` SETTINGS readonly = 1,  max_execution_time = 2, enable_http_compression = false

这可以防止具有此设置配置文件的用户使用 HTTP 压缩,并将执行时间限制为 2 秒。因此,以下查询都将失败

echo "SELECT sleep(3)" | curl -s -u play: 'https://clickpy-clickhouse.clickhouse.com
' --data-binary @-
Code: 159. DB::Exception: Timeout exceeded: elapsed 2.002346261 seconds, maximum: 2. (TIMEOUT_EXCEEDED) (version 24.6.1.4501 (official build))

echo "SELECT sleep(1) SETTINGS enable_http_compression=1" | curl -u play: 'https://clickpy-clickhouse.clickhouse.com?compress=true' --data-binary @- -H 'Accept-Encoding: gzip' --output -
Code: 164. DB::Exception: Cannot modify 'enable_http_compression' setting in readonly mode. (READONLY) (version 24.6.1.4501 (official build))

我们可以修改我们的设置配置文件,以允许使用约束修改这些设置。

ALTER SETTINGS PROFILE `play` SETTINGS readonly = 1, max_execution_time = 10 CHANGEABLE_IN_READONLY min=1 max=60, enable_http_compression = false CHANGEABLE_IN_READONLY

现在可以将之前的查询配置为执行

echo "SELECT sleep(3) SETTINGS max_execution_time=4" | curl -s -u play_v2: 'https://k5u1q15mc4.us-central1.gcp.clickhouse.cloud' --data-binary @-
0

echo "SELECT sleep(1) SETTINGS enable_http_compression=1" | curl -u play_v2: 'https://k5u1q15mc4.us-central1.gcp.clickhouse.cloud?compress=true' --data-binary @- -H 'Accept-Encoding: gzip' --output -
}''ߨ')CN''
')'2'

应用配额

我们之前的所有设置都仅限制单个查询,并且不适用于整体查询吞吐量的任何限制。具有限制单个查询的限制的恶意用户仍然可以运行数千个并发查询,从而消耗所有可用资源。

为了限制特定用户名的查询数量,我们可以创建一个配额并将其应用于分配的角色。配额限制每个单位时间的查询数量。重要的是,许多实际用户或客户端都使用相同的用户名进行查询。我们希望我们的配额适用于每个客户端,而不仅仅是用户名 - 否则单个恶意客户端可能会耗尽所有人的限制!

为此,我们将配额键控为 IP 地址。这会导致限制按 IP 跟踪。例如,我们在下面创建一个配额 play,允许每分钟最多 2 个查询,并将其分配给 play_role。

CREATE QUOTA play KEYED BY ip_address FOR INTERVAL 1 minute MAX queries = 2 TO play_role

在没有 play 用户的情况下进行查询,我们可以轻松超出这些配额限制

echo "SELECT sum(number) FROM numbers(1000)" | curl -u play_v2: 'https://k5u1q15mc4.us-central1.gcp.clickhouse.cloud' --data-binary @-
Code: 201. DB::Exception: Quota for user `play_v2` for 60s has been exceeded: queries = 2/1. Interval will end at 2024-09-27 16:52:00. Name of quota template: `play_v2`. (QUOTA_EXCEEDED) (version 24.6.1.4501 (official build))

在 ClickHouse Cloud 中,配额是按副本计算的。设置值时请务必考虑这一点。

不同角色的多个用户

在开发功能时,通常希望为应用程序的不同功能部分设置不同的限制。例如,用于执行查询的用户名可能会限制为每小时可以运行的查询数量。相反,您可能有一个组件需要定期更新以显示统计信息。此数据可以从物化视图中获取,并且需要每秒更新一次 - 比标准用户快得多。

clickpy-users.png

为了实现此目的,我们通常建议根据需要创建具有不同角色和配额的不同用户。CryptoHouse 中就是这种情况,用于运行查询的用户 crypto 限制为每小时 120 个查询。相反,用于获取查询执行进度的用户使用 monitor 用户,该用户具有更高的配额限制,但相反,该用户被限制为特定表。

使用物化视图

我们建议开发人员确保针对物化视图执行频繁执行的查询。 从最简单的形式来看,ClickHouse 中的物化视图只是一个在向表插入数据时触发的查询。 关键在于物化视图本身不存储任何数据。 它们只是对插入的行执行查询,并将结果发送到另一个“目标表”进行存储。

重要的是,运行的查询可以将行聚合为更小的结果集,从而使查询在目标表上运行得更快。 这种方法有效地将工作从查询时间转移到插入时间,并避免查询达到复杂性限制,同时最大限度地减少运行查询所需的资源,并确保 UI 尽可能地响应迅速。

我们在我们流行的 ClickPy 演示中采用了这项技术,该演示允许用户对 Python 包执行分析。 示例视图和有关实现的详细信息可以在此处找到。

利用压缩

ClickHouse 的 HTTP 端点支持请求和响应压缩。 对于上面描述的基于文本的格式,我们建议使用 HTTP 响应压缩。 由于读取请求通常很小,因此用户通常只需要在开发仅客户端应用程序时才需要响应压缩。 我们建议仅在流式传输大型响应且网络成为瓶颈时才启用压缩。 压缩可能会减慢响应时间,因为服务器会产生 CPU 开销。 与往常一样,进行测试和衡量。

如果为用户设置了 enable_http_compression=1(或在请求中设置),则应在标头 Accept-Encoding: compression_method 中指定所需的压缩方法,并提供多个支持的选项

const client = createClient( {
    url: 'https://clickpy-clickhouse.clickhouse.com',
    username: 'play',
     compression: {
       response: true
   }
  }
);

如果需要压缩,我们建议使用 ClickHouse JS Web 客户端,它支持 使用 gzip 的响应压缩,如上所示,并自动设置所需的标头和设置。

预定义的 HTTP 接口

开源用户可以使用预定义的 HTTP 接口从客户端抽象出 SQL。 此功能允许 ClickHouse 公开一个端点,参数将提供给该端点。 这些参数反过来被注入到预定义的 SQL 查询中,响应返回给用户。 对于简单的业务应用程序,这可以简化客户端代码,客户端代码只需与有限的 REST API 通信。 上述相同原则可以应用于调用用户,以强制执行访问限制和配额。

ClickHouse Cloud 的查询端点

预定义的 HTTP 接口具有其局限性,尤其是实施更改或添加端点需要修改 clickhouse.xml 配置。

一些用户也可能不愿意公开他们的 ClickHouse HTTP 接口。 查询端点通过限制可以执行的查询来帮助解决这些问题,从而减少攻击面。

ClickHouse Cloud 在 查询端点 中公开了一个类似但更高级的功能,它使用基于令牌的身份验证机制。 除了模板化查询外,用户还可以将角色分配给端点(可以通过这些角色应用设置约束和配额),并为每个端点配置 COR。 这些端点可以随时从 ClickHouse Cloud 的统一控制台轻松添加或修改。

API 端点不仅仅简化了接口,它们还增加了关注点分离。 除了使更新应用程序查询更简单(无需修改或重新部署代码)之外,这还允许团队轻松公开分析,而无需编写 SQL 或直接与不同团队拥有的 ClickHouse 数据库交互。 我们上面概述的所有最佳实践都抽象在这些端点之后,开发人员只需负责与简单的 REST API 端点交互。

有关此示例,我们推荐这篇博客

结论

对于小型应用程序、演示和单页应用程序,仅客户端架构可能是理想的选择,它可以简化开发并实现更快的迭代,同时将扩展问题卸载到数据库。 然而,这确实要求目标数据库支持 HTTP 接口和必要的功能,以使数据库可以安全地暴露给客户端。 虽然 Firebase 和 Supabase 已经普及了这种用于 OLTP 工作负载的架构,但 ClickHouse 具有必要的功能,可以将其用于实时分析。 我们为配置 ClickHouse 以用于此类架构提供了全面的建议。 对于寻求更简单体验的用户,ClickHouse 中的查询端点抽象了大部分复杂性,并允许通过简单的 REST 端点使用 ClickHouse 快速的分析查询功能。

立即开始使用 ClickHouse Cloud 并获得 300 美元的信用额度。 在 30 天试用期结束时,继续使用按需付费计划,或联系我们以了解有关我们基于数量的折扣的更多信息。 访问我们的定价页面了解详情。

分享这篇文章

订阅我们的新闻通讯

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image
©2025ClickHouse, Inc. 总部位于加利福尼亚州湾区和荷兰阿姆斯特丹。