Saltar a contenido

Sbe Serialization

SBE es el formato binario ultra-low-latency usado por Camunda para records en el log y comunicación entre brokers. Da performance extremo (10-50 ns por encode/decode) gracias a 6 design principles: copy-free, native types, allocation-free, streaming access, word-aligned, backward compat. Para el MVP: NO usar — JSON/Protobuf son suficientes. Las principles son aplicables a cualquier sistema high-performance.

Qué es SBE

SBE = Simple Binary Encoding. Especificación FIX (Financial Information eXchange) para encoding de mensajes en binario. Implementación reference open-source por Real Logic Limited.

  • Layer: OSI layer 6 (presentation)
  • Performance: encode/decode en decenas de nanosegundos
  • Use case original: financial trading (latency-sensitive)
  • Adoptado por: Camunda (Zeebe), Aeron, sistemas de trading

Por qué Camunda lo usa

Camunda lo usa para: - Records persistidos en el log: cada event/command serializado vía SBE - Inter-partition communication: brokers se hablan vía SBE - NO se usa para: API cliente (Protobuf/gRPC) o variables (MessagePack)

Razones: - Throughput máximo en hot paths del engine - Latencia predecible (sin GC variance) - Versioning del log via SBE schema versioning

Las 6 Design Principles

1. Copy-Free

"Not employ any intermediate buffers for the encoding or decoding of messages."

Encode/decode directamente al buffer subyacente. No staging. No copies.

// SBE direct write:
buffer.putLong(offset, value);  // single CPU instruction

// vs JSON (multiple copies):
String json = mapper.writeValueAsString(obj);  // creates String
byte[] bytes = json.getBytes();                 // copies to byte[]
output.write(bytes);                            // copies to output buffer

2. Native Type Mapping

Tipos del schema mapean directamente a tipos CPU-native:

SBE type CPU type Instructions
int64 64-bit integer 1 MOV
float64 64-bit double 1 MOV
char 8-bit byte 1 MOV
string byte slice 1 MOV (zero-copy)

Field access cost ≈ struct field access en C.

3. Allocation-Free (Flyweight)

Decoder es un window sobre el buffer, no una copy:

ProcessInstanceDecoder decoder = new ProcessInstanceDecoder();
decoder.wrap(buffer, offset);  // NO allocation
long key = decoder.processInstanceKey();
String bpmnProcessId = decoder.bpmnProcessId();  // points into buffer

Beneficios: - Zero GC pressure: no objetos creados - No CPU cache churn: misma memoria reusada - Latencia consistente: sin stop-the-world pauses

4. Streaming Access

Forward-only addressing — secuencial, no random:

// Bueno: sequential access
for (int i = 0; i < count; i++) {
    long value = buffer.getLong(offset + i * 8);
}

// Malo: random access
long a = buffer.getLong(1000);
long b = buffer.getLong(50);
long c = buffer.getLong(10000);

Exploits: - CPU prefetching automático - Cache lines completos - Branch prediction efectivo

5. Word Aligned Access

Fields alineados a word boundaries del CPU:

Offset 0:  int64 (8 bytes aligned to 8) ✓
Offset 8:  int32 (4 bytes aligned to 4) ✓
Offset 12: padding (4 bytes)
Offset 16: int64 (next 8-byte boundary) ✓

Misaligned access penaliza: - May require multiple reads - Cache line crossing - 2-10x slower que aligned

6. Backward Compatibility

Mechanism para evolution: - Nuevos sistemas agregan campos opcionales al final - Sistemas viejos ignoran campos unknown - Mixed-version deployments funcionan

Schema en XML

<sbe:messageSchema id="1" version="0" byteOrder="littleEndian">
  <types>
    <composite name="messageHeader">
      <type name="blockLength" primitiveType="uint16"/>
      <type name="templateId" primitiveType="uint16"/>
      <type name="schemaId" primitiveType="uint16"/>
      <type name="version" primitiveType="uint16"/>
    </composite>
  </types>

  <sbe:message name="ProcessInstanceRecord" id="1">
    <field name="processInstanceKey" id="1" type="int64"/>
    <field name="bpmnProcessId" id="2" type="utf8String"/>
    <field name="version" id="3" type="int32"/>
  </sbe:message>
</sbe:messageSchema>

Compiler (sbe-tool) genera Java/C++/C# classes con encoder/decoder.

Performance comparativa

Formato Encode Decode GC pressure Self-describing
SBE 10-50 ns 10-50 ns Zero NO
Protobuf 100-500 ns 100-500 ns Moderate NO
MessagePack 50-200 ns 50-200 ns Moderate YES
JSON 1-10 μs 1-10 μs High YES
Avro 100-300 ns 100-300 ns Moderate YES

SBE es 10-100x más rápido que Protobuf, 100-1000x más rápido que JSON.

Trade-offs: - SBE: máxima performance, schema obligatorio, no human-readable - Protobuf: muy rápido, gRPC integration, tooling maduro - JSON: human-readable, ergonómico, lento - MessagePack: balance entre binary y readable

Para el MVP: NO usar SBE

Razones para skip

  1. MVP no necesita nanosegundos: single-digit ms es target aceptable (ver analysis/sizing-benchmarks)
  2. Postgres como backend: serialization no es el bottleneck (SQL roundtrip domina)
  3. Debuggability matters: tools standard funcionan con JSON
  4. Team productivity: JSON/Protobuf más productivo que SBE

Stack recomendado para el MVP

Use case Formato Razón
Log internal JSON en JSONB de Postgres Queryable, debuggable
Cliente API JSON sobre REST Standard, ergonómico
gRPC opcional Protobuf Si streaming jobs
Variables del proceso JSON Compatible con FEEL

Lecciones aplicables sin usar SBE

Las 6 design principles aplican a cualquier sistema high-performance, no solo SBE:

  1. Copy-free: minimizar buffers intermedios en hot paths
  2. Native types: usar primitivos del lenguaje (no Integer, Long boxing en Java)
  3. Allocation-free / pooling: lección de concepts/microbenchmark-methodology — reuso reduce allocations 25x
  4. Streaming access: iterar secuencialmente
  5. Word aligned: relevante para sistemas con perf goals
  6. Backward compat: schema versioning desde día 1 (incluso en JSON)

Cuándo reconsider SBE para el MVP

  • Si el MVP necesita > 100K TPS
  • Si latencia P99 < 1ms es requirement
  • Si Postgres se reemplaza con storage custom embebido
  • Si serialization aparece en profiler como top hot spot

Hasta entonces, JSON/Protobuf es la elección correcta.

Recursos

  • SBE GitHub: https://github.com/real-logic/simple-binary-encoding
  • Mechanical Sympathy blog (Martin Thompson, creador de SBE): https://mechanical-sympathy.blogspot.com
  • Aeron messaging system: relacionado, mismo equipo