(LLM writing rant below)
---
> That alone tells you something: Users had a real need, and the ecosystem filled the gap.
> This sounds straightforward, but it solves a real operational problem.
> None of these change the world. All of them make day-to-day data workflows better.
> The easy thing to do here is list planner changes and call it done. But the more useful takeaway is this: Postgres keeps getting better at recognizing the shape of common queries and doing less unnecessary work.
> [Proceed to list planner changes]
If Orwell were alive today, he might declare himself illiterate in English and learn Klingon just to avoid having to read these.
SELECT customer_name FROM GRAPH_TABLE (myshop MATCH (c IS customers)-[IS customer_orders]->(o IS orders WHERE o.ordered_when = current_date) COLUMNS (c.name AS customer_name));
That is _awful_ syntax; it is reminiscent of neo4j, which is surely not a tool anyone serious should copy from outright in 2026.
And of course the final thing I am left wondering is if it's fast. Row-level security is such a useful feature and yet only a fool would contemplate building anything serious with Postgres', as the planner goes haywire and does per-row-matching, nuking performance.
These days, I do myself a favor and always avoid Oracle and MySql/MariaDB.
Postgres is amazing, and the two big things I wished it had:
1. lightweight connection; connection bouncers improve the situation, but you still have an unreasonably high memory footprint per concurrent connection.
2. Synchronously updated materialized views (Sql Server calls them indexed views). These are incredible tools in complex data situations. I saw a project struggle with complex technical implementations that would be elegant, trivial and always correct with indexed views.
Sql Server can be costly, but in many cases the benefits it provides are totally worth the cost.
Choosing the data store carefully prevents lots of future trouble.
Something like rocksdb as PG backend would be fantastic. Yugabyte does this but it's not PG.
It's SQL/PGQ, which was derived from the Cypher language for Neo4J and now is part of the SQL standard.
Pg connections are definitely heavy, but usually on resources other than memory in my experience. If you configure reasonable dirty reclamation and recycling, the memory numbers are often overstated due to Linux tools’ deceptive fork accounting and shared buffers. Ofc, if you’re averaging lots of heavy queries per connection it’ll be truly heavy, but many times the numbers overstate the impact.
If I am going to use a "free" provider, SQLite is impossible to beat. They cover a majority of use cases today. SQLite starts to fall apart with backup, replication and tooling. If I am on the hook for things like system availability and disaster recovery, I don't have a problem spending money to cover my ass.
If I am going to pay any amount of money at all, I am going all the way. The developer experience around MSSQL is untouchable. SSMS and VS with sql projects runs circles around contemporary entity framework crap. Sprinkle in 3rd party tools from vendors like RedGate and you can replace multi-million dollar consulting packages.
I wouldn't ever advocate for standing up a new Oracle or DB2 machine, but if one was already in place I'd probably die on the hill of not trying to refactor it away. These databases typically come with multi-volume ghost stories attached. Reinventing all those weird effects on a new engine will typically kill the business if there are no other options available.
1. "materialize" the view as a full table, then index that. Any reasonable pipeline/ETL tool can provide incremental updates between tables. Obviously, anything materialized requires considerations around storage, replication, backup/restore, I/O, etc.
2. use a regular VIEW and index (precisely) the underlying expressions mentioned in the view, i.e. so when the view is used, then the indexes get used.
Both require rewriting SQL, though I've used VIEWs to make the change transparent.
In my eyes they're similar to triggers, which incur a high performance overhead in OLTP systems and are shunned by developers. In OLAP systems custom ETL code will likely outperform them.
Windows Server is a real pain to operate and the SQL Server ecosystem expects you to run a lot of add-ons on the server alongside your database. Those don’t translate to managed database services, so you lose a lot of functionality if you jump to RDS or similar.
The first party tools are also aging poorly. SSIS and SSRS are not fun. SSMS is ok for what it is but can’t compete with the ecosystem around PostgreSQL.
Maybe I’m missing something but I can’t wait to ditch it.
So what's wrong with MySQL or MariaDB?
> Several of these features were first introduced by DuckDB, while some are inspired by other systems. Many of the features originally introduced by DuckDB (e.g., GROUP BY ALL) have been since adapted by other systems.
This may be the case for MS-centric, application & human developers, but I'm not convinced moving forward. Microsoft's BI story is pretty thin and out of date. Postgres has some solid columnar support/functions (which probably why Snowflake is writing about it) which means you can potentially use it for both you transactional and analytical workflows. As more development shifts to agentic workflows I'd bet Postgres shines when the overall ecosystem is more important than the human tools that were essential for the past 20 years. I loved Redgate's value-add but I don't think agents care about the UI which was the big win. SQL Server will continue to live in the enterprise andf where MS can sell lucrative support contracts or build for their clients, but I'm not seeing any net-new projects where the builders have any choice to not use SQL Server.
Stale MV is a thing you only ever burn your fingers on once. Like how "It's not DNS" is a common meme in networking.
Oracle treats empty strings as being NULL.
Anyone who's never used Oracle before in their life is probably wondering if I'm making it up. I'm not. In Oracle, inserting '' in a VARCHAR column is exactly the same as inserting NULL. And if there's a NOT NULL column, you're not allowed to store the empty string in there.
Which means that in Oracle, you do not have any way of distinguishing "I don't know the person's middle name" vs. "I know what the person's middle name is: he/she doesn't have one".
There are apparently historical reasons for this, but I don't care. The empty string is NOT the same as NULL, and any software that treats them as the same IS BUGGY!
Sorry. Had to get that off my chest. I know I'm answering a question you didn't ask, but that has been bothering me for nearly 25 years (I first learned about this misfeature of Oracle's in 2002 or 2003), and I just had to vent to somebody who would understand.
Note: the below taken nearly verbatim from https://sql-info.de/mysql/referential-integrity.html#3_5
CREATE DATABASE foo;
USE foo;
CREATE TABLE one ( id TINYINT NOT NULL PRIMARY KEY ) TYPE=InnoDB ;
CREATE TABLE two (
id TINYINT NOT NULL PRIMARY KEY,
INDEX (id),
CONSTRAINT id_fkey FOREIGN KEY (id) REFERENCES one(id)
) TYPE=InnoDB ;
Now that we've created both tables, let's insert a record into table one: INSERT INTO one VALUES (127);
And now let's insert a record with a different primary key into table two: INSERT INTO two VALUES (128);
MariaDB will give you an error at this point (ERROR 1264 (22003): Out of range value for column 'id' at row 1), but MySQL (at least back when I tried this about ten years ago, which was the last time I was forced to work with MySQL — and I am so glad I never have to go back!) would return no error message and just say "Query OK, 1 row affected (0.009 sec)".Now let's select the value we inserted into table "two":
SELECT * FROM two;
And what do we see? The value 127, even though we inserted 128. Which has created a foreign-key relationship to table "one" that we never intended to put in there.There are other reasons why MySQL was inadequate, but I no longer remember them. Probably MariaDB has fixed them by now. But I no longer have to use MySQL/MariaDB for anything, and I never want to go back. I have a VERY strong averse reaction, caused by past pain, when I think of using MariaDB. (I actually spun up a virtual machine to test what I wrote here, because there's no way I was going to install MariaDB on my primary work machine).
When I started I thought there's too much inherent overhead in using Postgres tables for storage and using the Postgres executor, so figured it would be pretty cool to match Timescale in performance. I didn't think it would be possible to get close to dedicated analytical DBs. But as the project progressed and the performance got better and better, I'm now firmly in the camp of doing analytics with Postgres + an extension.
When Claude writes things like "as someone who has spent a lot of time doing X", I think this is also a kind of failure of alignment. LLMs shouldn't write as if they had personal experience. It's something a person might say in the training data, but I just think LLMs shouldn't claim life experience they don't have, even if that's a statistically likely sequence of tokens.
Indexed views are not much worse than indexes. Of course, when they refer to other tables there are underlying data lookups, but in our experience when we moved from triggers to indexed views, large scale data ingestion went way faster.
Where we used it: While revamping a large scale sales program, we stored the warehouse in/out in one table, and several things like current stock were calculated using indexed views.
Bonus: Using Snapshot concurrency control, you can do many things concurrently, and only when they both updates to a certain product in the same store you'll get the second transaction failing (which could be retried on the backend).
The fact that they are completely in-sync with your data is amazing.
It’s like saying that you’re getting worried Apple doesn’t sell washing machines.
I think the better way is to use Postgresql for new data and routinely archive off older data to data warehouse type database, to keep the Postgres one small.
(Many companies also now use a RDBMS alongside either a KV database or document store in main app)
There was a big fanfare about orioledb a while ago, and i think it got bought by people that wanted to push that into mainstream postgres?
Did it die somewhere along the road?
https://vldb.org/cidrdb/2026/a-multi-tenant-relational-oltp-...
https://learn.microsoft.com/en-us/sql/linux/sql-server-linux...
What you're describing is amazing, and I wish I had it available to us. We've hand rolled far too many triggers to achieve the same thing, with all the expected problems you'd assume. I'm sure it could be abused/misused, but a batteries-included approach like that would be huge.
My list:
No `explain (analyze,buffers)`. Instant DDL has some warts (e.g. fk, metadata locks). Query planning bugs (actually... query planning in general is disappointing). Exiting the repl doesn't stop queries. Implicit type casting. Replication lag from large DDL (e.g. creating an index). Lack of two phase DDL (creating constraints NOT VALID and then VALIDATE later). Lack of extensions (e.g. pg_vector). No safe access to inspect buffer cache. AWS Aurora seems to only add shiny new things to Postgres. And more.
Again, none of this is quite enough to migrate off of it for an established system, but certainly enough to avoid it on a new project.
Columnar and all the other fun stuff (JSON, GIS, inverted indexes, embedding vectors) is a natural progression of that thinking. With TimescaleDB, Hydra, Citus, pg_mooncake, etc. becoming very popular the last few years, there is a clear demand for an integrated experience.
(Stonebraker also thought one database shouldn't do everything, as described in his early 2000s "One Size Does Not Fit All" paper, and Stonebraker branched out into HStore/Vertica for columnar. In hindsight, I think that was appropriate for the time, but no longer a significant concern.)
There are other relational databases that have both kinds of storage engines and some use both on the same table (row based insert with column based migration and secondary column store indexes: https://learn.microsoft.com/en-us/sql/relational-databases/i...
Just like you can have b-tree based table storage vs heap in the case of index organized tables / clustered indexes (which pg doesn't have) you can choose column based instead the logical data model is still the same relational model.
https://supabase.com/blog/orioledb-launch
And they continue to work on it.
Context: https://www.orioledb.com/docs#:~:text=OrioleDB%20currently%2...
No real point here other than an observation about how the installed base’s needs change, across industries.
By the same logic, you could say Microsoft Access should have all the capabilities of Postgres because it's painful for small businesses to move off of it when it's no longer a good fit for their needs.
But I agree in the end I may be forced to move elsewhere...
I hope everyone keeps pointing it out. Even better, change the site guidelines to make AI generated articles a flaggable offense. It's already been done for comments.
Every Postgres release has a personality.
Some releases are about one big headline feature. Some are about removing a long-standing pain point. Some are full of those "oh, nice" improvements that you don't fully appreciate until you upgrade and suddenly your day-to-day workflow is just a little bit smoother. Oh, and of course almost every release has some performance improvements sprinkled in.
Postgres 19 feels like one of those releases that has a bit of everything. REPACK CONCURRENTLY is a big quality-of-life upgrade for larger production databases and it's built in. There are some big-ticket features, yes. SQL property graph queries are going to get a lot of attention. Logical replication keeps getting more complete. And then there are the steady improvements across VACUUM, EXPLAIN, COPY, partitioning, monitoring, performance and planner behavior that continue to make Postgres better not in a flashy way, but in the very practical "this helps me run production systems" way.
As always with a beta, details can change before GA. But with Postgres 19 now in beta, it's a good time to start looking at what is coming and, more importantly, what it means for people building and operating on Postgres.
If you have operated Postgres for long enough, you have probably had a moment where you wanted to reclaim table bloat, rewrite a table or reorganize data — but you very much did not want to take the lock that came with VACUUM FULL or CLUSTER.
There has long been an extension ecosystem around this problem, most notably pg_repack. That alone tells you something: Users had a real need, and the ecosystem filled the gap.
Postgres 19 brings a new REPACK command into core, including support for REPACK CONCURRENTLY.
I expect REPACK CONCURRENTLY to be one of those features that production Postgres users care about more than the average release-note reader might expect.
Partitioning in Postgres has improved a lot over the years. It went from "you can do this, but you probably need to understand a lot of internals and sharp edges" to something much more approachable.
Postgres 19 continues that work with support for merging and splitting partitions.
This sounds straightforward, but it solves a real operational problem. Partitioning strategy is one of those things you often choose with imperfect information. You start with a partitioning scheme that makes sense at the time. Then your workload changes. Your retention window changes. Your data volume grows faster than expected. Or maybe a few partitions become awkwardly large while others are tiny.
Being able to split and merge partitions gives you more room to evolve your design over time.
-- Combine Q1 and Q2 into a single partition
ALTER TABLE customer_orders MERGE PARTITIONS (orders_2026_q1, orders_2026_q2) INTO orders_2026_h1;
-- Split the Q3 partition into monthly intervals ALTER TABLE customer_orders SPLIT PARTITION orders_2026_q3 INTO ( PARTITION orders_2026_07 FOR VALUES FROM ('2026-07-01') TO ('2026-08-01'), PARTITION orders_2026_08 FOR VALUES FROM ('2026-08-01') TO ('2026-09-01'), PARTITION orders_2026_09 FOR VALUES FROM ('2026-09-01') TO ('2026-10-01') );
Features like this matter because the best database designs are rarely static. Good systems evolve. Postgres giving you more ways to adjust without rebuilding everything from scratch is exactly the kind of incremental improvement that makes it easier to run Postgres for the long haul.
Logical replication has been one of the most important areas of Postgres development over the last several releases. It is useful for migrations, upgrades, reporting systems, data movement, selective replication, and increasingly for high-availability and operational workflows.
Postgres 19 continues to round out logical replication in a few important ways.
The big one: Sequence values can now be synchronized so subscribers better match publishers. Anyone who has worked with logical replication for application tables that depend on sequences knows why this matters. Data replication without sequence state can leave you with awkward post-cutover problems. The data moved, but the next generated ID may not be where you need it to be.
Postgres 19 also adds ALL SEQUENCES support for publications, sequence sync error reporting and better subscription refresh behavior around sequences.
Another nice improvement: Publications can use an EXCEPT clause to exclude some tables when publishing all tables. This fits how people actually operate systems. You often want "basically everything, except these few things." Making that easier is a good thing.
And then there is the ability for wal_level = replica to automatically enable logical replication when needed, along with a new effective_wal_level to report what is actually happening. This is another practical improvement: fewer configuration footguns and better visibility when Postgres is doing something on your behalf.
Logical replication still has nuance. It always will. But every release makes it feel less like a specialized feature and more like part of the normal Postgres operational toolkit.
Vacuum is one of the most Postgres things there is.
You can use Postgres for years and never care about the internals. Then one day your table bloat grows, wraparound warnings appear or query performance changes, and suddenly you care a lot.
Postgres 19 has several improvements here that are worth calling out.
Autovacuum can now use parallel vacuum workers, controlled globally and per table. For larger tables and indexes, that gives Postgres more ability to keep up with maintenance work.
-- Allow up to 4 parallel workers globally for autovacuum processes
ALTER SYSTEM SET autovacuum_max_parallel_workers = 4;
There is also a new scoring system to control the order tables are autovacuumed. This is important because autovacuum is always making tradeoffs. Which table is most urgent? Which one should go next? Making that prioritization smarter helps in the exact place where Postgres users need help: keeping the system healthy before it becomes an incident.
-- Adjust the priority scoring just for this table:
-- Give insert-based vacuuming a massive urgency boost (3.0),
-- while dropping the normal update/delete vacuum urgency (0.5) since rows are rarely deleted.
ALTER TABLE application_logs SET (
autovacuum_vacuum_insert_score_weight = 3.0,
autovacuum_vacuum_score_weight = 0.5
);
The new pg_stat_autovacuum_scores view gives more visibility into that decision-making. Add in more details in vacuum and analyze progress views, memory usage and parallelism in VACUUM VERBOSE and autovacuum logs, and a separate log_autoanalyze_min_duration, and Postgres 19 feels like a release that is trying to make maintenance more observable.
That is a theme I'm always happy to see. The database doing work in the background is good. The database explaining that work better is even better.
Now for one of the more interesting additions: SQL/PGQ, or SQL property graph queries.
Graph databases have had their moments over the years. And to be clear, there are absolutely workloads where thinking in terms of vertices and edges is a useful model: fraud detection, recommendations, network analysis, permission graphs, supply chains, org charts and plenty more.
-- sample property graph
CREATE PROPERTY GRAPH store_graph
VERTEX TABLES (
customers LABEL customer,
orders LABEL "order"
)
EDGE TABLES (
customer_orders
SOURCE customers
DESTINATION orders
LABEL placed_order
);
But the thing I like about this landing in Postgres is that it is not asking you to throw away the relational model. It is Postgres adding another way to query the data you already have.
That is very Postgres.
Postgres has always had a way of absorbing useful capabilities without forcing an entirely new architecture on you. JSONB did not mean you had to stop using relational tables. Full text search did not mean you needed a separate search database for every use case. Extensions did not mean forking the database. And now SQL/PGQ gives Postgres a graph-shaped way of looking at relational data.
The most interesting part to me is less "Postgres is now a graph database" and more "Postgres continues to make the database you already picked more capable."
That is a subtle but important distinction.
For developers, this means there may be a lot of cases where graph-style queries become available without adding another datastore, another sync path, another operational surface area, and another thing to debug at 2:00 a.m. And as someone who has spent a lot of time around managed databases and production incidents, fewer moving pieces is generally a win.
COPY is one of those Postgres features that is both boring and wonderful. It moves data in and out quickly. It is dependable. And because loading and exporting data is such a common workflow, even small improvements can matter a lot.
Postgres 19 has several nice COPY improvements.
COPY FROM can skip multiple header lines. That sounds minor until you get handed yet another CSV from a vendor, internal tool or "export" process with extra metadata lines at the top.
COPY FROM also gets ON_ERROR SET_NULL, allowing invalid input values to be set to null. This gives you another option between "fail the whole load" and "pre-clean everything elsewhere."
-- Imagine a file where a price column occasionally contains 'N/A' or 'MISSING' instead of a numeric value
COPY product_catalog (product_id, title, price_usd)
FROM '/path/to/dirty_products.csv'
WITH (
FORMAT CSV,
HEADER,
ON_ERROR SET_NULL
);
COPY TO can output JSON format, including as a single JSON array. And COPY TO can now output partitioned tables directly, where previously you needed to use COPY (SELECT ...).
-- Export an entire table directly into a cleanly formatted, valid JSON array
COPY customers TO '/path/to/customers_export.json' WITH (FORMAT JSON, ARRAY true);
None of these change the world. All of them make day-to-day data workflows better.
Postgres 19 also brings a handful of SQL improvements that developers will appreciate.
GROUP BY ALL lets you automatically group by all nonaggregate and nonwindow target-list expressions. This is the kind of syntax that can make exploratory SQL and reporting queries cleaner. (Thanks to our own David Christensen for this one.)
-- Using GROUP BY ALL
SELECT
category,
manufacturer,
COUNT(*) as total_items,
AVG(price) as avg_price
FROM inventory_products
GROUP BY ALL;
Window functions get IGNORE NULLS and RESPECT NULLS support for functions like lead, lag, first_value, last_value and nth_value. If you have ever written awkward workarounds to get the previous non-null value in a series, this one should catch your eye.
INSERT ... ON CONFLICT DO SELECT ... RETURNING is another nice addition. Upserts are everywhere in application code, and being able to return conflicting rows more directly gives developers more flexibility.
INSERT INTO tags (tag_name)
VALUES ('postgres')
ON CONFLICT (tag_name) DO SELECT
RETURNING tag_id;
Postgres also adds UPDATE and DELETE FOR PORTION OF, continuing work around temporal use cases. Time is one of the most common dimensions in real applications, and better temporal syntax in core SQL is a welcome direction.
Expanded RANDOM() temporal functions. (Thanks to our own Paul Ramsey and Greg Sabino Mullane for work on this one.)
The planner and executor continue to get steady work in Postgres 19. Special thanks to Snowflake's Tom Lane for his efforts in Postgres 19 in performance and many other areas.
There are improvements around anti-joins, semi-joins, constant folding, incremental sort with append paths, aggregate processing before joins, join selectivity computation, function statistics and more.
The easy thing to do here is list planner changes and call it done. But the more useful takeaway is this: Postgres keeps getting better at recognizing the shape of common queries and doing less unnecessary work.
Some aggregate processing can now happen before joins, which can reduce the number of rows processed. More NOT IN and LEFT JOIN cases can become efficient anti-joins. Memoize has more visibility in EXPLAIN. Sort performance improves with radix sort. Foreign key constraint checks get faster. COPY FROM text and CSV input can use SIMD instructions.
These are not always features you change application code to use. In many cases, you upgrade and Postgres simply has a better chance of doing the right thing.
That is one of the best kinds of database improvements.
The thing that stands out to me about Postgres 19 is not one single feature. It is the breadth.
There is something for application developers: graph queries, better SQL syntax, window function improvements, better upsert behavior.
There is something for operators: REPACK CONCURRENTLY, better autovacuum, better monitoring, online checksum changes, more replication visibility.
There is something for performance-minded folks: planner improvements, SIMD improvements, asynchronous I/O visibility, faster foreign key checks, better sorts.
There is something for people building on top of Postgres: new hooks, planner advice modules, extension improvements, FDW stats retrieval and continued investment in the extension ecosystem.
That breadth is one of the reasons Postgres keeps winning. It is not just getting better for one workload or one persona. It is getting better as an application database, an operational database, an analytical database, an extensible database and a platform.
Postgres 19 is not GA yet, so now is the time to test. Try your application. Run your migration tests. Check your extensions. Look at plans for your most important queries. Exercise logical replication if you depend on it. Run your maintenance workflows. See what breaks while there is still time to fix it.
Postgres releases get better because people test them against real workloads.
And based on what is already in Postgres 19 beta, there is a lot here worth testing.