Skip to content

超大基数bitmap的存取

Chen Huajun edited this page Dec 18, 2022 · 3 revisions

问题

如果我们需要将roaringbitmap列中存储的数据取回客户端,有几种方式。常规的方式包括

  • 方法1:将bitmap中的每个数字作为一条记录返回

    比如:

     SELECT unnest(rb_to_array('{1,2,3}'::roaringbitmap));
    

     SELECT rb_iterate('{1,2,3}'::roaringbitmap);
    
  • 方法2:将bitmap转换成数组传回客户端

    比如:

     SELECT rb_to_array('{1,2,3}'::roaringbitmap);
    
  • 方法3:将bitmap格式化成字符串传回客户端

    比如:

     SET roaringbitmap.output_format='array';
     SELECT '{1,2,3}'::roaringbitmap::text;
    

但是,对于一个基数超大的bitmap,以上方法需要做类型转换。类型转换不仅耗费CPU,而且转换后的数据大小可能会远大于原始的roaringbitmap, 不仅增加了数据传输的开销,单个字段超过1GB还会产生内存分配错误。

ERROR: invalid memory alloc request size 1073741824

除了读取,写入一个基数超大的bitmap也存在类似问题。

解决方案

读取原始的roaringbitmap二进制裸数据,然后在客户端进行解析。 Roaringbitmap有各种语言的类库实现,可以轻松解析roaringbitmap数据。写bitmap也类似,先在客户端生成好roaringbitmap数据再直接把二进制数据写到数据库。 以下是Java语言的实现:

采用这种方案的示例如下:

表定义

create table testtb(id int, bitmap roaringbitmap);

bitmap读取

String sql = "select bitmap::bytea from testtb where id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setInt(1, 1);
ResultSet rs = stmt.executeQuery();
while(rs.next()){
    RoaringBitmap rb = new RoaringBitmap();
    DataInputStream is = new DataInputStream(rs.getBinaryStream(1));
    rb.deserialize(is);
    is.close();
}
rs.close();
stmt.close();

bitmap写入

String sql = "INSERT INTO testtb(id, bitmap) VALUES (?,?::bytea::roaringbitmap)";
PreparedStatement stmt = conn.prepareStatement(sql);
RoaringBitmap rb = RoaringBitmap.bitmapOf();
for(int i = 0; i < 10000000; i ++)
  rb.add((int)(1+Math.random()*100000000));
  rb.runOptimize(); // run存储格式优化
  byte[] array = new byte[rb.serializedSizeInBytes()];
  try {
      rb.serialize(new java.io.DataOutputStream(new java.io.OutputStream() {
      int c = 0;
      public void flush() {
      }
      public void close() {
      }
      public void write(int b) {
        array[c++] = (byte) b;
      }
      public void write(byte[] b) {
        write(b, 0, b.length);
      }
      public void write(byte[] b, int off, int l) {
        System.arraycopy(b, off, array, c, l);
        c += l;
      }
    }));
  } catch (IOException ioe) {
    throw new RuntimeException("unexpected error while serializing to a byte array");
  }
stmt.setInt(1, 1);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(array);
stmt.setBinaryStream(2, byteArrayInputStream, array.length);
stmt.executeUpdate();
stmt.close();

参考