“Schema is the contract between writer and reader. Get it wrong and your data lake becomes a data landfill.”
— Doug Cutting (co-creator of Avro and Hadoop)
Summary
This note defines serialization formats as the choices that govern how data moves between memory, storage, and network boundaries, then compares text and binary formats, compression codecs, and format-selection trade-offs so engineers can pick the right contract for debugging, interoperability, analytical reads, and trusted persistence.
Decision matrix and codec trade-offs
Starts with a format decision matrix and codec comparison so speed, size, schema behavior, and readability are considered together rather than one format at a time.
Treats compression choice as part of serialization design because file size, decompression speed, and downstream tooling all change operational outcomes.
Format families and use cases
Covers JSON, YAML, CSV, MessagePack, Protobuf, Avro, Parquet, and Pickle, linking each format to the workflows where it is a good fit and the boundaries where it becomes risky.
Compares schema-less, schema-required, row-oriented, and columnar formats so the trade-offs stay visible across APIs, configs, messaging, and analytics.
Operational fit and safety boundaries
Explains how format choice affects debugging, cross-language support, warehouse loading, streaming interoperability, and security posture.
Uses warnings such as YAML coercion, Parquet row-group sizing, and Pickle code execution to show that serialization errors are often architecture and safety failures, not just parsing inconveniences.
Operations and safety
Warnings: text readability often costs speed and size, binary performance often needs stronger schema discipline, and Pickle must never cross untrusted boundaries.
Recommendations: use JSON for universal interoperability, Parquet for analytical storage, Avro or Protobuf where schema contracts matter, zstd as the default codec, and safer formats instead of Pickle for any external or shared boundary.
Glossary
Serialization format
A representation used to encode in-memory data into bytes or text for storage, transport, or later reconstruction.
It matters here because the note compares formats as architectural interface choices rather than as isolated library features.
Boundary contract
Once data crosses a process, file, or network boundary, the serialization format becomes part of the interface every producer and consumer must honor.
JSON
A text-based schema-less data format widely used for APIs, configs, and debugging-friendly interchange.
It matters here because JSON is often the interoperability default even when it is not the most efficient format.
Universal but verbose
JSON wins by ubiquity and readability, not by compactness or raw throughput.
YAML
A human-oriented text format commonly used for configuration files and declarative infrastructure documents.
It matters here because the note positions YAML as useful for configs but risky for general data exchange.
Implicit coercion surprises
YAML parsers may reinterpret unquoted values as booleans, dates, or numbers, which creates subtle bugs when literal strings resemble those shapes.
CSV
A simple text-based tabular format that stores rows as delimited strings with little or no embedded type information.
It matters here because CSV remains a common interchange format even though it lacks schema, compression awareness, and analytical efficiency.
Broadest tabular compatibility
CSV persists because almost every tool can read it, not because it is a strong format for long-term analytical correctness or performance.
MessagePack
A compact binary format with a JSON-like data model intended as a faster and smaller replacement for JSON.
It matters here because it offers a useful middle ground for internal systems that want speed without introducing a separate schema language.
Faster JSON substitute
MessagePack is most attractive when both sides are controlled internally and human readability is no longer worth the overhead of JSON.
Protocol Buffers / Protobuf
A binary schema-first format that uses numbered fields to support compact cross-language interchange and controlled evolution.
It matters here because the note treats Protobuf as the preferred format for strongly typed service boundaries such as gRPC.
Schema discipline required
Protobuf’s performance benefits come with the expectation that teams manage .proto files and compatibility rules carefully over time.
Avro
A binary format with schema evolution support, often paired with schema registries and streaming systems.
It matters here because Avro is a common answer when pipelines need compact messages plus evolving schemas in event-driven environments.
Streaming-friendly evolution
Avro is especially useful when producers and consumers evolve at different times but still need a shared compatibility contract.
Parquet
A columnar binary format optimized for analytical reads, compression, and schema-aware large-scale data storage.
It matters here because the note treats Parquet as the default analytical storage format for lakes, warehouses, and bulk movement.
Layout affects performance
Partitioning and row-group sizing matter almost as much as the format choice itself. Poor physical layout can negate Parquet’s analytical advantages.
Pickle
A Python-specific binary serialization format that can encode arbitrary Python objects.
It matters here because it is convenient inside trusted Python-only boundaries but extremely unsafe for broader interchange.
Code execution risk
Unpickling attacker-controlled or otherwise untrusted bytes can execute arbitrary code. That makes Pickle a local trusted-only tool, not a general data-exchange format.
Compression codec
The algorithm used to reduce the size of serialized data, trading off compression ratio, encode speed, and decode speed.
It matters here because formats such as Parquet are often deployed together with codec decisions that materially affect cost and runtime behavior.
Format and codec interact
A good format with a poor codec choice can still be operationally expensive. Compression should be selected with workload and reader behavior in mind.
Serialization Format Decision Matrix
Choose your format based on the primary constraint: speed, size, schema enforcement, cross-language support, or human readability.
Format
Speed
Size
Schema
Cross-Language
Best For
JSON
Slow
Large
No
Excellent
APIs, configs, debugging
YAML
Slow
Large
No
Good
Config files (Airflow, Docker, K8s)
CSV
Moderate
Large
No
Excellent
Tabular data exchange
MessagePack
Fast
Small
No
Good
Fast JSON replacement (internal APIs)
Protobuf
Fastest
Smallest
Required
Excellent
gRPC, cross-service communication
Avro
Fast
Small
Embedded
Good
Kafka, streaming, schema evolution
Parquet
Fast read
Smallest
Embedded
Good
Analytics, data lakes
Pickle
Fast
Medium
No
Python only
Python-to-Python (never untrusted data)
Format Selection Guide
APIs and configs → JSON (universal) or YAML (human-friendly config)
Data lake and analytics → Parquet (columnar, compressed, schema embedded). For loading Parquet into BigQuery, see data-loading-and-export.
Quick Python object persistence → Pickle (but NEVER deserialize from untrusted sources)
Compression Codecs
Codec
Speed
Ratio
Best For
gzip
Slow
Best (70-80% reduction)
Archival, cold storage, network transfer
zstd
Fast
Very good (65-75% reduction)
Default choice — best speed/ratio trade-off
snappy
Fastest
Moderate (50-60% reduction)
Hot data, frequent reads, Parquet default in Spark
lz4
Fastest
Moderate (50-60% reduction)
Real-time systems, message queues
Default Codec Recommendation
Use zstd as the default compression codec for Parquet files and data
archives. It compresses nearly as well as gzip but decompresses 5-10x
faster. Snappy is faster to decompress but compresses 15-20% less.
The only reason to use gzip in 2026 is backward compatibility with
systems that don’t support zstd (increasingly rare).
Standard for config files: Airflow DAGs, Docker Compose, Kubernetes manifests, GitHub Actions
Dangerous: YAML has implicit type coercion (yes → true, 1.0 → float, 2026-03-10 → date object)
Never use YAML for data exchange — use JSON
YAML Implicit Type Coercion
YAML silently converts strings: yes, no, on, off, true, false → boolean; 2026-03-10 → date object; 1e5 → 100000.0 (float). This breaks when a field value happens to look like a boolean or date. Always quote strings in YAML config files when they contain ambiguous values.
Quote all ambiguous string values in YAML config files
Write enabled: "yes" not enabled: yes, and date: "2026-03-10" not date: 2026-03-10. Use a YAML linter (yamllint) in CI to catch unquoted boolean-like and date-like values before they reach production.
CSV
Text format, tabular
No schema, no types — all values are strings
Universal: Excel, SQL Server bulk insert, pandas, every ETL tool
Large: no compression, no column pruning
Best for: data exchange with external parties, human inspection, SQL Server BULK INSERT
Fields are identified by their number (1, 2, 3), not their name. You can rename a field without breaking backward compatibility. You can add new fields without breaking old readers (they ignore unknown field numbers).
Avro
Binary format, schema embedded in the file/message
The standard for Kafka streaming pipelines
Schema evolution: readers can use a different schema version than writers — field additions and removals are handled gracefully
Row groups that are too small (1,000 rows) create excessive metadata
overhead — each row group has its own statistics, column chunks, and
page headers. Row groups that are too large (100M rows) prevent
effective predicate pushdown — the query must scan the entire group
even if only 1% of rows match the filter. Target 128 MB per row group
(Parquet default) or 50K-1M rows for typical financial datasets.
Target 128 MB per row group and partition by date for financial datasets
Use PyArrow’s max_rows_per_group or set the row group size explicitly when writing Parquet files. Partition files by trade_date so queries that filter on date only scan the relevant partition. This combination gives optimal predicate pushdown and keeps per-file metadata overhead low.
Pickle
Python-specific binary format
Fastest Python object serialization — saves any Python object including custom classes
No cross-language support
Security risk: pickle.load() on untrusted data executes arbitrary code — it’s a known remote code execution vector
Never Unpickle Untrusted Data
pickle.load() can execute arbitrary Python code. If an attacker controls a .pkl file you load, they own your process. Only use Pickle for Python-to-Python workflows where you control both the writer and the reader and the data never travels over a network or through untrusted storage.
Use MessagePack or JSON for any inter-system data exchange instead of Pickle
MessagePack is a safe, fast, cross-language binary format that replaces Pickle for internal Python pipelines where you need speed. For anything that crosses a network boundary or comes from an external source, use JSON or Avro. Reserve Pickle exclusively for short-lived, local Python-to-Python caching where you control both the writer and reader.
Pickle Arbitrary Code Execution
pickle.loads(untrusted_bytes) can execute ARBITRARY Python code.
A crafted pickle payload can import os; os.system('rm -rf /').
This is not a theoretical risk — it is trivially exploitable.
NEVER use pickle for: inter-system communication, user-uploaded files,
data from external APIs, or any source you don’t fully control.
Pickle is safe ONLY for Python-to-Python within a single trusted system
(e.g., caching intermediate pipeline results on the same machine).
Validate the source before any deserialization; use safer formats for untrusted data
If you must deserialize data from any external or user-controlled source, use JSON (json.loads) or Avro with a schema registry. Both formats are incapable of executing code during deserialization. Add a CI lint rule that flags any new pickle.load call that reads from a non-local path.
Compression Codec Comparison
Compression is orthogonal to format — most formats support multiple codecs. Choose based on the dominant constraint. For a deeper treatment of compression algorithms (snappy, gzip, zstd, lz4) and their trade-offs beyond serialization, see the dedicated compression note.
Codec
Compress Speed
Decompress Speed
Ratio
Best For
None
Instant
Instant
1x
Debugging, already-compressed data
Snappy
Very fast
Very fast
1.5-2x
Parquet default, streaming
LZ4
Very fast
Fastest
1.5-2x
Real-time processing
Zstd
Fast
Fast
2-4x
Best overall for storage
Gzip
Slow
Moderate
2-3x
Maximum compatibility
Compression Recommendations
New Parquet files → Zstd (best ratio, still fast, growing support)