Data structures and storage engines
Serialization pitfalls
The production bugs that come from encoded data. Large integers in JSON, mixed timestamp units, enums, null versus absent, money as floats, and checksums that are not security.
Most serialization bugs are not crashes. The data arrives, parses and looks right, but a number is off, a time is shifted or a field means something different to the reader. These bugs live in the gap between what the writer meant and what the reader decoded. This page lists the common ones and how to prevent each.
Big integers in JSON and JavaScript
JavaScript stores numbers as 64-bit floats. Integers are exact only up to Number.MAX_SAFE_INTEGER, which is 2^53 − 1. Above that, a value is rounded silently.
sent in JSON: {"id": 9007199254740993}
read in JavaScript: 9007199254740992Snowflake IDs and 64-bit database keys often exceed this limit. Send them as strings in JSON, or use a binary format with a true 64-bit integer type. The details are in binary, integers and overflow.
Timestamp units and zones
A timestamp can be seconds or milliseconds, and it can be a Unix number or a local string. One consumer reading seconds and another reading milliseconds shifts every event by a factor of 1,000. Make the unit and the zone explicit in the schema. Use a typed timestamp when the format has one, such as Protobuf's google.protobuf.Timestamp.
Enums that grow
Adding a new enum value is a compatibility hazard. An old reader with a switch that has no default branch may crash on the new value. Plan for an unknown value: give the switch a default branch that logs and keeps going. In Protobuf, an enum value the reader does not know should be kept, not treated as an error.
Null versus absent
These three JSON payloads mean different things.
{"phone": null} phone was cleared on purpose
{} phone was not sent, so it is unknown
{"phone": "555-0100"} phone has a valueAvro unions such as ["null", "string"] make null explicit. Protobuf tracks whether a field is set, and the rules differ across editions. Write down what each state means: "unknown" or "cleared by the user". Otherwise, one service reads a missing field as an empty value, and another treats it as a deletion.
Money as a float
Encode currency as integer minor units, such as total_cents, or as a decimal string. Do not use a binary float or double, in any format. The rounding errors are the same whether the value travels as JSON or Protobuf. See floating point and money.
Checksums are not security
Storage formats and network frames often carry a checksum, so bit flips can be caught.
| Tool | Purpose |
|---|---|
| CRC32 or CRC32C | Fast detection of accidental corruption |
| xxHash or CityHash | Fast hashing for partitioning and hash maps, not security |
| SHA-256 | Cryptographic hash, for integrity against an attacker and content addressing |
| AEAD (AES-GCM, ChaCha20-Poly1305) | Encryption plus integrity. See crypto basics |
Warning
A CRC does not prove that data is authentic. Anyone can change the data and recompute the CRC. A matching checksum means the bits were not damaged by accident. It does not mean the data came from who you think. Authenticity needs a signature, a message authentication code or TLS.
Common mistakes
- Trusting the parse. Valid JSON can still hold a rounded integer or a wrong unit.
- Leaving the unit implicit. Put seconds or milliseconds in the field name, or use a typed timestamp.
- Using a switch with no default on an enum. New values crash old readers.
- Treating a checksum as a signature. It detects damage, not tampering.
Check yourself
- Why does
9007199254740993come back as9007199254740992in JavaScript? The value is above2^53 − 1, so the 64-bit float cannot hold it exactly, and it rounds. - A message has no
phonefield. Why must the producer and consumer agree on what that means? Absent can mean unknown, or it can mean the user cleared the value. The two readings lead to different actions.
Key takeaways
- Integers above
2^53 − 1need strings or a true 64-bit type in JSON. - Make timestamp units and zones explicit, and use typed timestamps where available.
- Plan for enum values that a reader does not yet know.
- Define null, absent and cleared as separate states.
- Store money as integers or decimal strings.
- A CRC shows that bits were not damaged, not that the data is authentic.
Further learning
Kleppmann's book covers encoding, schema evolution and the failure modes of data formats in depth.
Designing Data-Intensive Applications (Kleppmann)Designing Data-Intensive Applications (DDIA) — an O’Reilly book by Martin Kleppmann (The Wild Boar Book)dataintensive.net Protocol Buffers documentationProtobuf is a language-neutral, platform-neutral extensible mechanism for serializing structured data.protobuf.devRelated
Serialization, schemas and compression
How data becomes bytes for the wire and for disk, how formats like JSON, Protobuf and Avro differ, how to change a schema safely, and when compression pays. Start here for data formats.
Numbers, text and encodings
How integers, floats, text, timestamps and Base64 are really stored as bytes, and the production bugs each one causes. Start here for the data-representation map.
Binary, integers and overflow
How integers are stored as bits, why signed counters wrap around, and why byte order matters when you read raw binary.
Cryptography for engineers
The few building blocks you need for everyday security decisions, which question each one answers, and where teams go wrong. Start here for the map.