You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#分布式系统下如何高效按行处理Parquet文件并实现行级删除

Parquet格式下按行索引及删除的C#实现方案

核心前提:Parquet不存在单条行的连续字节偏移

你之前针对CSV的字节偏移方案完全不适用于Parquet,本质原因是Parquet是按行组+列块的结构存储:单文件内部分为多个行组,每个行组内的不同列数据拆分为独立的列块存储,同一条行的不同列数据不存在连续的字节区间,不可能通过删除固定字节范围的方式移除单条行。

常见疑问解答

需要发起10次调用拼接10列的完整行吗?

完全不需要。成熟的Parquet读写库已经内部实现了行组内列数据到行的映射逻辑,你只需要调用一次行读取接口就能拿到完整行,不需要自行拼接多列数据。
建索引时甚至可以只读取主键列,完全不加载其他列,性能比全量读CSV建索引还要高。

删除时需要先拼接整行再处理字节吗?

不需要。Parquet是为只读场景优化的存储格式,不支持原地修改字节内容,所有删除操作都是通过重写变更的行组实现,不需要计算单条行的字节大小。

索引设计方案(无膨胀问题)

直接抛弃字节偏移思路,改用行组ID + 行在组内的序号作为单条行的位置标识:

  • 单个索引条目仅需存储两个整数,体积比你原来的CSV偏移区间更小,完全不会出现索引膨胀问题
  • 主键到位置的映射示例:user11 -> (RowGroupId: 2, RowInGroupIndex: 17)

C# 后台作业实现流程

依赖选型

直接用NuGet上成熟的Parquet.Net库即可,支持所有.NET Core/.NET 5+版本,性能优异、API易用。

建索引流程

只读取主键列即可完成索引构建,IO和内存占用极低:

using var stream = File.OpenRead("your_file.parquet");
using var reader = await ParquetReader.CreateAsync(stream);
// 假设主键是第0列,仅读取主键列的元数据,不加载其他列
DataField pkField = reader.Schema.GetDataFields()[0];

for(int rowGroupId = 0; rowGroupId < reader.RowGroupCount; rowGroupId++)
{
    using var rowGroupReader = reader.OpenRowGroupReader(rowGroupId);
    // 仅读取当前行组的主键列数据
    Array pkData = await rowGroupReader.ReadColumnAsync(pkField);
    for(int rowInGroupIdx = 0; rowInGroupIdx < pkData.Length; rowInGroupIdx++)
    {
        string userId = pkData.GetValue(rowInGroupIdx).ToString();
        // 写入你的索引表:userId 对应 (rowGroupId, rowInGroupIdx)
    }
}

按行删除流程

  1. 收到删除请求后,查询索引表拿到所有待删除行的位置,按行组ID分组汇总每个行组需要删除的行序号。
  2. 遍历原Parquet文件的所有行组:
    • 若该行组无待删除行,直接将行组原始字节复制到新文件,不需要解码/编码,性能极高
    • 若该行组有待删除行,读取该行组全量列数据,过滤掉待删除行后,将剩余行编码写入新文件
  3. 用新生成的Parquet文件替换原文件,删除索引表中对应已删除用户的条目即可。

高规模分布式场景优化建议

  • 批量累积删除:如果是高频小批量删除请求,可以先将待删除的用户ID存入独立的删除标记库,查询时先过滤已标记删除的用户,等待删除行占对应行组行数的20%~30%时再统一重写Parquet文件,减少不必要的IO开销。
  • 分布式并行处理:大文件可以按行组拆分给多个作业节点并行处理,进一步提升处理速度。

内容的提问来源于stack exchange,提问作者Sejal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 23:24:04