功能点 6:Arrow 列式格式

功能点 6:Arrow 列式格式 ------ 源码阅读笔记

对应源码阅读计划功能点 6:ArrowLogWriter/Reader、ColumnProjector 列裁剪、Arrow 内存管理。


笔记 6.1:ArrowLogWriter ------ 列式写入

文件:ArrowLogWriter.java

路径 :fluss-common/src/main/java/org/apache/fluss/record/ArrowLogWriter.java

核心实现

java 复制代码
public class ArrowLogWriter implements LogWriter {
    private final VectorSchemaRoot root;  // Arrow 的列式内存结构
    private final BufferAllocator allocator;
    private final ArrowStreamWriter ipcWriter;
    
    /**
     * 将一批行数据以列式写入 Arrow IPC 格式
     */
    public void write(LogRecordBatch batch) {
        List<RowData> rows = batch.getRows();
        int rowCount = rows.size();
        
        // ★ 关键:按列填充数据(不是按行!)
        for (int colIdx = 0; colIdx < schema.getFieldCount(); colIdx++) {
            FieldVector vector = root.getVector(colIdx);
            vector.allocateNew();  // 预分配内存
            
            // 逐列写入(与行式相反)
            for (int rowIdx = 0; rowIdx < rowCount; rowIdx++) {
                setVectorValue(vector, rowIdx, rows.get(rowIdx), colIdx);
            }
            vector.setValueCount(rowCount);
        }
        root.setRowCount(rowCount);
        
        // ★ 写入 Arrow IPC 格式
        // Arrow IPC = 元数据 (Schema) + 数据体 (RecordBatch)
        ipcWriter.writeBatch();
    }
    
    /**
     * 将单个单元格的值写入对应的 Arrow Vector
     */
    private void setVectorValue(FieldVector vector, int index, RowData row, int col) {
        DataType type = schema.getField(col).getDataType();
        
        switch (type.getPrimaryType()) {
            case INT:
                ((IntVector) vector).set(index, row.getInt(col));
                break;
            case BIGINT:
                ((BigIntVector) vector).set(index, row.getLong(col));
                break;
            case STRING:
                byte[] bytes = row.getString(col).getBytes();
                ((VarCharVector) vector).setSafe(index, bytes);
                break;
            case DECIMAL:
                BigDecimal value = row.getDecimal(col);
                ((DecimalVector) vector).set(index, value);
                break;
            // ... 其他类型
        }
    }
}

写入到 .log 文件的 Arrow IPC 布局

复制代码
.log 文件中的 Arrow 数据布局:
┌──────────────────────────────────────────┐
│  Arrow IPC Message: Schema               │
│  - field[0]: user_id (INT64, not null)   │
│  - field[1]: name (UTF8, nullable)       │
│  - field[2]: amount (DECIMAL(10,2))      │
│  ...                                     │
├──────────────────────────────────────────┤
│  Arrow IPC Message: RecordBatch 1        │
│  ┌────────────────────────────────────┐  │
│  │ user_id column: [1, 2, 3, ..., N]  │  │ ← 连续内存
│  │ name column:    ["Alice","Bob"...]  │  │ ← 连续内存
│  │ amount column:  [99.9, 49.9, ...]   │  │ ← 连续内存
│  └────────────────────────────────────┘  │
├──────────────────────────────────────────┤
│  Arrow IPC Message: RecordBatch 2        │
│  ...                                     │
└──────────────────────────────────────────┘

关键优势:同一列的所有值存储在连续内存中,CPU 缓存友好、SIMD 友好。


笔记 6.2:ArrowLogReader ------ 列式读取

文件:ArrowLogReader.java

java 复制代码
public class ArrowLogReader {
    private final int[] projectedColumns;  // 查询需要的列的索引
    private final BufferAllocator allocator;
    
    /**
     * 读取一个 RecordBatch 并进行列裁剪
     */
    public ArrowRecordBatch readBatch(FileChannel channel, long position) {
        // 1. 从指定位置读取 Arrow IPC 消息
        ArrowStreamReader reader = new ArrowStreamReader(channel, allocator);
        
        // 2. 读取完整的 RecordBatch (所有列)
        reader.loadNextBatch();
        VectorSchemaRoot fullRoot = reader.getVectorSchemaRoot();
        
        // 3. ★ 列裁剪:只保留查询需要的列
        if (projectedColumns.length < fullRoot.getSchema().getFieldCount()) {
            return project(fullRoot, projectedColumns);
        }
        
        // 4. 返回完整数据(无裁剪)
        return new ArrowRecordBatch(fullRoot);
    }
}

笔记 6.3:ColumnProjector ------ 零拷贝列裁剪

文件:ColumnProjector.java

java 复制代码
public class ColumnProjector {
    
    /**
     * 列裁剪的核心实现
     * 从完整的 VectorSchemaRoot 中提取需要的列
     * ★ 零拷贝:不复制数据,只修改引用
     */
    public static VectorSchemaRoot project(
            VectorSchemaRoot fullRoot, 
            int[] columnIndices) {
        
        // 1. 创建投影后的 Schema
        Schema fullSchema = fullRoot.getSchema();
        Schema projectedSchema = projectSchema(fullSchema, columnIndices);
        
        // 2. ★ 零拷贝:直接引用原 Vector,不复制数据
        List<FieldVector> projectedVectors = new ArrayList<>();
        
        for (int colIdx : columnIndices) {
            FieldVector originalVector = fullRoot.getVector(colIdx);
            
            // ★ TransferPair:创建对同一块内存的引用
            // 底层共享 DirectByteBuffer,零拷贝!
            TransferPair transfer = originalVector.getTransferPair(allocator);
            transfer.transfer();  // 不复制数据,只转移所有权/引用
            
            projectedVectors.add(transfer.getTo());
        }
        
        // 3. 创建投影后的 VectorSchemaRoot
        VectorSchemaRoot projected = new VectorSchemaRoot(
            projectedSchema, projectedVectors, fullRoot.getRowCount()
        );
        
        return projected;
    }
}

零拷贝原理

复制代码
原始数据 (200 列,Arrow RecordBatch)
┌────────────────────────────────────────────────────┐
│ col_0  │ col_1  │ ... │ user_id │ ... │ amount │ ... │ col_199 │
│ [val0] │ [val1] │ ... │ [100]   │ ... │ [99.9] │ ... │ [val199]│
│                           ↑                ↑
└───────────────────────────┼────────────────┼────────┘
                            │                │
                    查询只投影 user_id 和 amount:
                            │                │
                    ┌───────┘        ┌───────┘
                    ▼                ▼
         ┌──────────────┐  ┌──────────────┐
         │ user_id      │  │ amount       │
         │ [100]        │  │ [99.9]       │
         └──────────────┘  └──────────────┘
         
         ↑ 这两个 Vector 指向原始内存缓冲区
         ↑ 没有复制数据,只是创建了新引用!

内存不复制,只修改指针引用。 这就是为什么 Fluss 能做到列裁剪几乎零 CPU 开销。


笔记 6.4:Arrow 内存管理

文件:ArrowMemoryAllocator.java

java 复制代码
public class ArrowMemoryAllocator {
    private final BufferAllocator allocator;
    
    // 分配策略:
    // 1. Direct Memory(堆外内存)→ 避免 GC 压力
    // 2. Chunk 分配 → 减少内存碎片
    // 3. 引用计数 → 自动释放
    
    public ArrowBuf allocate(long size) {
        // 从 Netty 的 PooledByteBufAllocator 升级为
        // 自定义 Bump-Pointer ChunkedAllocationManager
        return allocator.buffer(size);
    }
}
java 复制代码
// org.apache.fluss.flink.row.RowDataSerializationSchema
public class RowDataSerializationSchema {
    
    public ArrowRecordBatch serialize(List<RowData> rows) {
        // Flink RowData → Arrow Vector 的类型映射:
        //
        // Flink Type          → Arrow Type
        // ─────────────────────────────────
        // IntType             → IntVector
        // BigIntType          → BigIntVector
        // VarCharType         → VarCharVector
        // DecimalType         → DecimalVector
        // TimestampType       → TimeStampMicroVector
        // ArrayType           → ListVector
        // MapType             → MapVector
        
        ArrowLogWriter writer = new ArrowLogWriter(arrowSchema);
        for (RowData row : rows) {
            writer.write(row);
        }
        return writer.flush();
    }
}

阅读小结

已理解 尚未深入
✅ Arrow 列式写入(逐列填充 Vector) ⬜ Arrow IPC 协议的消息边界和压缩
✅ 列裁剪的 TransferPair 零拷贝机制 ⬜ FilterContext 如何与列裁剪联动
✅ Arrow BufferAllocator 的 Direct Memory 策略 ⬜ LargeVarCharVector 的大字符串处理
✅ Flink RowData ↔ Arrow Vector 的类型映射 ⬜ V1 log batch format 的统计信息收集

下一步:功能点 7------副本管理器、ISR 判定、Leader 选举、Follower 同步机制。

相关推荐
头茬韭菜1 个月前
功能点 10:Delta Join 与状态外部化
fluss
头茬韭菜1 个月前
图解 Fluss(三):读写全链路 —— 一次写入、一次扫描、一次点查
fluss
头茬韭菜1 个月前
图解 Fluss(五):Flink 接入与数据生命周期 —— Connector、表生命周期与冷热分层
大数据·flink·fluss
StarRocks_labs1 个月前
StarRocks × Fluss × Paimon:流湖仓分析与 ETL 闭环
starrocks·flink·jni·native·paimon·湖仓·fluss
头茬韭菜1 个月前
图解 Fluss(四):分布式协调 —— 选举、副本状态机与
分布式·fluss
头茬韭菜1 个月前
图解 Fluss(一):一张图看清整体架构,两张图理解核心服务
架构·fluss
StarRocks_labs1 个月前
基于 Fluss、Paimon 与 StarRocks 构建淘天集团湖流一体数据链路
starrocks·olap·schema·paimon·fluss·湖流一体
StarRocks_labs1 个月前
StarRocks x Fluss x Paimon 湖流一体方案:构建秒级响应、湖流一体的实时数据引擎
starrocks·kafka·lambda·查询·paimon·fluss·湖流一体
头茬韭菜1 个月前
功能点 11-12:客户端、RPC 通信、监控与安全
网络协议·安全·rpc·fluss
头茬韭菜1 个月前
功能点 9:Flink Connector
大数据·flink·fluss