PostgreSQL JSONB en 2026: cuándo una base relacional reemplaza a MongoDB
Introducción: de hstore a JSONB
La historia del almacenamiento de datos semiestructurados en PostgreSQL comienza mucho antes de que el movimiento NoSQL se convirtiera en tendencia. En 2006 apareció hstore, una extensión para almacenar pares clave–valor en formato de texto. Funcionaba, pero no soportaba anidamiento y tenía capacidades de indexación limitadas.
PostgreSQL 9.2 (2012) introdujo el tipo json, almacenamiento en texto con validación básica. El punto de inflexión llegó con PostgreSQL 9.4 (2014), que presentó jsonb: una representación binaria e indexable de JSON. Desde entonces, cada versión mayor ha añadido nuevas funcionalidades: jsonb_path_query (SQL/JSON Path en la versión 12), suscripciones y jsonb_set_lax en la 14, soporte mejorado de JSON Schema en la 16, y en PostgreSQL 17 (2024) y el próximo 18 (2025–2026), optimizaciones adicionales del planificador para consultas JSONB.
Hoy, en 2026, PostgreSQL JSONB es una herramienta madura que cubre la mayoría de los escenarios por los que los desarrolladores históricamente recurrían a MongoDB. Veamos cuándo está justificado y cuándo no.
JSONB vs JSON: diferencias clave
PostgreSQL ofrece dos tipos: json y jsonb. La diferencia es fundamental.
- json almacena los datos como texto, preservando el orden original de las claves y los espacios en blanco. La validación ocurre al insertar, pero el análisis se realiza en cada lectura. Escritura rápida, lectura lenta.
- jsonb almacena los datos en formato binario: las claves se deduplicán, se ordenan y se eliminan los duplicados. La escritura es ligeramente más lenta por el parseo, pero la lectura y la indexación son significativamente más rápidas.
La regla es sencilla: utiliza json solo cuando necesites preservar el formato original del documento de forma estricta (por ejemplo, para un registro de auditoría de la solicitud en crudo). En todos los demás casos, usa siempre jsonb.
Indexación de JSONB: GIN, GiST e índices parciales
La principal ventaja de jsonb sobre json es la posibilidad de una indexación eficiente. PostgreSQL ofrece varias estrategias.
Índice GIN: cobertura completa del documento
GIN (Generalized Inverted Index) es la opción estándar para JSONB. Indexa todas las claves y valores del documento, permitiendo usar los operadores @>, ?, ?|, ?&.
-- Crear tabla con campo JSONB
CREATE TABLE products (
id BIGSERIAL PRIMARY KEY,
data JSONB NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Índice GIN completo
CREATE INDEX idx_products_data_gin ON products USING GIN (data);
-- Consulta: encontrar todos los productos con la etiqueta 'electronics'
SELECT id, data->>'name'
FROM products
WHERE data @> '{"tags": ["electronics"]}';
-- Verificar el uso del índice
EXPLAIN (ANALYZE, BUFFERS)
SELECT id FROM products
WHERE data @> '{"category": "laptop"}';
GIN con jsonb_path_ops: más rápido pero más restringido
La clase de operador jsonb_path_ops crea un índice más compacto, solo para el operador @>, pero con menor tamaño y mayor velocidad de búsqueda.
CREATE INDEX idx_products_path ON products
USING GIN (data jsonb_path_ops);
-- Este índice es utilizado por el operador @>
SELECT * FROM products
WHERE data @> '{"specs": {"ram": 16}}';
Índice sobre un campo específico
Si filtras regularmente por un único campo, es más eficiente crear un índice B-tree sobre una expresión:
-- Índice sobre el campo price dentro de JSONB
CREATE INDEX idx_products_price
ON products ((data->>'price')::NUMERIC);
-- Ahora esta consulta utiliza el índice B-tree
SELECT * FROM products
WHERE (data->>'price')::NUMERIC > 1000;
Índice parcial
-- Índice solo para productos activos
CREATE INDEX idx_active_products
ON products USING GIN (data)
WHERE (data->>'status') = 'active';
Operadores y funciones de JSONB: guía práctica
PostgreSQL proporciona un amplio arsenal para trabajar con JSONB.
Operadores de acceso y verificación
-- Obtener valor por clave (devuelve jsonb)
SELECT data->'specs' FROM products;
-- Obtener valor como texto
SELECT data->>'name' FROM products;
-- Acceso por ruta
SELECT data#>'{specs,memory}' FROM products;
SELECT data#>>'{specs,memory}' FROM products; -- como texto
-- Verificar existencia de clave
SELECT * FROM products WHERE data ? 'discount';
-- ¿El documento contiene un subdocumento?
SELECT * FROM products
WHERE data @> '{"brand": "Dell"}';
-- JSON Path (PostgreSQL 12+)
SELECT jsonb_path_query(data, '$.specs.ram ? (@ > 8)')
FROM products;
Modificación de JSONB
-- Establecer un valor
UPDATE products
SET data = jsonb_set(data, '{specs,ram}', '32')
WHERE id = 1;
-- Eliminar una clave
UPDATE products
SET data = data - 'old_field'
WHERE id = 1;
-- Fusión de objetos (PostgreSQL 9.5+)
UPDATE products
SET data = data || '{"featured": true}'
WHERE id = 1;
-- jsonb_set_lax (PostgreSQL 14+): manejo de rutas nulas
UPDATE products
SET data = jsonb_set_lax(data, '{discount}', 'null', true, 'use_json_null')
WHERE id = 1;
Agregación y transformación
-- Expandir un array en filas
SELECT id, jsonb_array_elements(data->'tags') AS tag
FROM products;
-- Agrupar filas en un array JSONB
SELECT jsonb_agg(data->>'name') FROM products;
-- Construir un objeto
SELECT jsonb_object_agg(data->>'sku', data->>'price')
FROM products;
Escenarios reales de sustitución de MongoDB por PostgreSQL JSONB
Esquema flexible sin migraciones
Uno de los principales argumentos a favor de MongoDB es la posibilidad de almacenar documentos con estructuras diferentes. PostgreSQL JSONB resuelve esta tarea igual de bien. El patrón clásico es el esquema híbrido: campos fijos para los atributos críticos y una columna jsonb para datos extensibles.
CREATE TABLE events (
id BIGSERIAL PRIMARY KEY,
event_type TEXT NOT NULL,
user_id BIGINT REFERENCES users(id),
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
payload JSONB NOT NULL DEFAULT '{}'
);
-- Se pueden almacenar eventos con estructuras completamente diferentes
INSERT INTO events (event_type, user_id, payload) VALUES
('page_view', 1, '{"url": "/home", "referrer": "google.com"}'),
('purchase', 1, '{"order_id": 42, "amount": 299.99, "currency": "USD"}'),
('signup', 2, '{"plan": "pro", "trial": true, "source": "organic"}');
Documentos anidados
MongoDB es popular en escenarios con anidamiento multinivel. PostgreSQL lo gestiona de forma eficiente:
-- Encontrar usuarios cuya dirección de envío es Moscú
SELECT id, data->>'email'
FROM users
WHERE data @> '{"addresses": [{"city": "Moscow", "type": "shipping"}]}';
-- JSON Path para condiciones complejas
SELECT id
FROM orders
WHERE jsonb_path_exists(
data,
'$.items[*] ? (@.price > 100 && @.quantity > 1)'
);
Trabajo con arrays
-- Encontrar productos con las etiquetas 'sale' Y 'new'
SELECT * FROM products
WHERE data @> '{"tags": ["sale"]}'
AND data @> '{"tags": ["new"]}';
-- Longitud del array
SELECT id, jsonb_array_length(data->'images') AS image_count
FROM products
WHERE jsonb_array_length(data->'images') > 3;
Rendimiento: JSONB vs MongoDB en 2026
Los benchmarks objetivos de 2025–2026 (pruebas independientes con conjuntos de datos de 1 a 50 millones de documentos) muestran el siguiente panorama.
- Búsqueda puntual por clave con índice GIN: PostgreSQL JSONB — 0,3–0,8 ms, MongoDB — 0,2–0,6 ms. Paridad; MongoDB es ligeramente más rápido gracias a su arquitectura especializada.
- Agregaciones complejas con JOIN: PostgreSQL gana por un 40–60%; el planificador de consultas es significativamente más eficiente en consultas mixtas relacionales/JSON.
- Inserción masiva (bulk insert): MongoDB es un 20–30% más rápido sin índices; con índices GIN la diferencia se reduce al 10–15%.
- Escaneo completo de JSONB sin índice: resultados comparables; ambos son lentos, los índices son imprescindibles.
- Escenarios transaccionales con JSONB + datos relacionales: PostgreSQL es 2–3x más rápido al eliminar la sobrecarga de la comunicación entre servicios.
Conclusión clave: si tu aplicación combina datos documentales y relacionales, PostgreSQL JSONB ofrece ventajas no solo en rendimiento, sino también en simplicidad operativa. Un solo servicio en lugar de dos.
Integración con Laravel y Go
Laravel: trabajar con JSONB a través de Eloquent
Laravel soporta campos JSONB de forma nativa mediante el mecanismo de casting y operadores where.
// Migración
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->jsonb('data')->default('{}');
$table->timestamps();
});
// Modelo
class Product extends Model
{
protected $casts = [
'data' => 'array',
];
}
// Búsqueda por campo anidado
$laptops = Product::whereRaw(
"data @> ?",
[json_encode(['category' => 'laptop'])]
)->get();
// Actualizar una clave específica sin sobrescribir todo el objeto
DB::statement(
"UPDATE products SET data = jsonb_set(data, '{specs,ram}', ?) WHERE id = ?",
['32', $product->id]
);
// Índice GIN en la migración
DB::statement('CREATE INDEX idx_products_gin ON products USING GIN (data)');
Go: trabajar con JSONB a través de pgx
En Go, la librería pgx es el estándar de facto para trabajar con PostgreSQL. Los campos JSONB se escanean directamente en estructuras mediante la interfaz pgtype.
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5/pgxpool"
)
type ProductData struct {
Name string `json:"name"`
Price float64 `json:"price"`
Tags []string `json:"tags"`
Specs map[string]interface{} `json:"specs"`
}
func getProducts(pool *pgxpool.Pool) ([]ProductData, error) {
rows, err := pool.Query(
context.Background(),
`SELECT data FROM products
WHERE data @> $1
ORDER BY (data->>'price')::NUMERIC DESC
LIMIT 20`,
[]byte(`{"tags": ["sale"]}`),
)
if err != nil {
return nil, err
}
defer rows.Close()
var results []ProductData
for rows.Next() {
var raw []byte
if err := rows.Scan(&raw); err != nil {
return nil, err
}
var pd ProductData
if err := json.Unmarshal(raw, &pd); err != nil {
return nil, err
}
results = append(results, pd)
}
return results, nil
}
func updateSpec(pool *pgxpool.Pool, id int64, ram int) error {
_, err := pool.Exec(
context.Background(),
`UPDATE products
SET data = jsonb_set(data, '{specs,ram}', $1::jsonb)
WHERE id = $2`,
fmt.Sprintf("%d", ram),
id,
)
return err
}
Limitaciones de JSONB y cuándo MongoDB sigue siendo mejor
Un análisis honesto exige reconocer las limitaciones de PostgreSQL JSONB.
- Escalado horizontal. PostgreSQL escala mejor verticalmente, pero el sharding horizontal es más complejo. MongoDB Atlas o MongoDB con sharding nativo gana cuando los volúmenes superan decenas de terabytes.
- Flexibilidad de esquema a nivel de aplicación. Si tu equipo tiene experiencia con el enfoque ODM (Mongoose, Doctrine ODM), el cambio de paradigma requiere esfuerzo.
- Change Streams. MongoDB proporciona Change Streams nativos para arquitecturas reactivas. PostgreSQL LISTEN/NOTIFY + replicación lógica cubre el 80% de los casos, pero requiere configuración adicional.
- Documentos JSONB muy grandes. Documentos de más de 1 MB en PostgreSQL se almacenan a través de TOAST, lo que genera sobrecarga. MongoDB está optimizado para documentos grandes (hasta 16 MB).
- Ausencia de API nativa de base de datos documental. Si el producto requiere el MongoDB Wire Protocol (por ejemplo, compatibilidad con clientes existentes), PostgreSQL no ayudará sin proxies adicionales.
Recomendaciones prácticas para diseñar esquemas con JSONB
- El esquema híbrido es el estándar de oro. Extrae a columnas independientes los campos por los que filtras o haces joins con frecuencia:
user_id,status,created_at. El resto, endata JSONB. - Un índice GIN completo vs. múltiples expresiones. El índice GIN completo es conveniente pero pesado. Para aplicaciones de alta carga, crea índices sobre expresiones específicas:
(data->>'status'),(data->>'user_id')::BIGINT. - Normaliza los campos "calientes". Si descubres que filtras por
data->>'email'en el 80% de las consultas, extrae el email a una columna separada con un índice B-tree normal. - Versionado de documentos. Añade un campo
schema_version INTjunto adata JSONB. Esto permite migrar la estructura de los documentos de forma segura sin tiempo de inactividad. - Revisa los planes de consulta regularmente.
EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON)es tu mejor herramienta. El índice GIN puede no utilizarse si las estadísticas están desactualizadas: ejecutaANALYZE productsdespués de inserciones masivas. - Usa JSON Schema Validation (PostgreSQL 16+). La función integrada
jsonb_matches_schemapermite validar documentos a nivel de base de datos sin capas adicionales.
-- Ejemplo de esquema híbrido
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
status TEXT NOT NULL DEFAULT 'pending',
total NUMERIC(10,2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metadata JSONB NOT NULL DEFAULT '{}'
);
CREATE INDEX idx_orders_user ON orders (user_id);
CREATE INDEX idx_orders_status ON orders (status);
CREATE INDEX idx_orders_meta ON orders USING GIN (metadata jsonb_path_ops);
-- La consulta utiliza eficientemente ambos índices
SELECT id, total, metadata->>'source'
FROM orders
WHERE user_id = 42
AND status = 'completed'
AND metadata @> '{"promo": true}';
Conclusión
PostgreSQL JSONB en 2026 no es un compromiso, sino una solución completa para la mayoría de los casos en los que históricamente los desarrolladores elegían MongoDB. Los esquemas flexibles, los documentos anidados, los arrays, la potente indexación mediante GIN, el amplio conjunto de operadores y SQL/JSON Path hacen que PostgreSQL sea competitivo en el nicho documental.
La principal ventaja es una única base de datos para datos relacionales y documentales. Esto reduce la complejidad operativa, simplifica las transacciones y permite aprovechar todo el poder de SQL para consultas analíticas complejas.
Dicho esto, MongoDB sigue siendo la mejor opción para escenarios con sharding horizontal nativo, Change Streams y documentos muy grandes. Si tu sistema ya funciona en PostgreSQL, no te apresures a introducir MongoDB "por flexibilidad". Lo más probable es que JSONB ya resuelva tu problema.
La integración a través de Laravel Eloquent y Go pgx es madura y predecible. Comienza con un esquema híbrido, crea los índices correctos, revisa los planes de consulta con regularidad, y las capacidades NoSQL de PostgreSQL cumplirán con las expectativas.
Tecnologías
Etiquetas
Ruslan Ismailov
Desarrollador Senior Web / Backend. Desarrollador senior web/backend con 9 años de experiencia. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservicios, CI/CD. Más sobre mí →