工具实战指南)
Quickwit Metastore gRPC 流量录制与回放Replay工具实战指南【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit导读Replay 是 Quickwit 仓库中quickwit-metastore-utils工具包提供的一个小型实用程序用于按顺序、以最快速度回放一批发往 Quickwit metastore 的 gRPC 调用。它的典型应用场景是先由配套的 recording proxy 把真实业务流量录制为 NDJSON 请求日志再用 Replay 在干净环境或目标版本上重放从而完成 metastore 接口的压力测试、性能基准测量、回归验证与故障复现。读完本文你将掌握 Replay 与 Proxy 两个二进制工具的完整用法、录制文件的数据格式、前后端地址配置、PostgreSQL 环境搭建以及两次重放之间如何正确清理元数据状态。Replay 的说明文档位于 quickwit-metastore-utils/src/bin/README.md配套的可执行源码为 replay.rs 与 proxy.rs。Replay 是什么一个面向 metastore 的 gRPC 请求回放器metastore 是 Quickwit 的元数据服务负责维护索引元数据、split 列表、source 配置、删除任务delete task等核心状态。Replay 的职责很纯粹逐行读取录制好的请求日志文件把每一条记录反序列化为一个 gRPC 请求然后通过MetastoreServiceClient顺序发送给正在运行的 metastore 服务并尽可能快地执行。从 replay.rs 的实现可以看到其回放主循环非常简单直接while let Some(line) lines.next_line().await? { println!(line {i} {line}); let grpc_call: GrpcCall serde_json::from_str(line)?; replay_grpc_request(mut client, grpc_call.grpc_request).await?; i 1; }也就是说Replay 对请求之间不做任何调度、节流或并发控制一条请求处理完成后立即发起下一条这正是“as fast as possible”尽可能快语义的体现适合用来考察 metastore 在连续高压请求下的表现。回放支持的 gRPC 方法全集replay_grpc_request函数通过match把录制文件中的GrpcRequest枚举逐一映射到具体的 gRPC 方法调用覆盖了 metastore 服务的全部核心接口见 replay.rs录制文件中的枚举变体实际调用的 gRPC 方法业务含义CreateIndexRequestcreate_index创建索引IndexMetadataRequestindex_metadata读取单个索引元数据ListIndexesMetadataRequestlist_indexes_metadata列出索引元数据DeleteIndexRequestdelete_index删除索引ListSplitsRequestlist_splits列出 splitStageSplitsRequeststage_splits暂存stage一批 splitPublishSplitsRequestpublish_splits发布 splitMarkSplitsForDeletionRequestmark_splits_for_deletion标记 split 待删除DeleteSplitsRequestdelete_splits删除 splitAddSourceRequestadd_source添加数据源ToggleSourceRequesttoggle_source启用/停用数据源DeleteSourceRequestdelete_source删除数据源LastDeleteOpstampRequestlast_delete_opstamp查询最新删除 opstampResetSourceCheckpointRequestreset_source_checkpoint重置数据源 checkpointDeleteQuerycreate_delete_task创建删除任务UpdateSplitsDeleteOpstampRequestupdate_splits_delete_opstamp更新 split 的删除 opstampListDeleteTasksRequestlist_delete_tasks列出删除任务ListStaleSplitsRequestlist_stale_splits列出过期 split由此可见Replay 覆盖的是索引、split 生命周期、source 与删除语义这四大类 metastore 操作几乎等同于一次完整的索引建删、数据摄取发布与删除清理的元数据旅程。从录制到回放Proxy 与 Replay 的分工Replay 本身只负责“读文件、发请求”它需要的 NDJSON 请求日志从哪里来答案是仓库中配套的另一个二进制Proxy录制代理见 proxy.rs。Proxy 实现了一个实现了MetastoreServicetrait 的转发代理它监听一个本地端口把收到的每个 metastore gRPC 请求先序列化记录到文件再原样转发给后端的真实 metastore 服务。每个被代理的方法实现都遵循同样的三步模式以create_index为例async fn create_index(self, request: RequestCreateIndexRequest) - ... { let mut lock self.inner.lock().await; lock.record(request.get_ref().clone()).await.unwrap(); // 1. 记录请求 let resp lock.client.create_index(request).await?; // 2. 转发给真实 metastore Ok(resp) }这样Proxy 就像一台“行车记录仪”在不干扰线上调用语义的前提下把真实工作负载完整留档Replay 随后可以在任意环境上原样重放。录制文件的数据格式Proxy 写入、Replay 读取的日志文件是NDJSONNewline Delimited JSON每一行对应一次 gRPC 调用。核心数据结构定义在 lib.rs 中#[derive(Serialize, Deserialize)] pub struct GrpcCall { pub ts: u64, // 距代理启动的毫秒时间戳 pub grpc_request: GrpcRequest, }其中ts是调用发生的相对时间毫秒grpc_request是带type标签的请求枚举。GrpcRequest枚举由 grpc_request.rs 中的宏批量生成带#[serde(tagtype)]属性因此文件中的每一行形如{ts:12,grpc_request:{type:CreateIndexRequest,index_id:my-index,index_config:...,index_uri:s3://...}}Proxy 记录时写入的正是这种格式见 proxy.rs构造GrpcCall { ts, grpc_request }后serde_json::to_vec序列化并追加换行符。两个二进制的参数一览二进制参数默认值说明replay--file./replay-data/requests-partition-wikitenant.ndjson录制文件路径NDJSONreplay--forward-tohttp://127.0.0.1:7281目标 metastore 的 gRPC 地址proxy--listen-to位置参数127.0.0.1:7291代理监听地址proxy--forward-tohttp://127.0.0.1:7281后端 metastore gRPC 地址proxy--file./replay.ndjson录制输出文件说明仓库内已附带了默认录制数据./replay-data/requests-partition-wikitenant.ndjson的引用见 replay.rs其中第一条请求即CreateIndexRequest。你也可以自行用 Proxy 录制符合自己场景的请求日志。完整运行步骤从零搭建到回放前提启动 Quickwit metastore 服务Replay 假定一个 Quickwit metastore 服务正在localhost:7280REST 端口上运行其 gRPC 服务默认监听 REST 端口 1即7281参见 docs/configuration/ports-config.mdgRPC 服务端口为${rest.listen_port} 1默认 7281。这也是replay --forward-to与proxy --forward-to默认指向http://127.0.0.1:7281的原因。启动一个仅含 metastore 的 Quickwit 服务./quickwit run --service metastore步骤 1准备 PostgreSQL 后端metastore 需要持久化后端仓库推荐使用 PostgreSQL。先在 Quickwit 仓库根目录启动 postgres 容器docker-compose up postgres对应的服务定义在根目录 docker-compose.yml 中使用postgres:12.17-alpine镜像默认用户名/密码/数据库分别为quickwit-dev/quickwit-dev/quickwit-metastore-dev映射端口5432可用POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB环境变量覆盖。步骤 2编写最小 quickwit.yaml一份对接上述 PostgreSQL 的最小配置version: 0.7 metastore_uri: postgres://quickwit-dev:quickwit-devlocalhost/quickwit-metastore-dev把这份配置保存为quickwit.yaml放在 Quickwit 根目录再执行步骤 1 的./quickwit run --service metastore即可让 metastore 持久化到本地 PostgreSQL。步骤 3运行 Replay在quickwit-metastore-utils所在的工作区目录下以 release 模式构建并运行cargo run --release --bin replay默认它会读取./replay-data/requests-partition-wikitenant.ndjson并逐行回放到http://127.0.0.1:7281。如果需要指定其他录制文件或目标地址cargo run --release --bin replay -- --file ./my-capture.ndjson --forward-to http://127.0.0.1:7281运行过程中Replay 会为每一行打印line {i} {...}的进度信息见 replay.rs方便对照录制文件逐条核对回放结果。进阶先录制、再回放的完整链路如果需要复现一次真实线上负载可以按如下顺序操作启动真实 metastore./quickwit run --service metastore。启动录制代理cargo run --release --bin proxy -- 127.0.0.1:7291 --forward-to http://127.0.0.1:7281 --file ./capture.ndjson把你的客户端/索引器改为连接代理地址127.0.0.1:7291让它正常工作一段时间Proxy 会把每个请求写入./capture.ndjson并透明转发。停止代理与客户端清理 metastore 状态见下节然后用 Replay 重放./capture.ndjson。两个必须知道的警告Warning原文档特别强调了两点使用约束直接影响回放的正确性务必遵守。警告 1录制文件的第一条请求是创建索引Replay 的默认录制数据第一条请求是CreateIndexRequest而该请求内含index_config 的 JSON 数据这部分结构正在经历大幅变更原文档写作时点。因此文档建议在 quickwit rev2b0e3963f67303f4e6a362d53fa8bebd3cbad33e之上进行实验以避免 index_config 结构变化导致录制数据与当前 metastore 版本不兼容。这一点对任何自行录制的文件同样适用——录制方与回放方的 Quickwit 版本应尽量保持一致至少保证CreateIndexRequest中index_config的 schema 兼容。警告 2重放数据不会清理索引与 split重复回放前必须清库Replay 只负责“发送请求”不会在回放前或回放后删除索引与 split。也就是说同一份录制文件第二次重放时CreateIndexRequest、StageSplitsRequest、PublishSplitsRequest等会与数据库中已存在的索引、split 冲突导致回放失败或状态错乱。解决办法是每次重放前清空 PostgreSQL 中的元数据表。使用psql连接数据库并执行级联清空psql -h localhost -U quickwit-dev quickwit-metastore-devTRUNCATE TABLE indexes CASCADE;执行后退出 psql\q再重新运行 Replay 即可。这也是把 PostgreSQL 作为回放后端的一个额外好处清库操作简单、可重复、可脚本化。源码级解读请求格式如何生成与反序列化为了让你能够自行构造录制文件或理解 Replay 的兼容性边界这里补充说明格式生成机制。GrpcRequest枚举不是手写的而是由 grpc_request.rs 中的三个宏协作生成build_req_enum!生成带#[serde(tagtype)]的枚举定义JSON 中通过type字段区分具体请求类型req_from_impls!为每一个具体请求类型生成FromSpecificRequest for GrpcRequest实现generate_req_enum!将上述两者串联。这样 Proxy 侧只需要写lock.record(request.get_ref().clone())就能借助From实现把任意具体请求自动装箱为GrpcRequest并序列化Replay 侧则用serde_json::from_str::GrpcCall反序列化再在replay_grpc_request中按枚举变体分派到对应 gRPC 方法。由于GrpcCall同时派生了Serialize与Deserialize只要录制文件保持这一结构你也可以用任意脚本生成自定义的 NDJSON 负载用于测试例如构造只含ListSplitsRequest的读密集场景或只含PublishSplitsRequest的写密集场景。常见问题与排查思路现象可能原因排查方向Replay 连接失败metastore 未启动或 gRPC 端口不是 7281确认./quickwit run --service metastore已运行检查--forward-to地址与 ports-config.md 中 gRPC 端口定义是否一致反序列化报错录制文件与当前版本的GrpcRequest枚举不匹配对照 grpc_request.rs 中的枚举变体清单检查type字段第二次回放报“索引已存在”类错误上一次回放的数据未清理执行TRUNCATE TABLE indexes CASCADE;后重试index_config解析失败录制/回放版本差异导致 schema 不兼容使用文档建议的 rev2b0e3963f67303f4e6a362d53fa8bebd3cbad33e或保持两端版本一致小结Replay 是 Quickwit metastore 开发与运维中非常趁手的一对工具Proxy 负责把真实 gRPC 流量无损录制为 NDJSONReplay 负责以最快速度顺序重放两者配合可以覆盖接口回归、性能基准、故障复现等多种场景。使用时的三个关键点是metastore 服务监听于7280/7281REST/gRPC、后端使用 PostgreSQL 以便重复实验、每次重放前用TRUNCATE TABLE indexes CASCADE;清理元数据。如果你需要构造自定义负载也可以参考 lib.rs 中的GrpcCall结构自行生成录制文件。【免费下载链接】quickwitCloud-native OSS search engine for observability项目地址: https://gitcode.com/GitHub_Trending/qu/quickwit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考