摘要:Materialize 是一个兼容 PostgreSQL 的流式数据库,它基于微软研究院的Timely和Differential Dataflow框架构建,能够维护严格可串行化的、增量更新的 SQL 物化视图。该公司由 Frank McSherry(底层数据流
Materialize 是一个兼容 PostgreSQL 的流式数据库,它基于微软研究院的 Timely 和 Differential Dataflow 框架构建,能够维护严格可串行化的、增量更新的 SQL 物化视图。该公司由 Frank McSherry(底层数据流技术的创造者)、Arjun Narayan和 Nikhil Benesch 于 2019 年创立,迄今已在三轮融资中从 Kleiner Perkins、Lightspeed Venture Partners 和 Redpoint Ventures 募集超过 1 亿美元。
在数据库领域中,Materialize 的定位十分独特:它比 Flink 等原始流处理器高一个抽象层次,提供了数据库语义和流式性能;但又不同于传统数据仓库的每次查询时重新计算结果,而是采用增量计算。
Materialize 的理论基础源自微软研究院学术研究。Frank McSherry 在那里共同发表了具有里程碑意义的论文 《Naiad: A Timely Dataflow System》,该论文在 2013 年 SOSP 大会上获得最佳论文奖。McSherry 开发了 timely Dataflow—— 一个可扩展的、有状态的、支持循环的数据流引擎,以及 differential Dataflow—— 一种通过将集合表示为差分流(streams of differences)而不是完整状态来实现增量计算的方法。
前 Cockroach Labs 工程师 Arjun Narayan 和 Nikhil Benesch 于 2017 年左右与 McSherry 共同创立了 Materialize 公司。该公司于 2020 年 2 月正式发布了 v0.1 beta 版本。
初始产品以单一 Rust 二进制文件 的形式推出,支持 Kafka 流式数据源、使用 Debezium CDC 格式的文件源,以及兼容 PostgreSQL 的 SQL。该版本展示了核心创新:随着源数据的变化,物化视图会增量更新,并支持包括连接、子查询、聚合在内的复杂操作,同时在视图之间共享索引以优化内存使用。创始愿景是消除现有流处理系统在正确性、性能与互操作性之间的妥协,通过标准 SQL 提供实时分析,而不是要求开发者具备专业的流处理技能。
到 2021 年,Materialize 的发展加速,完成 C 轮融资 后,总融资额超过 1 亿美元。同年 9 月,公司推出了托管的 Materialize Cloud(数据库即服务),员工数量在 B 轮融资时的 20 人扩张到 6 个月后的 40 人。早期客户包括金融服务公司 Kepler Cheuvreux、物联网公司 Density,以及电商平台 Drizly(后被 Uber 收购)。团队在纽约市开设了总部,选址在前 Slack 办公室,可容纳 85 人,展现了超越小型初创公司的雄心。
2022 年 10 月发布的 v0.27 版本标志着架构上的根本转变 —— 向云原生系统演进。这个版本与之前的版本并不兼容,实现了计算与存储分离,并引入可持久、可扩展的存储层。
到 2023 年,Materialize 的战略定位从“流式数据仓库”演变为“操作型数据仓库”,并伴随 CEO 的更替。原董事会成员、曾在 Cockroach Labs 负责产品七年的 Nate Stewart 接任 CEO,而联合创始人 Arjun Narayan 转任工程副总裁。新的定位强调实时操作型工作负载:
使用 CQRS 模式 进行查询分流;实现操作型数据存储模式的集成中枢;为小型数据团队提供实时分析;构建事件驱动应用与实时仪表盘。Timely Dataflow 是 Microsoft Research 开发的分布式数据流计算框架,解决了流式计算中的循环依赖和状态管理问题。它主要解决的问题是:
1. 支持循环数据流 (Cyclic Dataflow)
传统流处理系统(如早期的 MapReduce、Spark Streaming)只支持 DAG(有向无环图),无法处理迭代算法。Timely Dataflow 允许数据流中存在循环,使得以下场景成为可能:
机器学习的迭代算法(梯度下降、PageRank)图算法(最短路径、连通分量)递归查询(SQL 中的递归 CTE)2. 精确的进度追踪 (Progress Tracking)
通过引入逻辑时间戳机制:
每条数据都带有时间戳 (epoch, iteration)
系统能准确知道某个时间点的计算是否完成解决了"何时可以安全输出结果"的问题3. 低延迟 + 高吞吐
协作式调度:多个算子共享线程,避免线程切换开销批处理优化:自动将小批量消息合并处理零拷贝:通过 Rust 的所有权系统实现高效内存管理use timely::dataflow::operators::{ToStream, Join, Inspect};fn main {timely::execute_from_args(std::env::args, |worker| {worker.dataflow::(|scope| {// 用户数据流: (user_id, name)let users = vec![(1, "Alice"),(2, "Bob"),(3, "Charlie"),].to_stream(scope);// 订单数据流: (user_id, order_id)let orders = vec![(1, 101),(2, 102),(1, 103),].to_stream(scope);// Join 操作users.join(&orders).inspect(|(user_id, (name, order_id))| {println!("{} (ID:{}) 的订单: {}", name, user_id, order_id);});});}).unwrap;}Differential Dataflow 是 Frank McSherry 在 Timely Dataflow 之上构建的增量计算框架,解决了"如何高效维护计算结果"的问题。
Differential Dataflow的核心思想:用"增量"代替"全量"
传统数据库每次查询都重新计算:
SELECT COUNT(*) FROM orders WHERE status = 'pending' -- 数据变化后,需要重新扫描整张表Differential Dataflow 的方式:
初始状态: 1000 个 pending 订单 → 结果: 1000新增 5 个订单 (+5),完成 3 个订单 (-3)→ 只计算变化量: 1000 + 5 - 3 = 1002 ✓所有数据表示为 (data, time, diff) 三元组
data: 数据本身(如某个订单)time: 逻辑时间戳diff: 变化量(+1 表示插入,-1 表示删除)避免重复计算场景:实时统计每个城市的订单数 传统方式: 每次新订单到达,重新 GROUP BY 整张表 Differential: 只更新受影响的城市计数 (+1) 性能提升 = 数据总量 / 变化量 如果 1 亿订单中只有 100 个变化 → 提速 100 万倍高效处理复杂 Join-- 三表 Join:用户 × 订单 × 商品SELECT u.name, o.id, p.title FROM users u JOIN orders o ON u.id = o.user_idJOIN products p ON o.product_id = p.id
传统方式:新增一个订单,重新执行整个 Join
Differential 方式:
只对新订单 (+1) 执行 Join查询已索引的 users 和 products只产生受影响的新结果行这就是 Materialize 中的 Delta Join 技术。
3.自动维护物化视图
CREATE MATERIALIZED VIEW daily_revenue ASSELECT DATE(created_at), SUM(amount) FROM orders GROUP BY 1;传统数据库: 手动 REFRESH,扫描全表Differential: 自动增量更新- 新订单 (+$100) → 只更新对应日期的 SUM- 取消订单 (-$100) → 同样只更新一个数值Timely Dataflow 提供基础设施:
时间戳机制(让 Differential 知道"何时"的变化)分布式协调(跨机器同步状态)循环支持(处理递归查询)Differential Dataflow 提供计算语义:
增量算子(join_incremental, group_by_incremental)Collection 抽象(把数据集合看作 diff 流)Arrangement(索引结构,类似数据库的 B-tree)这里是一个增量Join的例子
use timely::dataflow::operators::Inspect;use differential_dataflow::input::Input;use differential_dataflow::operators::Join;fn main {timely::execute_from_args(std::env::args, |worker| {worker.dataflow::(|scope| {let (mut users_input, users) = scope.new_collection;let (mut orders_input, orders) = scope.new_collection;users.join(&orders).inspect(|(user_id, (name, order_id))| {println!("时间 {:?}: {} (ID:{}) 的订单: {}", user_id, name, user_id, order_id);});// === 时间 0: 初始数据 ===users_input.insert((1, "Alice"));users_input.insert((2, "Bob"));orders_input.insert((1, 101));// 推进时间,触发计算users_input.advance_to(1);orders_input.advance_to(1);users_input.flush;orders_input.flush;println!("\n--- 新增订单 ---\n");// === 时间 1: 新增订单 ===orders_input.insert((1, 102)); // Alice 新订单orders_input.insert((2, 201)); // Bob 新订单users_input.advance_to(2);orders_input.advance_to(2);users_input.flush;orders_input.flush;println!("\n--- 新增用户 ---\n");// === 时间 2: 新增用户 ===users_input.insert((3, "Charlie"));orders_input.insert((3, 301)); // Charlie 的订单users_input.advance_to(3);orders_input.advance_to(3);users_input.flush;orders_input.flush;println!("\n--- 删除订单 ---\n");// === 时间 3: 删除订单 ===orders_input.remove((1, 101)); // diff = -1users_input.advance_to(4);orders_input.advance_to(4);users_input.flush;orders_input.flush;});}).unwrap;}Materialize 的架构通过一个三层计算栈实现了增量视图维护:
Timely Dataflow 提供分布式执行运行时;Differential Dataflow 增加了一组为增量计算优化的操作符;Materialize 本身 负责 PostgreSQL 协议终止、目录管理和 SQL编译。Arrangement:流式索引
Arrangement 抽象 类似于数据库索引,是关键的性能支撑。
它是 按键索引的、时间感知的集合表示,存储为 key → [(time, diff)] 的映射;使用类似 LSM-Tree 的结构:近期更新存小批量,旧数据批量合并,支持 O(log n) 的键查找;查询共享索引:若多个视图需要相同索引,Materialize 只构建一次。系统优化演进:单条记录开销从早期的 96 字节 降低至 0-16 字节,得益于小值内联、u32 替代 u64、列式分块存储,以及利用 Linux swap 代替自研分页。
存储层:Persist
存储层Persist是一个持久化的 pTVC 存储,基于 S3 兼容对象存储,并通过分布式事务数据库进行元数据协调。
每个 Persist 分片存储一个 pTVC,采用键值结构、通用时间戳和差分类型,写入为不可变批次;读/写前沿 跟踪可写时间与可压缩时间;写路径:批次写入 S3 → 原子注册元数据 → 推进写前沿;读路径:获取目标时间范围的批次元数据 → 拉取 S3 批次 → 合并重构集合状态。这种存储与计算的分离,支持独立扩展与复杂复制策略。
Materialize原生支持CDC,而不需要依赖类似Debezium这样的外部CDC工具,这是它的一个亮点
PostgreSQL CDC:利用逻辑复制协议,两类 Timely 操作符:snapshot:在指定 LSN 上一致性快照;replication:流式处理 WAL 变更并保持事务边界。PostgreSQL 的 LSN 持久映射到 Materialize 时间戳,确保事务从不部分可见。MySQL CDC:利用 BInlog 与 GTID,每个 source_id 在时间戳中拥有独立坐标。MySQL binlog 提供完整行镜像,避免像 Debezium+Kafka 那样需维护完整 KV 存储。
计算-存储分离的集群架构
environmentd 进程:控制平面,负责 PostgreSQL 协议终止、SQL 解析/规划、目录管理、时间戳协调;clusterd 进程:数据平面,用户可控制的集群,运行存储任务(source/sink)或计算任务(视图/索引/查询的数据流)。集群之间无直接网络通信,所有数据传输都经 Persist 与 S3。
总结一下Materialize 的技术优势主要集中在增量视图上,这是其核心差异化能力:能够在任意插入、更新和删除的情况下自动维护物化视图,而无需从头重新计算。这样,即使是复杂查询,也能在 亚秒级到毫秒级延迟 下完成,而不必担心缓存失效问题。
在 SQL 支持方面,Materialize 提供完整的 ANSI SQL-92 标准与 PostgreSQL 方言,支持复杂的多路连接,自动子查询去相关化,以及外连接、窗口函数、聚合、递归 SQL 和 JSON。一致性方面,Materialize 默认提供 严格可串行化(最高隔离级别),确保流式结果在特定时间点始终反映正确状态,没有“最终一致性”的妥协,虚拟时间机制保证状态转变的一致性。
PostgreSQL 兼容性 使其可直接接入现有生态,支持 psql、现有驱动和 BI 工具(如 Metabase、dbt),对 SQL 用户而言几乎没有学习成本。其多源集成能力包括直接从 PostgreSQL/MySQL CDC、Kafka 及兼容系统,以及 SaaS 应用的 Webhook 进行数据摄入,支持 跨数据库连接且具备强一致性。架构上采用计算与存储分离:
水平可扩展;云原生 S3 存储;多活复制保障高可用;集群实现工作负载隔离。运维优势包括:
简化复杂的流处理基础设施(无需 Flink/Spark/ksqlDB 的复杂性);成本效益:官方声称对复杂业务逻辑可比 Aurora 只读副本便宜 20 倍,且吞吐量提升 100 倍,延迟降低 1000 倍;纯 SQL 方法,无需自定义代码;同时支持查询拉取和基于 SUBSCRIBE 的推送模式。但是据我了解,Materialize也存在一些技术和功能的限制
内存与资源消耗:高基数(High Cardinality)字段可能导致内存飙升,复杂查询易于资源膨胀(如 GitHub #521 报告:内存一夜增长到 180GB)。大规模连接消耗严重,尽管 v0.27 引入持久化存储替代早期的纯内存架构。查询优化不足:部分查询存在次优计划,大连接维持较大内存占用,用户对优化的控制有限;某些模式下物化开销较高。窗口函数不足:缺乏 Flink 的原生 tumble/hop 窗口,只能使用 mz_logical_timestamp,不够直观,需要学习 Materialize 特有模式。PostgreSQL 覆盖不完整:虽然已实现大部分功能,但仍不及 Snowflake 或 BigQuery 等成熟系统,部分函数缺失。压缩与延迟问题:数据压缩会导致处理延迟,链式物化视图延迟累积,下游查询必须等待上游一致性。临时查询不够优化:该系统主要面向长期维护的物化视图,单次临时查询仍更适合传统处理器。“先定义查询”的模型:与传统数据库(读时计算)相反,需要提前定义查询为视图,不太适合临时分析探索(Ad Hoc)。成熟度不足:社区“不如 Snowflake 或 Postgres 成熟”,社区规模也小于 Flink/Spark,生产可用性存疑。锁定风险:需要“all-in” 投入,作为独立数据库存在,无法完全替代主数据仓库,只能作为额外层。扩缩容复杂:集群大小需要试错(起步 400cc,根据指标调整),扩容缩容会导致停机;资源利用模式需深入理解;数据流延迟问题排查复杂。源配置要求高:Postgres CDC 需开启 REPLICA IDENTITY FULL,增加源库内存压力;不能直接查询其他 Kafka 集群;不同源的内存需求差异大。这里我觉个例子说明一下“先定义查询”的模型:
-- Materialize 需要先创建物化视图-- ❌ 错误:直接查询会很慢SELECT user_id, COUNT(*) FROM orders GROUP BY user_id;-- 问题:每次都要扫描全表,没有增量维护-- ✓ 正确:必须先创建物化视图CREATE MATERIALIZED VIEW user_order_count ASSELECT user_id, COUNT(*) as order_countFROM ordersGROUP BY user_id;-- 然后才能快速查询SELECT * FROM user_order_count WHERE user_id = 123;-- ✓ 毫秒级返回,因为视图一直在维护问题:
-- 数据分析师的一天-- 早上 9:00SELECT city, COUNT(*) FROM orders GROUP BY city;-- 等等,查询很慢... 需要创建视图CREATE MATERIALIZED VIEW orders_by_city ASSELECT city, COUNT(*) FROM orders GROUP BY city;-- 等待视图构建...(可能几分钟)-- 早上 10:00-- 老板:改一下,按省份统计SELECT province, COUNT(*) FROM orders GROUP BY province;-- 又慢了... 又要创建新视图CREATE MATERIALIZED VIEW orders_by_province ASSELECT province, COUNT(*) FROM orders GROUP BY province;-- 又要等待...-- 早上 11:00-- 老板:再加个时间维度SELECT DATE(created_at), province, COUNT(*) FROM orders GROUP BY 1, 2;-- 还是要创建视图...-- 问题:每次探索都要等,不能快速迭代也就是说,增量计算快的前提是我把需要用到的历史数据已经算好了。如果用户对于计算只需要一次结果,这个相比批处理的计算并没有优势。但是用户如果对每个新的事件,或者对于每个新的时间段(1秒,30秒或者5分钟),计算新的结果,那么增量计算可以带来很大的优势。
Materialize是继ksqlDB之后,又一个以数据库形态存在的流处理工具。构建在其学术论文研究的增量计算之上,让人眼前一亮。但是这款产品仍然有许多需要改进的地方。让我们拭目以待,关注它的发展。
来源:闻数起舞