{
  "schema_version": 1,
  "site": "https://boringsql.com",
  "last_updated": "2026-08-31T23:36:00",

  "posts": [
    {
      "slug": "read-your-own-writes",
      "title": "Read your own writes, off the primary","published": "2026-08-31","updated": null,
      "description": "WAIT FOR in PostgreSQL 19 lets a reader block until the replica has replayed a specific LSN. Measured against sticky routing, sleeps, and synchronous commit, on a replica that misses the write 99% of the time.",
      "url": "https://boringsql.com/posts/read-your-own-writes/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 17,
      "tags": ["postgresql", "replication"]
    },
    {
      "slug": "google-alloydb",
      "title": "The curious case of Google's AlloyDB","published": "2026-08-15","updated": null,
      "description": "AlloyDB offers Postgres wire compatibility with a divergent storage engine. A deep-dive review into HTAP claims, ScaNN limits, adaptive autovacuum, and costs",
      "url": "https://boringsql.com/posts/google-alloydb/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 35,
      "tags": ["postgresql", "deep-dive", "postgres-forks", "ai"]
    },
    {
      "slug": "distinct-in-your-count",
      "title": "The DISTINCT in your COUNT","published": "2026-08-05","updated": null,
      "description": "COUNT(DISTINCT) has no combine function, so PostgreSQL can't merge per-worker results: the whole statement drops to one core and a disk-spilling sort. Push the distinctness into a GROUP BY instead and parallel query comes back.",
      "url": "https://boringsql.com/posts/distinct-in-your-count/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 8,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "mvcc-bad-bad",
      "title": "PostgreSQL's MVCC is bad. So is everyone else's.","published": "2026-07-27","updated": null,
      "description": "The critics are right: PostgreSQL's MVCC bloats tables and amplifies writes. Oracle, InnoDB, SQL Server, MongoDB, LSM stores, and etcd answer the same four design questions differently, and each pays for it somewhere else.",
      "url": "https://boringsql.com/posts/mvcc-bad-bad/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 25,
      "tags": ["internals"]
    },
    {
      "slug": "regresql20",
      "title": "The tests passed. The plan didn't.","published": "2026-07-12","updated": null,
      "description": "The rows didn't change; the plan did. RegreSQL 2.0 catches queries that return the right rows the wrong way: plan drift, cardinality error, and regressions verified against production's real PostgreSQL statistics.",
      "url": "https://boringsql.com/posts/regresql20/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 9,
      "tags": ["postgresql", "sql", "boringsql", "tools"]
    },
    {
      "slug": "vacuum-at-the-page-level",
      "title": "VACUUM at the Page Level","published": "2026-07-05","updated": null,
      "description": "Watch a VACUUM cycle with pageinspect, pg_freespace, and pg_visibility: how a dead tuple loses its storage, then its index entry, then its slot, and how the freed space gets reused.",
      "url": "https://boringsql.com/posts/vacuum-at-the-page-level/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 7,
      "reading_min": 16,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "same-rows-different-sum",
      "title": "Same rows, different SUM","published": "2026-06-28","updated": null,
      "description": "Same query, same rows, different SUM totals in PostgreSQL. The culprit is parallel aggregation over double precision floats, not a bug. Here is the mechanism and the fix.",
      "url": "https://boringsql.com/posts/same-rows-different-sum/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 12,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "not-in-null",
      "title": "The NULL in your NOT IN","published": "2026-06-14","updated": null,
      "description": "A single NULL turns NOT IN into a query that silently returns zero rows, and the planner couldn't optimize around it for as long as anyone can remember, until a PostgreSQL 19 patch.",
      "url": "https://boringsql.com/posts/not-in-null/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 13,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "pg-stat-statements-part-2",
      "title": "pg_stat_statements: everything it can't","published": "2026-06-03","updated": null,
      "description": "Where pg_stat_statements stops short: the entries it silently evicts, the query text that can vanish, the plans and replicas it never sees, and the query store Postgres still doesn't ship.",
      "url": "https://boringsql.com/posts/pg-stat-statements-part-2/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 15,
      "tags": ["postgresql", "deep-dive"]
    },
    {
      "slug": "pg-stat-statements",
      "title": "pg_stat_statements: everything it tells you","published": "2026-06-02","updated": null,
      "description": "What pg_stat_statements records and what it quietly drops: the queryid jumble that splits one query into many rows, the frozen first-seen query text, and averages that hide your p99.",
      "url": "https://boringsql.com/posts/pg-stat-statements/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 17,
      "tags": ["postgresql", "deep-dive"]
    },
    {
      "slug": "postgresql-toast",
      "title": "TOAST: Where PostgreSQL hides big values","published": "2026-05-24","updated": null,
      "description": "A heap tuple cannot cross a page boundary, yet a single jsonb column can hold a megabyte. TOAST is the trick. A pageinspect tour of pointers and chunks.",
      "url": "https://boringsql.com/posts/postgresql-toast/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 6,
      "reading_min": 14,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "order-by-jungle",
      "title": "Welcome to ORDER BY jungle","published": "2026-05-15","updated": null,
      "description": "Bare names and expressions in ORDER BY go through two different parsers in PostgreSQL. The boundary is one if-statement, and it shows up in odd places.",
      "url": "https://boringsql.com/posts/order-by-jungle/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 11,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "strong-views",
      "title": "Strong views on PostgreSQL VIEWs","published": "2026-05-10","updated": null,
      "description": "Views are PostgreSQL's cleanest abstraction and its most rigid. Rewrite rules, attribute numbers, and the dependency catalog turn column changes into teardowns.",
      "url": "https://boringsql.com/posts/strong-views/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 21,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "hot-updates",
      "title": "HOT Updates in Postgres","published": "2026-04-28","updated": null,
      "description": "Every UPDATE risks rewriting every index. HOT updates skip that by chaining tuple versions in one page. A pageinspect tour of HOT chains and fillfactor.",
      "url": "https://boringsql.com/posts/hot-updates/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 5,
      "reading_min": 12,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "postgresql-mvcc-byte-by-byte",
      "title": "PostgreSQL MVCC, Byte by Byte","published": "2026-04-17","updated": null,
      "description": "Two concurrent sessions, same page, different answers. An interactive tour of xmin, xmax, snapshots, and the visibility rules deciding what you see.",
      "url": "https://boringsql.com/posts/postgresql-mvcc-byte-by-byte/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 4,
      "reading_min": 9,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "dont-let-ai-to-prod",
      "title": "Don't let AI touch your production database","published": "2026-04-06","updated": null,
      "description": "Most PostgreSQL MCP servers are connection wrappers or provider gateways. Neither warns an AI agent that its migration locks a 4M-row table. dryrun MCP does.",
      "url": "https://boringsql.com/posts/dont-let-ai-to-prod/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 11,
      "tags": ["postgresql", "tools", "boringsql", "ai"]
    },
    {
      "slug": "good-cte-bad-cte",
      "title": "Good CTE, bad CTE","published": "2026-03-29","updated": null,
      "description": "The planner treats CTEs very differently depending on how you write them. Here's what happens under the hood, version by version, through PostgreSQL 18.",
      "url": "https://boringsql.com/posts/good-cte-bad-cte/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 18,
      "tags": ["postgresql", "deep-dive", "sql"]
    },
    {
      "slug": "regresql-extension",
      "title": "pg_regresql: truly portable PostgreSQL statistics","published": "2026-03-21","updated": null,
      "description": "pg_regresql hooks into the PostgreSQL planner to trust injected catalog statistics instead of file sizes. Production-scale query plans in CI without the data.",
      "url": "https://boringsql.com/posts/regresql-extension/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 5,
      "tags": ["postgresql", "expert"]
    },
    {
      "slug": "portable-stats",
      "title": "Production query plans without production data","published": "2026-03-08","updated": null,
      "description": "PostgreSQL 18 makes optimizer statistics portable. Export from production and inject into test databases for realistic query plans without the data.",
      "url": "https://boringsql.com/posts/portable-stats/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 8,
      "tags": ["postgresql", "expert"]
    },
    {
      "slug": "postgresql-statistics",
      "title": "PostgreSQL Statistics: Why queries run slow","published": "2026-02-26","updated": null,
      "description": "Slow PostgreSQL queries? Stale statistics are often to blame. Learn how the query planner uses statistics to estimate row counts and how to fix them.",
      "url": "https://boringsql.com/posts/postgresql-statistics/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 15,
      "tags": ["postgresql", "deep-dive"]
    },
    {
      "slug": "inside-the-8kb-page",
      "title": "Inside PostgreSQL's 8KB Page","published": "2026-02-19","updated": null,
      "description": "A byte-level tour of the 8KB page, PostgreSQL's atomic unit of storage. Page headers, line pointers, and free space via pageinspect — how data lives on disk.",
      "url": "https://boringsql.com/posts/inside-the-8kb-page/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 3,
      "reading_min": 14,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "explain-buffers",
      "title": "Reading Buffer statistics in EXPLAIN output","published": "2026-02-06","updated": null,
      "description": "Learn how to interpret buffer statistics in PostgreSQL EXPLAIN. Shared hits, disk reads, temp spills, and planning buffers for diagnosing slow queries.",
      "url": "https://boringsql.com/posts/explain-buffers/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 2,
      "reading_min": 10,
      "tags": []
    },
    {
      "slug": "introduction-to-buffers",
      "title": "Introduction to Buffers in PostgreSQL","published": "2026-01-24","updated": null,
      "description": "How PostgreSQL actually manages memory, from shared_buffers and dirty pages to the OS page cache sitting underneath it all.",
      "url": "https://boringsql.com/posts/introduction-to-buffers/",
      "guide_slug": "postgresql-storage-internals",
      "guide_part": 1,
      "reading_min": 13,
      "tags": ["internals", "deep-dive"]
    },
    {
      "slug": "good-bad-arrays",
      "title": "The hidden cost of PostgreSQL arrays","published": "2026-01-12","updated": null,
      "description": "Deep dive into PostgreSQL arrays: document storage in disguise, the TOAST trap, GIN vs B-tree indexing, the ANY() pitfall, and when junction tables win.",
      "url": "https://boringsql.com/posts/good-bad-arrays/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 12,
      "tags": ["sql", "deep-dive"]
    },
    {
      "slug": "instant-database-clones",
      "title": "Instant database clones with PostgreSQL 18","published": "2025-12-22","updated": null,
      "description": "Learn how to clone PostgreSQL databases instantly using reflinks. Turn slow template copies into milliseconds with PostgreSQL 18's new file copy options.",
      "url": "https://boringsql.com/posts/instant-database-clones/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 6,
      "tags": ["postgresql", "expert"]
    },
    {
      "slug": "vacuum-is-lie",
      "title": "VACUUM Is a Lie (About Your Indexes)","published": "2025-12-11","updated": null,
      "description": "PostgreSQL VACUUM cleans tables but leaves indexes bloated. Learn why B-trees fragment and how to fix them properly.",
      "url": "https://boringsql.com/posts/vacuum-is-lie/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 12,
      "tags": ["postgresql"]
    },
    {
      "slug": "regresql-testing-queries",
      "title": "RegreSQL: Regression Testing for PostgreSQL Queries","published": "2025-11-13","updated": null,
      "description": "Stop deploying broken SQL queries. RegreSQL provides regression testing for PostgreSQL queries with performance baselines and automated warnings.",
      "url": "https://boringsql.com/posts/regresql-testing-queries/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 10,
      "tags": ["postgresql", "sql", "boringsql", "tools"]
    },
    {
      "slug": "beyond-start-end-columns",
      "title": "Beyond Start and End: PostgreSQL Range Types","published": "2025-11-02","updated": null,
      "description": "Discover PostgreSQL range types for cleaner schemas and atomic conflict detection. Use tstzrange, daterange, and int4range with exclusion constraints.",
      "url": "https://boringsql.com/posts/beyond-start-end-columns/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 15,
      "tags": ["postgresql", "sql", "deep-dive"]
    },
    {
      "slug": "postgresql-predefined-roles",
      "title": "PostgreSQL maintenance without superuser","published": "2025-09-13","updated": null,
      "description": "Learn about PostgreSQL maintenance without superuser privileges. Predefined roles like pg_monitor and pg_maintain provide secure database administration.",
      "url": "https://boringsql.com/posts/postgresql-predefined-roles/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 9,
      "tags": ["postgresql", "maintenance"]
    },
    {
      "slug": "logical-replication-beyond-the-basics",
      "title": "Beyond the Basics of Logical Replication","published": "2025-06-24","updated": null,
      "description": "Advanced PostgreSQL logical replication: operations, monitoring, and decoding internals. Data copy strategies, manual sync, and publication control.",
      "url": "https://boringsql.com/posts/logical-replication-beyond-the-basics/",
      "guide_slug": "mastering-logical-replication",
      "guide_part": 2,
      "reading_min": 11,
      "tags": ["postgresql"]
    },
    {
      "slug": "logication-replication-introduction",
      "title": "First steps with Logical Replication in PostgreSQL","published": "2025-06-11","updated": null,
      "description": "Logical replication streams row-level changes between PostgreSQL instances. How publications, subscriptions, replication slots, and replica identity fit together, and how to set them up.",
      "url": "https://boringsql.com/posts/logication-replication-introduction/",
      "guide_slug": "mastering-logical-replication",
      "guide_part": 1,
      "reading_min": 13,
      "tags": ["postgresql"]
    },
    {
      "slug": "postgresql-service-definition",
      "title": "PostgreSQL Service Connections","published": "2025-05-15","updated": null,
      "description": "PostgreSQL service definition is a powerful feature that allows you to define and manage database connections in a secure and maintainable way.",
      "url": "https://boringsql.com/posts/postgresql-service-definition/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 5,
      "tags": []
    },
    {
      "slug": "know-the-time-in-postgresql",
      "title": "Time to Better Know The Time in PostgreSQL","published": "2025-04-06","updated": null,
      "description": "Understand how PostgreSQL handles time. TIMESTAMPTZ, TIMESTAMP, DATE, TIME, and INTERVAL — pick the right type and avoid common issues.",
      "url": "https://boringsql.com/posts/know-the-time-in-postgresql/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 11,
      "tags": ["sql", "deep-dive"]
    },
    {
      "slug": "view-inlining",
      "title": "VIEW inlining in PostgreSQL","published": "2025-02-08","updated": null,
      "description": "Understand how PostgreSQL optimizes queries through view inlining, what factors can prevent it, and best practices for designing views that perform efficiently.",
      "url": "https://boringsql.com/posts/view-inlining/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 5,
      "tags": ["sql"]
    },
    {
      "slug": "deletes-are-difficult",
      "title": "DELETEs are difficult","published": "2024-11-23","updated": null,
      "description": "Why are database DELETEs complex and inefficient? Uncover challenges from physical data removal to autovacuum, and find strategies for managing large deletions.",
      "url": "https://boringsql.com/posts/deletes-are-difficult/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 11,
      "tags": ["sql"]
    },
    {
      "slug": "text-identifier-in-db-design",
      "title": "Text identifiers in PostgreSQL database design","published": "2024-11-09","updated": null,
      "description": "Considering text identifiers in your database? Discover readability benefits and performance challenges, plus strategies for efficient use.",
      "url": "https://boringsql.com/posts/text-identifier-in-db-design/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 9,
      "tags": ["sql"]
    },
    {
      "slug": "postgresql-enums",
      "title": "We need to talk about ENUMs","published": "2024-09-04","updated": null,
      "description": "ENUMs offer simplicity, but are they right for your data? Compare them with CHECK constraints and reference tables to find the best fit.",
      "url": "https://boringsql.com/posts/postgresql-enums/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 9,
      "tags": ["sql", "deep-dive"]
    },
    {
      "slug": "beyond-upserts-with-merge",
      "title": "Beyond Simple Upserts with MERGE in PostgreSQL","published": "2024-08-25","updated": null,
      "description": "MERGE command goes beyond simple upserts. See how it handles complex data sync with conditional inserts, updates, and deletes in a single atomic statement.",
      "url": "https://boringsql.com/posts/beyond-upserts-with-merge/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 5,
      "tags": ["sql"]
    },
    {
      "slug": "window-functions-introduction",
      "title": "Gentle Introduction to Window Functions in PostgreSQL","published": "2024-07-07","updated": null,
      "description": "Unlock powerful data analysis in PostgreSQL with window functions. Rank, calculate running totals, and compare rows directly within your queries.",
      "url": "https://boringsql.com/posts/window-functions-introduction/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 6,
      "tags": ["sql"]
    },
    {
      "slug": "time-keepers-pg-cron-pg-timetable",
      "title": "The time keepers: pg_cron and pg_timetable","published": "2024-06-15","updated": null,
      "description": "Need to schedule tasks in PostgreSQL? Compare pg_cron for simple in-database scheduling with pg_timetable for complex workflows and multi-cluster orchestration.",
      "url": "https://boringsql.com/posts/time-keepers-pg-cron-pg-timetable/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 7,
      "tags": ["tools", "maintenance"]
    },
    {
      "slug": "postgrest-tutorial-part3",
      "title": "Deep Dive into PostgREST - Time Off Manager (Part 3)","published": "2024-06-06","updated": null,
      "description": "",
      "url": "https://boringsql.com/posts/postgrest-tutorial-part3/",
      "guide_slug": "postgrest-apis-made-easy",
      "guide_part": 3,
      "reading_min": 8,
      "tags": ["guides", "postgrest", "tools"]
    },
    {
      "slug": "custom-postgresql-extensions-with-rust",
      "title": "Custom PostgreSQL extensions with Rust","published": "2024-05-24","updated": null,
      "description": "Build PostgreSQL extensions with Rust and pgrx. Safely create powerful functions, like retrieving system load, and integrate them into database workflows.",
      "url": "https://boringsql.com/posts/custom-postgresql-extensions-with-rust/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 4,
      "tags": ["expert", "extensions"]
    },
    {
      "slug": "postgrest-tutorial-part2",
      "title": "Deep Dive into PostgREST - Time Off Manager (Part 2)","published": "2024-05-18","updated": null,
      "description": "",
      "url": "https://boringsql.com/posts/postgrest-tutorial-part2/",
      "guide_slug": "postgrest-apis-made-easy",
      "guide_part": 2,
      "reading_min": 10,
      "tags": ["guides", "postgrest", "tools"]
    },
    {
      "slug": "postgrest-tutorial-part1",
      "title": "Deep Dive into PostgREST - Time Off Manager (Part 1)","published": "2024-05-11","updated": null,
      "description": "",
      "url": "https://boringsql.com/posts/postgrest-tutorial-part1/",
      "guide_slug": "postgrest-apis-made-easy",
      "guide_part": 1,
      "reading_min": 7,
      "tags": ["guides", "postgrest", "tools"]
    },
    {
      "slug": "how-not-to-change-postgresql-column-type",
      "title": "How not to change PostgreSQL column type","published": "2024-05-04","updated": null,
      "description": "Changing PostgreSQL column types can be tricky. Avoid table rewrites and locks; follow best practices for safe migrations with minimal downtime.",
      "url": "https://boringsql.com/posts/how-not-to-change-postgresql-column-type/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 8,
      "tags": ["maintenance"]
    },
    {
      "slug": "the-bloat-busters-pg-repack-pg-squeeze",
      "title": "The Bloat Busters: pg_repack vs pg_squeeze","published": "2024-04-27","updated": null,
      "description": "Combat PostgreSQL table bloat with pg_repack and pg_squeeze. See how they differ from VACUUM FULL and pick the right strategy for maintenance without downtime.",
      "url": "https://boringsql.com/posts/the-bloat-busters-pg-repack-pg-squeeze/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 7,
      "tags": ["maintenance", "expert"]
    },
    {
      "slug": "boring-sql-and-databases",
      "title": "Are SQL & Databases Boring? Absolutely - and That's a Good Thing!","published": "2024-04-21","updated": null,
      "description": "",
      "url": "https://boringsql.com/posts/boring-sql-and-databases/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 2,
      "tags": ["boringSQL"]
    },
    {
      "slug": "why-postgresql-indexes-are-ignored",
      "title": "When and Why PostgreSQL Indexes Are Ignored","published": "2024-04-14","updated": null,
      "description": "Why isn't PostgreSQL using your indexes? Discover the surprising reasons behind ignored indexes and optimize your queries for significant performance gains.",
      "url": "https://boringsql.com/posts/why-postgresql-indexes-are-ignored/",
      "guide_slug": null,
      "guide_part": null,
      "reading_min": 5,
      "tags": ["indexes"]
    }
  ],

  "guides": [
    {
      "slug": "postgresql-storage-internals",
      "name": "PostgreSQL Storage Internals",
      "description": "Seven chapters on the storage layer, in reading order. Pages move through shared buffers, rows become tuples, MVCC keeps the old versions, HOT updates dodge the index tax, TOAST takes what doesn't fit, and VACUUM reclaims the rest. Live pageinspect sessions throughout, with an interactive visualizer for most chapters.",
      "url": "https://boringsql.com/guides/postgresql-storage-internals/",
      "chapters_planned": 7,
      "reading_min": 88
    },
    {
      "slug": "mastering-logical-replication",
      "name": "Mastering Logical Replication in PostgreSQL",
      "description": "Logical replication from first setup to production use. Publishers, subscribers, and row-level streaming, then the operational side: monitoring, data copy strategies, logical decoding, and publication control. Four more chapters are planned, from conflict resolution to zero-downtime major version upgrades.",
      "url": "https://boringsql.com/guides/mastering-logical-replication/",
      "chapters_planned": 6,
      "reading_min": 26
    },
    {
      "slug": "postgrest-apis-made-easy",
      "name": "PostgREST Tutorial: APIs made easy",
      "description": "Three-part tutorial building a Time Off Manager API on PostgREST. Expose tables as endpoints, add business logic with views and stored procedures, then lock the API down with JWT authentication and row-level security. No application server, no boilerplate.",
      "url": "https://boringsql.com/guides/postgrest-apis-made-easy/",
      "chapters_planned": 3,
      "reading_min": 25
    }
  ],

  "visualizers": [
    {
      "slug": "shared-buffers",
      "name": "PostgreSQL Shared Buffers Visualized",
      "description": "Interactive visualization of PostgreSQL's buffer pool, clock sweep eviction, and hash table lookups.",
      "url": "https://boringsql.com/visualizers/shared-buffers/"
    },
    {
      "slug": "8kb-page",
      "name": "Inside the 8KB Page: PostgreSQL Page Layout Visualized",
      "description": "Interactive visualization of PostgreSQL's 8KB page: header, line pointers, free space, and tuple data.",
      "url": "https://boringsql.com/visualizers/8kb-page/"
    },
    {
      "slug": "mvcc",
      "name": "PostgreSQL MVCC Visualized",
      "description": "Interactive visualization of PostgreSQL's MVCC: xmin, xmax, snapshots, two concurrent sessions, and VACUUM reclaim.",
      "url": "https://boringsql.com/visualizers/mvcc/"
    },
    {
      "slug": "hot-updates",
      "name": "PostgreSQL HOT Updates and Page Pruning Visualized",
      "description": "Interactive visualization of HOT updates and page pruning. Watch ctid chains form on a single page, see HOT_UPDATED and HEAP_ONLY flags flip, and trigger pruning to reclaim dead chain members.",
      "url": "https://boringsql.com/visualizers/hot-updates/"
    },
    {
      "slug": "vacuum",
      "name": "PostgreSQL VACUUM at the Page Level Visualized",
      "description": "Interactive visualization of VACUUM across three heap pages. Run DELETE and watch nothing get reclaimed, page-prune to reach LP_DEAD, then step VACUUM through heap scan, index cleanup, and heap cleanup that frees the slots.",
      "url": "https://boringsql.com/visualizers/vacuum/"
    },
    {
      "slug": "toast",
      "name": "PostgreSQL TOAST Visualized",
      "description": "Interactive visualization of PostgreSQL's TOAST machinery. Watch wide values get compressed, sliced into chunks, and relocated from the main heap into pg_toast, with the four storage strategies and two compression methods as live controls.",
      "url": "https://boringsql.com/visualizers/toast/"
    }
  ],

  "tools": [
    {
      "name": "Hindsight for PostgreSQL",
      "kind": null,
      "one_liner": "Run Production Postgres Without Hiring a Full-Time DBA",
      "url": "https://boringsql.com/products/hindsight/",
      "repo_url": null,
      "license": null
    },
    {
      "name": "Fixturize",
      "kind": null,
      "one_liner": "Data Subsetting Without the Invoice",
      "url": "https://boringsql.com/products/fixturize/",
      "repo_url": "https://github.com/boringSQL/fixturize",
      "license": null
    },
    {
      "name": "RegreSQL",
      "kind": null,
      "one_liner": "Because 'It Works on My Database' Won't Fly in the Postmortem",
      "url": "https://boringsql.com/products/regresql/",
      "repo_url": "https://github.com/boringsql/regresql",
      "license": null
    },
    {
      "name": "dryrun",
      "kind": null,
      "one_liner": "Give Your AI Agent Your Real Schema. Not Your Production Credentials.",
      "url": "https://boringsql.com/products/dryrun/",
      "repo_url": "https://github.com/boringSQL/dryrun",
      "license": null
    }
  ],

  "talks": [
    {
      "date": "2025-11-24",
      "title": "Simple things in PostgreSQL that will break your application",
      "event": "Prague Postgres Meetup",
      "event_url": "https://www.meetup.com/prague-postgresql-meetup/",
      "slides_url": "https://boringsql.com/slides/2025-10-24-Prague-Things%20to%20crash%20applications.pdf"
    },
    {
      "date": "2025-11-26",
      "title": "Simple things in PostgreSQL that will break your application",
      "event": "Malmö PostgreSQL User Group (M-PUG)",
      "event_url": "https://www.meetup.com/malmo-postgresql-user-group-m-pug/",
      "slides_url": "https://boringsql.com/slides/2025-10-24-Prague-Things%20to%20crash%20applications.pdf"
    },
    {
      "date": "2026-01-28",
      "title": "It works on my dabase - Regression testing of SQL queries",
      "event": "P2D2 2026",
      "event_url": "https://p2d2.cz/en/",
      "slides_url": "https://boringsql.com/slides/2026-01-29-p2d2-it-works-on-my-database.pdf"
    },
    {
      "date": "2026-03-12",
      "title": "It works on my database - Regression testing of SQL queries (Mk2)",
      "event": "PostgresEDI meetup",
      "event_url": "https://luma.com/PostgresEDI?e=evt-op8LCMpN1741BYg&period=past",
      "slides_url": "https://boringsql.com/slides/2026-03-12-edi-it-works-on-my-database.pdf"
    },
    {
      "date": "2026-03-24",
      "title": "It works on my database - Regression testing of SQL querions (v4)",
      "event": "Nordic PgDay 2026",
      "event_url": "https://2026.nordicpgday.org",
      "slides_url": "https://boringsql.com/slides/2026-03-24-it-works-on-my-database.pdf"
    },
    {
      "date": "2026-03-30",
      "title": "Visualizing PostgreSQL Storage Internals (v1)",
      "event": "Prague Postgres Meetup",
      "event_url": "https://www.meetup.com/prague-postgresql-meetup/",
      "slides_url": "https://boringsql.com/slides/2026-03-30-pg-storage-visualizer.pdf"
    },
    {
      "date": "2026-04-21",
      "title": "Visualizing PostgreSQL Storage Internals ",
      "event": "PostgreSQL Conf Germany 2026",
      "event_url": "https://2026.pgconf.de/",
      "slides_url": "https://boringsql.com/slides/2026-04-21-pg-storage-visualizer.pdf"
    },
    {
      "date": "2026-04-22",
      "title": "It works on my database (v5)",
      "event": "PostgreSQL Conf Germany 2026",
      "event_url": "https://2026.pgconf.de/",
      "slides_url": "https://boringsql.com/slides/2026-04-22-it-works-on-my-machine.pdf"
    },
    {
      "date": "2026-06-03",
      "title": "Visualizing PostgreSQL Storage Internals",
      "event": "PG DATA 2026",
      "event_url": "https://2026.pg-data.org/",
      "slides_url": "https://boringsql.com/slides/2026-06-03-postgres-internals-visualized.pdf"
    }
  ]
}