Bases de datos

PostgreSQL JSONB en 2026: cuándo una base relacional reemplaza a MongoDB

Ruslan Ismailov Publicado 12 min de lectura
P

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

  1. 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, en data JSONB.
  2. 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.
  3. 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.
  4. Versionado de documentos. Añade un campo schema_version INT junto a data JSONB. Esto permite migrar la estructura de los documentos de forma segura sin tiempo de inactividad.
  5. 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: ejecuta ANALYZE products después de inserciones masivas.
  6. Usa JSON Schema Validation (PostgreSQL 16+). La función integrada jsonb_matches_schema permite 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í →