« All posts

PostgreSQL JSONB vs MongoDB BSON: The Real Architectural Tradeoffs

BSON and JSONB reveal deep architectural differences between MongoDB and PostgreSQL, shaping indexing, update cost, and read performance trade-offs.

Choosing between PostgreSQL and MongoDB is usually framed as SQL versus documents, transactions versus flexibility, or team familiarity — rarely as a question of on-disk byte format. Yet BSON and JSONB encode fundamentally different engineering philosophies. BSON is a binary echo of MongoDB's runtime: a self-describing, length-prefixed wire format built to be scanned and patched in place. JSONB is a parse tree frozen inside a Postgres tuple, optimized for fast reads but bound to MVCC and TOAST at write time.

BSON's ordered, type-tagged layout lets the server skip entire subtrees via length prefixes and patch same-size fields with just a few bytes instead of rewriting the whole document. It also preserves richer types than plain JSON — 32/64-bit integers, decimal128, ObjectId, UTC datetimes, binary blobs, UUIDs — at the cost of storing raw field names as repeated cstrings in every document, with no shared dictionary.

JSONB takes the opposite bet: it normalizes and sorts keys, deduplicates them, and builds an offset structure that makes key lookup logarithmic rather than linear. The tradeoff is that JSONB can never be patched in place — updating a single boolean inside a 4KB document forces a full rewrite, a new heap tuple, WAL traffic, TOAST churn, and index invalidation. JSONB also drops key order and duplicate keys, represents numbers using Postgres's arbitrary-precision numeric type rather than distinct int tags, and lacks a native binary type, pushing teams toward base64 encoding or separate bytea columns.

For engineers, the takeaway is that this is not a religious debate but a set of measurable tradeoffs: JSONB rewards read-heavy, rarely-updated documents, while BSON's in-place mutation model suits workloads with frequent partial updates. The storage format — not the query language — ultimately decides read latency, write amplification, and index footprint.

» SourceHashnode #2