Apache IoTDB 高效插入数据的方法

4 篇文章 0 订阅
3 篇文章 0 订阅

IoTDB当前支持的客户端

哪种插入方式的效率更高

IoTDB为当前主流语言基本都提供了对应的native api,有一些语言的客户端放在了Apache IoTDB的主仓库下,如Java, C/C++, Python;另一些由于各种语言的依赖管理不同,放在了社区贡献者个人的仓库下,如Golang, C#以及Rust。

因为IoTDB本身是用Java编写,所以Java的客户端更新和维护的较为完善。因为IoTDB提供了一种类sql的查询语言,所以对于Java客户端,不仅提供了native api,还提供了JDBC接口,方便之前熟悉使用JDBC接口的客户迁移。

由于各个客户端native api之间的实现差别不大,所以接下来我们以Java的native api作为native api的代表,与JDBC接口进行写入性能的对比实验。

实验数据建模

关于IoTDB具体的建模方式,在下面这篇文章里有详细介绍,我就不过多赘述了

Apache IoTDB 建模方式详解

本次实验的数据模型采用官网的风电场物联网场景的数据模型作为示例:

root.ln.wf01.wt01.status

root.ln在IoTDB中是存储组的名字,类似于关系型数据库中db的概念,wf01.wt01是业务场景里的设备名字,最后status是设备上的传感器名字。一个设备上通常会有多个传感器进行数据采集,所以上述的wf01.wt01设备可能还有temperature, speed等传感器。

实验代码

完整代码我已经放在Github上了: iotdb-insert-experiment

Java Native API(session接口)插入代码

/**
 * 使用Session.insertTablet接口插入某一个设备的数据
 */
private static void insertTablet(Session session) throws IoTDBConnectionException, StatementExecutionException {
    /*
     * 一个Tablet例子:
     * deviceID: root.ln.wf01.wt01
     * time status, temperature, speed
     * 1    true        1.0       1
     * 2    false       2.0       2
     * 3    true        3.0       3 
     */
    // 设置设备名字,设备下面的传感器名字,各个传感器的类型
    List<MeasurementSchema> schemaList = new ArrayList<>();
    schemaList.add(new MeasurementSchema("status", TSDataType.BOOLEAN));
    schemaList.add(new MeasurementSchema("temperature", TSDataType.DOUBLE));
    schemaList.add(new MeasurementSchema("speed", TSDataType.INT64));

    Tablet tablet = new Tablet("root.ln.wf01.wt01", schemaList, BATCH_INSERT_SIZE);


    // 以当前时间戳作为插入的起始时间戳
    long timestamp = System.currentTimeMillis();

    for (long row = 0; row < TOTAL_INSERT_ROW_COUNT; row++) {
        int rowIndex = tablet.rowSize++;
        tablet.addTimestamp(rowIndex, timestamp);
        // 随机生成数据
        tablet.addValue("status", rowIndex, (row & 1) == 0);
        tablet.addValue("temperature", rowIndex, (double) row);
        tablet.addValue("speed", rowIndex, row);

        if (tablet.rowSize == tablet.getMaxRowNumber()) {
            session.insertTablet(tablet);
            tablet.reset();
            System.out.println("已经插入了:" + (row + 1) + "行数据");
        }
        timestamp++;
    }

    // 插入剩余不足 BATCH_INSERT_SIZE的数据
    if (tablet.rowSize != 0) {
        session.insertTablet(tablet);
        tablet.reset();
    }
}

JDBC插入代码

private static void jdbcInsert(Statement statement) throws SQLException {
    // 设置设备名字,设备下面的传感器名字,各个传感器的类型
    statement.execute("SET STORAGE GROUP TO root.sg1");
    statement.execute(
            "CREATE TIMESERIES root.ln.wf01.wt01.status WITH DATATYPE=BOOLEAN, ENCODING=RLE, COMPRESSOR=SNAPPY");
    statement.execute(
            "CREATE TIMESERIES root.ln.wf01.wt01.temperature WITH DATATYPE=DOUBLE, ENCODING=RLE, COMPRESSOR=SNAPPY");
    statement.execute(
            "CREATE TIMESERIES root.ln.wf01.wt01.speed WITH DATATYPE=INT64, ENCODING=RLE, COMPRESSOR=SNAPPY");

    // 以当前时间戳作为插入的起始时间戳
    long timestamp = System.currentTimeMillis();
    int row = 0;
    while (row < TOTAL_INSERT_ROW_COUNT) {
        row++;
        statement.addBatch(prepareInsertStatment(timestamp + row, row));
        if (row % BATCH_INSERT_SIZE == 0) {
            statement.executeBatch();
            statement.clearBatch();
            System.out.println("已经插入了:" + row + "行数据");
        }
    }

    // 插入剩余不足 BATCH_INSERT_SIZE的数据
    if (row != 0 && (row % BATCH_INSERT_SIZE) != 0) {
        statement.executeBatch();
        statement.clearBatch();
    }
}

private static String prepareInsertStatment(long time, long row) {
    return "insert into root.ln.wf01.wt01(timestamp, status, temperature, speed) values("
            + time
            + ","
            + ((row & 1) == 0)
            + ","
            + ((double) row)
            + ","
            + row
            + ")";
}

实验环境

  • 实验机器: MacBook Pro (14-inch, 2021)
  • 操作系统: macOS Monterey Version 12.1 (21C52)
  • CPU: Apple M1 Pro 10核(8个大核+2个小核)
  • 内存: 16GB
  • IoTDB版本:0.12.4
  • IoTDB配置:默认配置,未做任何修改

实验数据量

  • 1个device: root.ln.wf01.wt01
  • device下三个传感器
    • status: boolean类型
    • temperature: double类型
    • speed: int64类型
  • 总共插入10,000,000行,使用批量插入的方式,每隔1,000行插入一次

实验结果

Session insert 10000000 rows cost: 73551ms.

JDBC insert 10000000 rows cost: 123820ms.

Java Native API(session接口)插入的方式比JDBC插入的方式快了68%

实验结论

从上面的实验结果可以看出来,Java Native API的插入效率要比JDBC高出很多,所以在选择插入接口时,我们更加推荐用户选择Java Native API,这也是为什么官网文档的应用编程接口中,JDBC被标注了不推荐的原因。

其实之所以慢的原因也很简单

  • 通过JDBC接口发到iotdb server端的是一条一条的原始sql,server首先需要对这些sql进行解析,这一步就很耗时
  • 其次,即使在JDBC客户端这边是将多条插入语句组装成一个Batch发送到server,server这边的处理逻辑依然是一条一条的执行,每一条插入都会拿一次写锁;而对于Native API来说,一次Bacth的插入,在server处只会拿一次写锁,大大减少了拿锁的次数,如果存在读写或写写并发的话,Native API能减少锁冲突的可能性,所以在存在大量并发读写的情况下,两者的性能差异将被进一步放大
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值