#JSONB
Are PostgreSQL TOAST compression thresholds a good fit for your JSON, JSONB, or BSON document sizes?
dev.to/franckpachot...
PostgreSQL document compression: TOAST thresholds, storage policies, and page size
A common way to store documents in PostgreSQL is a table with an ID and a JSONB column. The...
dev.to
November 2, 2026 at 9:29 PM
The 7,708→150-byte case suggests a useful control: a heap scan reading only a small scalar column. Both JSONB values are inline, so this exposes page-density/cache effects without JSONB decompression. An index-only scan would hide that difference.
November 3, 2026 at 6:48 PM
Cloud Spanner update on October 30, 2026 https://docs.cloud.google.com/spanner/docs/release-notes#October_30_2026 #googlecloud Spanner supports equality and ordering comparisons on JSONB and JSON values
October 30, 2026 at 6:20 PM
Spanner can implicitly coerces the non-JSON value to JSON for comparison. Additionally, you can use search indexes to accelerate equality comparisons such as = or IN on JSONB and JSON columns and subpaths
October 30, 2026 at 6:20 PM
ecto_spectral 0.2.0 [updated]

An Ecto.ParameterizedType that stores Spectral-typed values in jsonb columns.

Licenses: Apache-2.0
October 31, 2026 at 8:00 PM
Postgres excels at scale not just through raw performance, but by providing primitives like `jsonb` and extensibility that adapt to evolving schema needs. #RubyOnRails #Rails
November 3, 2026 at 11:44 PM
Same 2M rows in PostgreSQL 18: the JSONB table was 422 MB against 158 MB for columns, and the planner guessed 10,000 rows for doc->>'status' = 'pending' when 500,624 matched.

https://importstatic.com/databases/postgres-jsonb-vs-columns
JSONB vs columns in PostgreSQL, measured on 2 million rows
PostgreSQL JSONB vs typed columns on 2 million rows: table size, aggregate speed, planner row estimates, HOT updates, and the queries a GIN index speeds up.
importstatic.com
October 29, 2026 at 4:11 PM
For more information, see JSON comparison and sorting, JSON comparison coercion, CAST AS JSON, JSON search indexes, and Working with JSONB.
October 30, 2026 at 6:20 PM
On 2M small JSONB documents in PostgreSQL 18, a GIN index with jsonb_path_ops was 159 MB and built in 15 s; the default jsonb_ops was 262 MB and took 28.5 s. If your queries only use @>, @? and @@, jsonb_path_ops covers them.
October 30, 2026 at 6:28 AM
1/ The `SELECT *` that seemed harmless in development becomes a bandwidth problem when a JSONB column that used to be small grows to megabytes in production.
October 28, 2026 at 11:00 AM
CRM'de özel alan (custom field) tasarlamak: EAV mı JSONB mi, alan tanım kayıt defteri, doğrulama ve indeksleme
Her CRM projesinde bir noktada şu istek gelir: "Lead kartına bir alan daha ekleyebilir miyiz?" Ben Koray Yalçın; lead yönetimi, CRM mimarisi, dijital pazarlama ve yapay zekâ destekli otomasyon projelerinde, ağırlıklı olarak gayrimenkul ve PropTech tarafında çalışıyorum. Bu istek ilk geldiğinde tabloya bir sütun eklemek yeterli görünür. Beşinci istekte, üç farklı projenin üç farklı alan listesi istediği ortaya çıkar. Onuncu istekte ise pazarlama ekibinin bir alanın adını değiştirdiği ve bu yüzden bir gecede iki entegrasyonun sessizce boş veri göndermeye başladığı bir sabah yaşanır. Konut satışında bu ihtiyaç çok somuttur. Bir proje için "ilgilenilen oda tipi", "bütçe aralığı", "kat tercihi" ve "deniz manzarası şart mı" alanları önemlidir. Başka bir proje ticari ünite sattığı için "metrekare ihtiyacı" ve "kullanım amacı" sorulur. Yurt dışından gelen alıcılar için "tercih edilen para birimi" ve "görüşme dili" eklenir. Hepsi aynı lead tablosunda yaşamak zorundadır ve hepsi raporlarda, otomasyonlarda ve reklam platformlarına giden geri bildirimlerde kullanılır. Bu yazıda özel alanları (custom field) geliştirici gözüyle ele alıyorum: veriyi nerede tutarsınız, alanın tanımını nasıl yönetirsiniz, değerleri nasıl doğrularsınız, sorguları nasıl hızlı tutarsınız ve bir alanın yaşam döngüsünü (ekleme, yeniden adlandırma, tip değiştirme, kaldırma) nasıl güvenli hâle getirirsiniz. ## Üç depolama seçeneği ve gerçek maliyetleri Özel alan değerlerini saklamanın üç yaygın yolu var. Hangisinin "doğru" olduğu, alan sayısına, sorgu desenine ve kaç farklı müşterinin (ya da projenin) aynı şemayı paylaştığına bağlı. **Gerçek sütunlar.** Her yeni alan için `ALTER TABLE` çalıştırırsınız. Tip güvenliği, indeksleme ve raporlama en iyi bu yöntemde olur. Ama alan eklemek bir şema değişikliği, yani bir dağıtım işi hâline gelir. Çok kiracılı (multi-tenant) bir yapıda her kiracının alanlarını aynı tabloya sütun olarak eklemek ise kısa sürede yüzlerce seyrek sütun üretir. **EAV (Entity-Attribute-Value).** Her değer ayrı bir satırdır: hangi kayıt, hangi alan, hangi değer. Esnektir ve alan eklemek yalnızca bir satır eklemektir. Bedeli sorgu tarafında ödenir. "Bütçesi 150.000 sterlinin üzerinde olan ve 2+1 arayan lead'ler" gibi basit bir filtre, iki ayrı birleştirme (join) gerektirir. On alanlı bir dışa aktarma on birleştirme demektir. Değerler genellikle metin olarak tutulduğu için tip dönüşüm hataları da raporlara sızar. **JSONB sütunu.** Kaydın üzerinde tek bir `custom` sütunu vardır ve tüm özel değerler orada bir nesne olarak durur. PostgreSQL'in JSONB tipi bu iş için oldukça olgundur: GIN indeksi, ifade indeksi ve içerme operatörleri sağlar. Esneklik EAV'ye yakındır, sorgu maliyeti ise çok daha düşüktür. Zayıf yanı, veritabanının şemayı bilmemesidir; doğrulamayı sizin yapmanız gerekir. Benim sahada vardığım denge şu: Herkesin kullandığı ve raporların omurgasını oluşturan alanlar (kaynak, aşama, sahip, proje, oluşturulma zamanı) gerçek sütundur. Projeye ya da ekibe göre değişen alanlar JSONB'dedir. Bir JSONB alanı zamanla çok kritik hâle gelirse (her raporda, her filtrede kullanılıyorsa) kontrollü bir göçle gerçek sütuna terfi ettirilir. EAV'yi ise yalnızca değer başına ayrı meta veri (kim, ne zaman, hangi kaynaktan girdi) tutmanın şart olduğu durumlarda düşünüyorum; o ihtiyacı da çoğunlukla denetim kaydı karşılıyor. ## Alan tanımı: Veriden önce sözleşme JSONB'nin şemasız olması, şemanın hiç olmaması anlamına gelmemeli. Şema veritabanında değil, bir alan tanım kayıt defterinde (field registry) yaşar. Her özel alan burada bir satırdır ve bu satır, alanın sözleşmesidir. CREATE TABLE custom_field_def ( id bigserial PRIMARY KEY, tenant_id bigint NOT NULL, entity text NOT NULL CHECK (entity IN ('lead','deal','contact')), api_key text NOT NULL, -- değişmez, entegrasyonların kullandığı ad label text NOT NULL, -- ekranda görünen, değiştirilebilir ad data_type text NOT NULL CHECK (data_type IN ('text','number','money','date','bool','select','multi_select')), config jsonb NOT NULL DEFAULT '{}', -- min/max, para birimi, seçenekler is_required boolean NOT NULL DEFAULT false, is_pii boolean NOT NULL DEFAULT false, status text NOT NULL DEFAULT 'active' CHECK (status IN ('active','deprecated','archived')), version int NOT NULL DEFAULT 1, created_at timestamptz NOT NULL DEFAULT now(), UNIQUE (tenant_id, entity, api_key) ); Buradaki en önemli karar `api_key` ile `label` ayrımıdır. Ekranda "Bütçe" yazan alanın etiketi yarın "Tahmini bütçe (GBP)" olabilir; bu tamamen serbesttir. Ama `budget_gbp` anahtarı bir kez oluştuktan sonra değişmez. JSONB içindeki anahtar, webhook yükleri, reklam platformuna giden eşleme ve otomasyon kuralları hep bu anahtarı kullanır. Yukarıda anlattığım "bir gecede iki entegrasyon boş veri göndermeye başladı" vakası, tam olarak etiketin anahtar yerine kullanılmasından kaynaklanıyordu. `is_pii` bayrağı da baştan eklenmeli. Bir özel alanın kişisel veri içerip içermediği, maskeleme, dışa aktarma ve saklama süresi kararlarını doğrudan etkiler. Pazarlama ekibinin "ikinci telefon numarası" diye bir alan açması çok olağandır ve bu alan, standart telefon alanıyla aynı korumayı hak eder. ## Seçenek listeleri: Etiket değil, kararlı anahtar Açılır liste (select) alanları en çok sorun çıkaran tiptir. "2+1" seçeneğinin etiketi bir gün "2+1 (Daire)" olarak düzeltilir ve geçmiş veride hem "2+1" hem "2+1 (Daire)" görünmeye başlar. Bunu önlemenin yolu, kayda etiketi değil seçeneğin kalıcı anahtarını yazmaktır. { "options": [ { "key": "r_1_1", "label": "1+1", "status": "active" }, { "key": "r_2_1", "label": "2+1 (Daire)", "status": "active" }, { "key": "r_3_1", "label": "3+1", "status": "active" }, { "key": "villa", "label": "Villa", "status": "deprecated" } ] } Bir seçenek kaldırılmak istendiğinde silinmez, `deprecated` yapılır. Yeni kayıtlarda seçilemez ama eski kayıtlarda okunabilir kalır ve raporlar geçmişi bozmadan çalışır. Seçenek sırası ve etiketi serbestçe değişebilir; anahtar hiçbir zaman değişmez. ## Yazma yolunda doğrulama Veritabanı JSONB içine ne yazıldığını denetlemediği için doğrulama, tüm yazma yollarının geçtiği tek bir katmanda yapılmalı: arayüzden gelen düzenleme, form entegrasyonu, toplu içe aktarma ve otomasyon hepsi aynı fonksiyonu çağırır. from decimal import Decimal, InvalidOperation from datetime import date class FieldError(Exception): def __init__(self, api_key, reason): super().__init__(f"{api_key}: {reason}") self.api_key, self.reason = api_key, reason def coerce(defn, raw): t, cfg = defn["data_type"], defn["config"] if raw is None or raw == "": if defn["is_required"]: raise FieldError(defn["api_key"], "zorunlu alan boş") return None if t == "number" or t == "money": try: v = Decimal(str(raw).replace(" ", "").replace(",", ".")) except InvalidOperation: raise FieldError(defn["api_key"], "sayı değil") if "min" in cfg and v < Decimal(str(cfg["min"])): raise FieldError(defn["api_key"], "alt sınırın altında") return {"amount": str(v), "currency": cfg.get("currency", "GBP")} if t == "money" else str(v) if t == "date": return date.fromisoformat(str(raw)).isoformat() if t == "bool": return str(raw).lower() in ("1", "true", "evet", "yes") if t in ("select", "multi_select"): allowed = {o["key"] for o in cfg["options"] if o["status"] == "active"} vals = raw if isinstance(raw, list) else [raw] bad = [x for x in vals if x not in allowed] if bad: raise FieldError(defn["api_key"], f"geçersiz seçenek: {bad}") return vals if t == "multi_select" else vals[0] return str(raw)[: cfg.get("max_length", 500)] def validate_custom(defs, payload): out, errors = {}, [] by_key = {d["api_key"]: d for d in defs if d["status"] != "archived"} for k, raw in payload.items(): d = by_key.get(k) if d is None: errors.append(FieldError(k, "tanımsız alan")) continue try: out[k] = coerce(d, raw) except FieldError as e: errors.append(e) return out, errors Birkaç ayrıntı önemli. Sayılar JSON'a ondalık metin olarak yazılır, kayan nokta olarak değil; para alanlarında yuvarlama hatası raporlarda kuruş kuruş birikir. Para tipi her zaman para birimiyle birlikte saklanır, çünkü aynı projede sterlin ve euro bütçesi olan alıcılar olabilir. Tanımsız anahtar sessizce yutulmaz, hata olarak döner; aksi hâlde yazım hatası yapılmış bir form alanı aylarca boş veri biriktirir. Toplu içe aktarmada ise hata davranışı farklı olmalı. Tek bir kötü hücre yüzünden binlerce satırlık bir dosyayı reddetmek yerine, geçerli alanlar yazılır, hatalı alanlar ayrı bir hata raporunda satır numarasıyla listelenir. Bunu baştan karar olarak yazmak, içe aktarma ekranındaki tartışmaları azaltır. ## Sorgu ve indeksleme JSONB'nin asıl gücü indekslemede ortaya çıkar. Genel amaçlı filtreler için tek bir GIN indeksi çoğu eşitlik sorgusunu karşılar: CREATE INDEX lead_custom_gin ON lead USING gin (custom jsonb_path_ops); -- "2+1 arayan lead'ler" (içerme operatörü GIN indeksini kullanır) SELECT id FROM lead WHERE tenant_id = 42 AND custom @> '{"room_type": "r_2_1"}'; Aralık sorguları (bütçe, tarih) GIN ile verimli çalışmaz. Sık kullanılan bir sayısal alan için ifade indeksi tanımlarım: CREATE INDEX lead_budget_idx ON lead (tenant_id, ((custom -> 'budget_gbp' ->> 'amount')::numeric)) WHERE custom ? 'budget_gbp'; SELECT id FROM lead WHERE tenant_id = 42 AND custom ? 'budget_gbp' AND (custom -> 'budget_gbp' ->> 'amount')::numeric >= 150000; Burada sorgudaki ifade, indeks tanımındakiyle birebir aynı olmalı; aksi hâlde planlayıcı indeksi kullanmaz. Bu yüzden bu ifadeleri elle yazmak yerine, alan tanımından sorgu parçası üreten küçük bir yardımcı fonksiyon kullanmak daha güvenlidir. Hangi alanlara ifade indeksi açılacağı da tahminle değil, gerçek filtre kullanım istatistiğiyle belirlenmeli. Her alan için indeks açmak yazma maliyetini gereksiz artırır. Raporlama tarafında JSONB'yi doğrudan analitik araca açmak yerine, alan tanımından otomatik üretilen düz bir görünüm (view) ya da gece güncellenen bir tablo vermeyi tercih ediyorum. Böylece veri ekibi `custom ->> 'room_type'` yazmak zorunda kalmaz, seçenek anahtarları da etiketlere çevrilmiş olarak gelir. ## Alanın yaşam döngüsü Özel alanlar eklenir, değişir ve eskir. Bu süreç kontrolsüz bırakılırsa tablo, kimsenin ne işe yaradığını hatırlamadığı alanlarla dolar. **Yeniden adlandırma** yalnızca etikete dokunur, anahtar sabit kalır. Bu yüzden sıfır risklidir ve yönetici ekranından serbestçe yapılabilir. **Tip değiştirme** ise bir göçtür. Metin olarak açılmış "bütçe" alanını para tipine çevirmek, mevcut değerlerin bir kısmının ("150-200 bin arası", "görüşülecek") dönüştürülemeyeceği anlamına gelir. Ben bunu yerinde yapmıyorum: yeni anahtarla yeni bir alan açılır, dönüştürülebilen değerler bir arka plan işiyle taşınır, dönüştürülemeyenler bir inceleme listesine düşer, eski alan `deprecated` olur. Tanım tablosundaki `version` alanı, hangi kayıtların hangi sürüme göre doğrulandığını izlemeyi kolaylaştırır. **Kaldırma** iki aşamalıdır. Önce alan `deprecated` olur: arayüzde gizlenir, yeni yazmalar reddedilir, ama veri durur. Bu aşamada alanı hâlâ kullanan otomasyon, entegrasyon ya da rapor varsa loglarda görünür. Belirlenen bekleme süresi sonunda alan `archived` olur ve değerler, kişisel veri içeriyorsa saklama politikasına göre temizlenir. Kişisel veri içeren bir alanın verisini "belki lazım olur" diye süresiz tutmak, KVKK açısından savunulması zor bir durumdur. ## Entegrasyonlar ve otomasyonlar Özel alanlar yalnızca ekranda yaşamaz. Reklam formlarından gelen cevaplar bu alanlara yazılır, otomasyon kuralları bu alanlara göre dallanır, satış kapandığında bazı değerler reklam platformuna geri bildirilir. Bu bağlantıların hepsi `api_key` üzerinden kurulmalı ve hangi entegrasyonun hangi alanı okuduğu ya da yazdığı bir yerde kayıtlı olmalı. Ben bunun için alan tanımına bağlı basit bir "kullanım" tablosu tutuyorum: hangi form eşlemesi, hangi iş akışı, hangi dışa aktarma bu alana dokunuyor. Bir alan `deprecated` yapılmak istendiğinde yönetici ekranı bu listeyi gösterir. "Bu alanı kaldırırsanız şu iki iş akışı ve bir form eşlemesi etkilenecek" uyarısı, sessiz kırılmaların büyük kısmını engeller. Kavramın kısa tanımını ve gayrimenkul satış operasyonundaki yerini kendi sitemdeki sözlükte özetledim: Custom field nedir? Bu yazı ise o tanımın veritabanı ve uygulama tarafındaki notları. ## SSS ### Hazır bir CRM kullanıyorsam bu tasarımın ne kadarı bana kalır? Depolama ve indeksleme kararları CRM'in kendisine aittir. Size kalan kısım alan adlandırma disiplini (değişmez API adı ile değişebilir etiket ayrımı), seçenek listelerinin yönetimi, kişisel veri alanlarının işaretlenmesi ve entegrasyonların hangi alanlara bağlı olduğunun kaydıdır. Bunlar hazır CRM'lerde de aynı ölçüde önemlidir. ### Kaç özel alan "çok fazla" sayılır? Kesin bir sayı yok, ama kullanım istatistiği iyi bir göstergedir. Son birkaç ayda kayıtların çok küçük bir kısmında doldurulmuş ya da hiçbir rapor ve otomasyonda kullanılmayan alanlar, kaldırma adayıdır. Kiracı başına bir üst sınır koymak da kontrolsüz büyümeyi frenler. ### JSONB sütunu büyüdükçe performans sorunu çıkar mı? Değerler küçük olduğu sürece genellikle hayır. Sorun, özel alanlara uzun metinler ya da dosya içerikleri yazılmaya başlandığında görülür; büyük JSONB değerleri satırın dışına taşınır ve her güncelleme tüm nesneyi yeniden yazar. Uzun notlar ve ekler ayrı tablolarda tutulmalı. ### Özel alan değişikliklerini denetim kaydına nasıl yansıtırım? JSONB nesnesini bütün olarak değil, anahtar bazında farkını alarak kaydetmek gerekir. Böylece "bütçe alanı kim tarafından, hangi değerden hangi değere değiştirildi" sorusu tek bir kayıtla cevaplanır. Kişisel veri işaretli alanlarda farkın kendisi de maskelenmeli. ### Bir alanı ne zaman gerçek sütuna taşımalıyım? Alan neredeyse her kayıtta doluysa, çoğu raporda ve filtrede kullanılıyorsa ve tipi artık değişmeyecek kadar oturmuşsa, gerçek sütun daha iyi bir yuvadır. Taşıma sırasında bir süre iki yere birden yazmak ve okuma tarafını sonra çevirmek, kesintisiz geçişi sağlar. Özel alanlar, CRM'in iş birimlerine "kendi verinizi tanımlayın" dediği yerdir. Bu esnekliği güvenli kılan şey depolama tekniğinden çok sözleşmedir: değişmez anahtarlar, açık tipler, tek bir doğrulama yolu, ölçüme dayalı indeksler ve kontrollü bir yaşam döngüsü. Bunlar baştan kurulduğunda, "bir alan daha ekleyebilir miyiz?" sorusu bir risk değil, sıradan bir iş hâline gelir.
dev.to
October 29, 2026 at 8:23 AM
This is how I find out about pgrust.com
October 25, 2026 at 5:13 PM
Add one table tonight: outbox(id uuid, payload jsonb, created_at timestamptz, sent_at timestamptz). Point your relay at unsent rows. That is the whole fix.

#AgentEngineering #Kafka
October 23, 2026 at 2:05 PM
📦 martin-georgiev/postgresql-for-doctrine v4.10.0

Extends Doctrine with native PostgreSQL support for arrays, JSONB, ranges, PostGIS geometries, text search, ltree, uuid, and 100+ PostgreSQL-specific functions.

🔗 https://github.com/martin-georgiev/postgresql-for-doctrine
October 23, 2026 at 12:30 PM
Chris van Eijk: hstore vs jsonb performance in PostgreSQL, measured

https://postgr.es/p/9zu

#postgresql
October 21, 2026 at 11:30 PM
Stop seeding local staging databases with unmasked production pg_dump files. PgDumpShield Studio anonymizes PostgreSQL COPY ... FROM stdin rows and JSONB PII 100% in local browser RAM while keeping UNIQUE indexes valid:
https://xvibelabs.com/tools/082-pgdump-sql-copy/ #infosec #webdev
PgDumpShield Studio | Local pg_dump SQL COPY PII Scrubber
xvibelabs.com
October 19, 2026 at 6:29 PM
i reject your false dichotomy and choose a third way: event sourcing built with Postgres index + jsonb. But! ☝️ there's a read replica for CQRS
October 14, 2026 at 12:21 AM
The replay path found two bugs. 19 games were missing from the verifier's imports, so finishing one returned a 501. And a jsonb double-encode stored every solve time as null.

A test now fails the build if a game is missing.
October 10, 2026 at 2:11 PM
A post is a row with a jsonb puzzle column the backend never reads. Usually it's just a seed.

Each game is one JS bundle and the database stores its path. Adding a game adds a row. No app update, no store review.
October 10, 2026 at 2:11 PM
1M small documents, json vs jsonb, PostgreSQL 18:

Table: 149 MB vs 167 MB
Insert: 12.3 s vs 14.4 s
WHERE doc->>'plan' = 'pro', full scan: 627 ms vs 201 ms

jsonb is bigger and slower to write, and 3x faster to read a key. json is stored as text and parsed again on every access.
October 8, 2026 at 11:16 PM
The pgvector vs Pinecone debate is a trap. AI agent memory is about failure boundaries.

Dumping massive LLM outputs into a Postgres JSONB column will destroy your DB.

SRE Blueprint:
• Redis for hot state
• pgvector for ACID vectors

www.servermo.com/howto/ai-age...
October 9, 2026 at 6:22 AM
📦 amarenkov/laravel-mutable-content v0.3.1

Flexible content models for Laravel: dynamic JSONB fields with a type system, change log and lists of values (LOV).

🔗 https://github.com/amarenkov/laravel-mutable-content
October 8, 2026 at 3:24 PM
PostgreSQL JSONB: JSON 데이터 조회, 수정 및 인덱싱

PostgreSQL의 JSONB 데이터를 실제로 다룰 때 필요한 과정을 하나의 지원 티켓 예제로 정리했습니다. 중첩된 값을 조회하는 ->와 ->>의 차이, @>를 이용한 포함 검색, jsonb_set()으로 문서 전체를 교체하지 않고 특정 값만 수정하는 방법을 다룹니다. 40,004개의 레코드로 GIN 인덱스를 테
PostgreSQL JSONB: JSON 데이터 조회, 수정 및 인덱싱
PostgreSQL의 JSONB 데이터를 실제로 다룰 때 필요한 과정을 하나의 지원 티켓 예제로 정리했습니다. 중첩된 값을 조회하는 ->와 ->>의 차이, @>를 이용한 포함 검색, jsonb_set()으로 문서 전체를 교체하지 않고 특정 값만 수정하는 방법을 다룹니다. 40,004개의 레코드로 GIN 인덱스를 테
news.hada.io
October 1, 2026 at 11:00 AM
Lo que estoy haciendo en el trabajo en este momento me tiene entretenido.
Estoy migrando una tabla a un campo de tipo JSONB en otra tabla que ya existía, estaban relacionadas por una FK.
Postgres es una BD tan completa que permite tener lo mejor de los 2 mundos en un mismo motor: sql y "no-sql".
September 30, 2026 at 12:28 AM