All Products
Search
Document Center

Tablestore:PlainBuffer

Last Updated:Apr 29, 2026

Compared with Protocol Buffers, PlainBuffer delivers better performance for serialization and resolution of small objects. Therefore, PlainBuffer is used to format data in Tablestore.

This document is intended for developers implementing a custom Tablestore SDK or debugging raw data transmission at the protocol level. Application developers using an existing SDK do not need to understand PlainBuffer internals.

Format definition

A PlainBuffer message is a header followed by one or more rows. Each row contains a primary key section and an attribute column section — both can be optional depending on the operation (see grammar below). Tags act as field delimiters — each tag tells the parser what comes next, so the parser can advance through the byte stream without ambiguity.

plainbuffer = tag_header row1  [row2]  [row3]
row = ( pk [attr] | [pk] attr | pk attr ) [tag_delete_marker] row_checksum;
pk = tag_pk cell_1 [cell_2] [cell_3]
attr  = tag_attr cell1 [cell_2] [cell_3]
cell = tag_cell cell_name [cell_value] [cell_op] [cell_ts] cell_checksum
cell_name = tag_cell_name  formated_value
cell_value = tag_cell_value formated_value
cell_op = tag_cell_op  cell_op_value
cell_ts = tag_cell_ts cell_ts_value
row_checksum = tag_row_checksum row_crc8
cell_checksum = tag_cell_checksum row_crc8

formated_value = value_type value_len value_data
value_type = int8
value_len = int32

cell_op_value = delete_all_version | delete_one_version
cell_ts_value = int64 
delete_all_version = 0x01 (1byte)
delete_one_version = 0x03 (1byte)
                

Tag values

Most tags are a single byte that marks the start of a field; tag_header is 4 bytes. The parser reads the tag, determines what field follows, reads the appropriate number of bytes, and advances to the next tag.

tag_header = 0x75 (4byte)
tag_pk = 0x01 (1byte)
tag_attr = 0x02 (1byte)
tag_cell = 0x03 (1byte)
tag_cell_name = 0x04 (1byte)
tag_cell_value = 0x05 (1byte)
tag_cell_op = 0x06 (1byte)
tag_cell_ts = 0x07 (1byte)
tag_delete_marker = 0x08 (1byte)
tag_row_checksum = 0x09 (1byte)
tag_cell_checksum = 0x0A (1byte)
            

ValueType values

Valid values for value_type in formated_value:

VT_INTEGER = 0x0
VT_DOUBLE = 0x1
VT_BOOLEAN = 0x2
VT_STRING = 0x3
VT_NULL = 0x6
VT_BLOB = 0x7
VT_INF_MIN = 0x9
VT_INF_MAX = 0xa
VT_AUTO_INCREMENT = 0xb
            

Checksum calculation

Checksums use CRC8. The calculation follows this logic:

  • Each cell's name, value, type, and timestamp contribute to that cell's checksum.

  • Each row's delete marker contributes a single byte: 0x1 if the row has a delete marker, 0x0 if it does not.

  • The row checksum is computed by running CRC8 over the individual cell checksums — not over the raw cell data.

Java implementation:

Note

The following code is extracted from $tablestore-4.2.1-sources/com/alicloud/openservices/tablestore/core/protocol/PlainBufferCrc8.java. For more information, see Installation.

public static byte getChecksum(byte crc, PlainBufferCell cell) throws IOException {

    if (cell.hasCellName()) {
        crc = crc8(crc, cell.getNameRawData());
    }

    if (cell.hasCellValue()) {
        if (cell.isPk()) {
            crc = cell.getPkCellValue().getChecksum(crc);
        } else {
            crc = cell.getCellValue().getChecksum(crc);
        }
    }

    if (cell.hasCellTimestamp()) {
        crc = crc8(crc, cell.getCellTimestamp());
    }

    if (cell.hasCellType()) {
        crc = crc8(crc, cell.getCellType());
    }

    return crc;
}

public static byte getChecksum(byte crc, PlainBufferRow row) throws IOException {
    for (PlainBufferCell cell : row.getPrimaryKey()) {
        crc = crc8(crc, cell.getChecksum());
    }

    for (PlainBufferCell cell : row.getCells()) {
        crc = crc8(crc, cell.getChecksum());
    }

    byte del = 0;
    if (row.hasDeleteMarker()) {
        del = (byte)0x1;
    }
    crc = crc8(crc, del);

    return crc;
}
            

Example

The following row has two primary key columns and four attribute columns.

  • Primary key columns:

    • [pk1:string:iampk]

    • [pk2:integer:100]

  • Attribute columns:

    • [column1:string:bad:1001]

    • [column2:integer:128:1002]

    • [column3:double:34.2:1003]

    • [column4:del_all_versions]

Encoding:

<The start position for headers>[0x75]
<The start position for primary key columns>[0x1]
  <Cell1>[0x3][0x4][0x3][3][pk1][0x5][0x3][5][iampk][_cell_checksum]
  <Cell2>[0x3][0x4][0x3][3][pk2][0x5][0x0][8][100][_cell_checksum]
<The start position for attribute columns>[0x2]
  <Cell1>[0x3][0x4][0x3][7][column1][0x5][0x3][3][bad][0x7][1001][_cell_checksum]
  <Cell2>[0x3][0x4][0x3][7][column2][0x5][0x0][8][128][0x7][1002][_cell_checksum]
  <Cell3>[0x3][0x4][0x3][7][column3][0x5][0x1][8][34.2][0x7][1003][_cell_checksum]
  <Cell4>[0x3][0x4][0x3][7][column4][0x6][1][_cell_checksum]
[_row_check_sum]

Reading the first primary key cell byte by byte:

Bytes

Tag/field

Meaning

0x3

tag_cell

Start of a cell

0x4

tag_cell_name

The cell name follows

0x3

value_type

VT_STRING — the name is a string

3

value_len

Name is 3 bytes long

pk1

value_data

Cell name: pk1

0x5

tag_cell_value

The cell value follows

0x3

value_type

VT_STRING — the value is a string

5

value_len

Value is 5 bytes long

iampk

value_data

Cell value: iampk

_cell_checksum

tag_cell_checksum + CRC8

Checksum over name, value, type, and timestamp

The second primary key cell follows the same structure, except the value type is 0x0 (VT_INTEGER) and value_len is 8 (a 64-bit integer).

Attribute cells add a timestamp field (0x7 = tag_cell_ts) after the value. column4 has no value — the 0x6 tag (tag_cell_op) with value 1 marks it as a delete-all-versions operation.