November 13, 2026
Improve RDS Proxy multiplexing for PostgreSQL, Part 1: Common causes
Improve RDS Proxy multiplexing for PostgreSQL, Part 2: Diagnose and monitor
Will your MongoDB upgrade break? Check it with UCFM
You were assigned one of the most common, yet feared, tasks in the life of a database administrator: Upgrades are needed for many reasons, whether to fix an existing bug or to keep the environment under support and eligible to receive security fixes. Either way, the task is yours. The binary swap is the easy … Continued
The post Will your MongoDB upgrade break? Check it with UCFM appeared first on Percona.
Village News: MySQL News + Events (13 November 2026)
Welcome back to Village News, our curated roundup of MySQL and database news. This week, PostgreSQL shipped updates fixing 52 security vulnerabilities and retired PostgreSQL 14, and the FOSDEM M-Ecosystem devroom opened its call for participation.
If you want to get these updates, just subscribe to the blog.
Enjoy!
Just Because You Can, Doesn’t Mean You Should: Re-evaluating Galera Node Weights
We all know that when using a cluster based on Galera (Codership) such as Percona XtraDB Cluster (PXC), it is recommended to deploy an odd number of nodes. The generic answer to this recommendation is that otherwise the risk of having an inconsistent cluster (like split-brain) is higher. (see Get started with Percona XtraDB Cluster … Continued
The post Just Because You Can, Doesn’t Mean You Should: Re-evaluating Galera Node Weights appeared first on Percona.
November 12, 2026
So, now you need to upgrade your MongoDB cluster…
Your MongoDB cluster has been running fine for years on 4.4 or 6.0. Then the version goes end of life, a CVE shows up in an audit, or a new feature needs 8.0, and the MongoDB upgrade lands on your desk. The upgrade itself is not hard. Knowing everything you have to check before, during … Continued
The post So, now you need to upgrade your MongoDB cluster… appeared first on Percona.
Introducing Percona Database Service (PDS): What Happens When an Engineer Just Wants to Try Something
It’s Thursday afternoon, and a developer has an idea. Nothing complicated. They are working on a new application feature and want to test an idea before committing to a larger implementation. A quick prototype is already running locally. So far, so good. Next, they want to deploy the application to a shared environment so their … Continued
The post Introducing Percona Database Service (PDS): What Happens When an Engineer Just Wants to Try Something appeared first on Percona.
Build a serverless recipe-sharing API with Go and Amazon Aurora DSQL
I asked Convex's CTO 34 spicy questions
Deploying OpenEverest on a 3-Node K3s Cluster
Part 1 of 2: From bare Ubuntu servers to a running MySQL cluster OpenEverest is an open source platform for running databases such as MySQL, PostgreSQL and MongoDB on Kubernetes. You get a web console and an API, and Kubernetes operators handle provisioning, scaling, backups and failover behind the scenes. This post builds a small … Continued
The post Deploying OpenEverest on a 3-Node K3s Cluster appeared first on Percona.
Inside an OpenEverest MySQL Cluster: Galera Health and a Live Failover Test
Part 2 of 2: Reading wsrep status, killing the writer, and proving zero data loss In Part 1 we built a 3-node K3s cluster, installed OpenEverest, and created a 3-node Percona XtraDB Cluster (PXC) named mysql-vjg. In this post we look inside that cluster. Section A explains how to read Galera’s wsrep_* status variables, so … Continued
The post Inside an OpenEverest MySQL Cluster: Galera Health and a Live Failover Test appeared first on Percona.
November 11, 2026
Similarity Is Not Truth: Comparing vector distance, is_true(), and extract() in Azure AI for PostgreSQL
Vector search queries often use top-k similarity, assuming the highest-scoring results are true and can serve as factual validation. These scores represent distances between vectors (e.g., <=> for cosine distance in PostgreSQL with pgvector), so they should be used to retrieve relevant results—not as a final ranking of truth. Azure AI on HorizonDB can call an AI model to generate embeddings for comparison and provides more specific functions, such as is_true() or extract(), for structured responses.
Objective
In this blog post, I'll use the three of them on a small dataset, a table of countries, with the same goal: tell me if the country is an island or not.
-
<=>should return a very high score for countries that are islands and a very low score for those that are not, so I can try to determine a threshold that tells me the truth -
is_true()should returntruefor countries that are islands andfalsefor those that are not -
extract()should extract the "is an island?" information as a boolean.
Create the table
I import a list of countries from a public resource
DROP TABLE IF EXISTS countries;
CREATE TABLE countries (
country text primary key,
region text
);
\copy countries(country,region)
FROM PROGRAM 'curl -L "https://raw.githubusercontent.com/cs109/2014_data/master/countries.csv"' WITH (FORMAT csv, HEADER true);
SELECT * FROM countries WHERE region = 'OCEANIA' LIMIT 10;
Here is a sample:
country | region
------------------+---------
Australia | OCEANIA
Fiji | OCEANIA
Kiribati | OCEANIA
Marshall Islands | OCEANIA
Micronesia | OCEANIA
Nauru | OCEANIA
New Zealand | OCEANIA
Palau | OCEANIA
Papua New Guinea | OCEANIA
Samoa | OCEANIA
(10 rows)
I generate embeddings from the country name, assuming that the model has enough knowledge about countries:
CREATE EXTENSION vector;
CREATE EXTENSION azure_ai;
ALTER TABLE countries
ADD COLUMN embedding vector(1536);
UPDATE countries
SET embedding = azure_openai.create_embeddings(
input => country,
dimensions => 1536
)::vector(1536);
For a real application, you may provide more information, such as a geographical description of the country, rather than using the AI model as a source of knowledge, but I keep this simple for the demo.
Naive query
Here is a query that uses Azure AI functions to generate embeddings for 'is an island' to compare the vector distance with the country, return a true or false for the question 'is an island' with the country name, and extract an 'is an island' boolean:
SELECT country
-- cosine distance to 'is an island'
, azure_openai.create_embeddings(
input => 'is an island',
dimensions => 1536
)::vector(1536) <=> embedding AS distance
-- boolean answer to 'is an island'
, azure_ai.is_true(
statement => country ||' is an island'
) AS is_true_result
-- extract 'is an island?' as a boolean, in addition to other questions
, azure_ai.extract(
country,
ARRAY[
'land: boolean - is an area of land',
'water: boolean - is surrounded by water',
'continent: boolean - is a continent',
'island: boolean - is an island?',
'neighbours: string - list the countries with common border'
]
) AS extracted
FROM countries
ORDER BY distance
;
I get this result:
country | distance | is_true_result | extracted
----------------------------------+--------------------+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Iceland | 0.5288325821583937 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Dominica | 0.583984959074203 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Marshall Islands | 0.5923968276529713 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Seychelles | 0.5933297352264011 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Solomon Islands | 0.5992036897764201 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Barbados | 0.6026798894629409 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Bahamas | 0.6037901963467236 | f | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Mauritius | 0.6124306023780004 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Jamaica | 0.6180866747206792 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Ireland | 0.6208333769699201 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": "United Kingdom (Northern Ireland)"}
Grenada | 0.6314871259473416 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Palau | 0.6331963890581136 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Kiribati | 0.6340927146328686 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Cyprus | 0.6354914993816243 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Maldives | 0.6368475975326672 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Malta | 0.6370369487669678 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
New Zealand | 0.6520422783711599 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Antigua and Barbuda | 0.6560949198973425 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Fiji | 0.6563765953128096 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Saint Lucia | 0.6565374364556272 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Saint Kitts and Nevis | 0.6569025217334334 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Madagascar | 0.6579842393491677 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Nauru | 0.6588623575239401 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Belize | 0.6617206536439253 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Mexico, Guatemala"}
Cape Verde | 0.6656987305382893 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Guyana | 0.6690943980832992 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Brazil, Suriname, Venezuela"}
Micronesia | 0.6694775662174064 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Sri Lanka | 0.6755248175409809 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Andorra | 0.6756446018762446 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "France, Spain"}
Israel | 0.6763307658990072 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Lebanon, Syria, Jordan, Egypt, Palestine"}
Cuba | 0.6780035821480603 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Tonga | 0.6854439801282708 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Vanuatu | 0.6898198008050471 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Dominican Republic | 0.6930591990187845 | f | {"land": true, "water": true, "island": true, "continent": false, "neighbours": "Haiti"}
Saint Vincent and the Grenadines | 0.6936146574365081 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Singapore | 0.6939824470932374 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Samoa | 0.6942054226212125 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Haiti | 0.6970099016123408 | f | {"land": true, "water": false, "island": true, "continent": false, "neighbours": "Dominican Republic"}
Indonesia | 0.6982757826983539 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Malaysia, Papua New Guinea, Timor-Leste"}
Bahrain | 0.7010005561641666 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Comoros | 0.7067622419634335 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Estonia | 0.7085149756741925 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Latvia, Russia"}
Liechtenstein | 0.7098232671398083 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Austria, Switzerland"}
Tuvalu | 0.7100676352558253 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Brunei | 0.710790435672632 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Malaysia"}
San Marino | 0.7188084142495059 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Italy"}
Vatican City | 0.7195959897300721 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Italy"}
Suriname | 0.7227729682040444 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Brazil, Guyana, French Guiana"}
Sao Tome and Principe | 0.7228708359839435 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Guinea-Bissau | 0.7250435443354935 | f | {"land": true, "water": true, "island": false, "continent": false, "neighbours": "Senegal, Guinea"}
Trinidad and Tobago | 0.7255906511994212 | t | {"land": true, "water": true, "island": true, "continent": false, "neighbours": ""}
Greece | 0.7294355944328467 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Albania, North Macedonia, Bulgaria, Turkey"}
Qatar | 0.7311974837740045 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Saudi Arabia"}
Bhutan | 0.7334687504057431 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "India, China"}
Monaco | 0.7340602079110607 | f | {"land": true, "water": true, "island": false, "continent": false, "neighbours": "France"}
Iraq | 0.734913671407935 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Turkey, Iran, Kuwait, Saudi Arabia, Jordan, Syria"}
Kuwait | 0.7369097472864523 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Iraq, Saudi Arabia"}
Denmark | 0.7379886532695916 | f | {"land": true, "water": true, "island": false, "continent": false, "neighbours": "Germany"}
Slovenia | 0.7381978525807786 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Italy, Austria, Hungary, Croatia"}
Liberia | 0.7392671571539429 | f | {"land": true, "water": true, "island": false, "continent": false, "neighbours": "Sierra Leone, Guinea, Côte d'Ivoire"}
Djibouti | 0.740412556074715 | f | {"land": true, "water": true, "island": false, "continent": false, "neighbours": "Eritrea, Ethiopia, Somalia"}
Oman | 0.7413224461946565 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Saudi Arabia, United Arab Emirates, Yemen"}
Somalia | 0.7449227200702471 | f | {"land": true, "water": false, "island": false, "continent": false, "neighbours": "Djibouti, Ethiopia, Kenya"}
Gabon | 0.7456118851623608 | f | ... (truncated)
Refresh a single tenant across accounts with Aurora MySQL and Amazon S3
Resilient IAM database authentication with Amazon Aurora Global Database writer endpoint
Building a multi-tenant CRM with Amazon Aurora DSQL
Ten ways a glibc change can break your PostgreSQL cluster
Between any two glibc versions, locales can change, and those changes alter the order of character string comparison (text/varchar/char/citext). It may also happen that locales are removed, such as en_US@ampm, present in RHEL8 and removed in RHEL9, and aa_ER@saaho, present in RHEL9 and then renamed to ssy_ER in RHEL10. Since PostgreSQL relies on locales to … Continued
The post Ten ways a glibc change can break your PostgreSQL cluster appeared first on Percona.
How Oracle changed the future of MySQL: Dominic Preuss on Inside the Silicon Mind
Build an Oracle AWR-style report for RDS/Aurora PostgreSQL
Help prevent out-of-memory restarts on Aurora MySQL 8.4
Diagnose Amazon Aurora MySQL performance using CloudWatch Database Insights
Automate Aurora zero-ETL recovery after global failover
How we migrate TBs from Heroku without logical replication
November 10, 2026
Heavy MySQL Reports in Seconds: Open Source HTAP with DBTrail and Percona Server for MySQL
In Part 1 we ran analytical reports in DuckDB on a DBTrail copy instead of on MySQL. This time I want to look at a different question: what if the application does not need to know the copy exists? Most MySQL servers have a few queries that make someone wait. The monthly report that aggregates … Continued
The post Heavy MySQL Reports in Seconds: Open Source HTAP with DBTrail and Percona Server for MySQL appeared first on Percona.
Set up Supabase for every Expo environment in one command
November 09, 2026
Beyond the Version Bump: Running PostgreSQL Major Upgrades in Percona Operator for PostgreSQL
It was quite challenging to perform postgres major upgrade, e.g., v17 to v18, in a Percona operator/K8s environment. There was no convenient approach to accomplish this task. The only viable solutions left were a Manual Dump/Restore to a new environment or using Logical replication. But these options were not free from challenges. Procuring a new … Continued
The post Beyond the Version Bump: Running PostgreSQL Major Upgrades in Percona Operator for PostgreSQL appeared first on Percona.
November 08, 2026
RIP, Harvest
I’ve been a self-employed contractor for about a decade now, and I’m bummed about Harvest.
Harvest was a time-tracking and invoicing service. You’d track how many hours or weeks you spent on a project, put together an invoice, email it to the client, and close it out when they paid. There was a little Stripe integration for accepting credit and ACH payments on the invoice web view. A historical chart of total billed/paid amounts over time, and a table of invoices. Harvest was exactly what you want from small-business software: it solved a well-defined problem with a reliable, simple interface, and did so at a reasonable monthly price. They managed to avoid scope creep and never tried to become an accounting platform, or a blockchain for washing machines.1 I loved Harvest and raved about it to every self-employed person I met.
Back in 2025 Harvest got bought out by Bending Spoons, the company that gutted Vimeo. I was worried they might ruin it, but was busy with other things and didn’t have time to look for new invoicing software.
Long story short, they raised my price from $13.32 to $133.98 per month by moving everyone to a new “enterprise” plan and “unlimited” usage billing model, which is AFAICT, exactly what they offered before for a tenth of the price. There’s also a “flex” plan which costs $12.65/mo… but that plan now skims an additional 2% on top of the usual ~3% card transaction fees. To be clear, that 3% pays for the whole credit card network, plus all the people working at Stripe and Mastercard, plus the financial risk of chargebacks and fraud, etc. Harvest isn’t doing any of that—it’s just a web form handing off data to Stripe.
I wrote to Harvest about this, and discovered that they don’t read customer emails any more. Instead they have a Large Language Model repeatedly vomit thousands of words at you. I’m no stranger to enshittification, but man, I did not anticipate the speed at which Bending Spoons would drive a perfectly good company into the ground.
Time to find a new invoicing service, I guess. Maybe bookkeeping too—Intuit’s Quickbooks Online seems to have completely jumped the shark. Any of y’all freelancers have a system you like?
-
Yes, someone actually tried this.
↩
November 07, 2026
Next-Level Benchmarking Overfitting
In my recent blog post I remarked that database benchmarking is in a crisis because AI agents start to overfit on individual benchmarks with questionable techniques, which drives execution times towards zero. This has happened with TPC-D in the past, and broke the benchmark. TPC-H tries to fight that with rules, but most people who publish (unofficial) TPC-H results simply ignore the rules. In combination with overly eager AI agents, this leads to brutal overfitting.
We are seeing the next level of that with the GenDB-V system. The corresponding paper commands “Stop Handcrafting Your Engine! GenDB-V: Towards AI-Generated and Formally Verified Database Systems”, and reports speedups of 22× to 61× over Umbra and DuckDB, and all that in a fully verified system. These are indeed impressive claims, and would suggest that traditional database engines are in trouble.
Taking a step back, though, it should be clear that a performance improvement of a factor of 22 over Umbra is infeasible, at least if we are talking about traditional query processing. At SF10, the 22 TPC-H queries read roughly 15GB of (compressed) base data, and on a 9950X3D Umbra runs all queries in a total of roughly 540ms. On that machine the memory touched for data divided by the memory bandwidth (54GB/s) alone is about 280ms; anything below that is infeasible. Better compression might bring that down a bit, but not by an order of magnitude.
Unsurprisingly, the most dramatic gains of GenDB-V come from
precomputation. The agents have carefully handcrafted auxiliary data
structures for each query, which are computed at load time, and which
avoid most of the work. The physical design is heavily tuned to the
queries, too, for example, there is an extra structure that lists the
supplier rows in s_acctbal DESC order, which
makes Q2 trivial. Similar per-query data structures are
built for most queries. And we see that in the performance numbers:
Several queries already run in near-constant time at SF100: Q2 in 0.7
ms, Q17 in about 15 ms and Q4 in about 17 ms on one thread. That is the
TPC-D symptom, without stored aggregates.
There is also a “plain-array” mode, which, at first glance, is more
fair, as it avoids these excessive preparations and also reports
performance improvements of just roughly a factor of 3, which seems
achievable. But when looking closely, we see that here the agents did
something even more insane: They hard-coded
their knowledge about the data generator. They “know” that each customer
k will be at row k-1. And they know that dbgen
numbers the i-th order 32·(i/8) + i%8, so key
k should be at row 8·(k/32) + k%32 − 1. Which
makes the join in query Q18 super cheap.
How does all this fit together with the claim of a fully verified database engine? Well, the code does check if the hard-coded knowledge about the data generator holds in practice, e.g., by checking if the guessed join partner row is correct. If not, it falls back to a super slow generic nested loop implementation, but as that never happens with the official TPC-H data generator, that code path is irrelevant in the reported experiments.
Which brings me back to my original blog post claim: Letting AI agents optimize for a given database benchmark makes the benchmark useless. They will do their best to find insane tricks to speed up this particular benchmark, but none of that would work in a real system. The high tuning and synthesis time aside, deleting a single tuple (or inserting one in the middle) can most likely cause the whole approach to fall off a cliff, as it forces the slow fallback path. Which is not something you want in a production system.
Writing as a thinking tool
In middle school I could not get through long passages no matter how hard I tried. I would read an entire page and find that nothing had registered, so I started taking notes out of desperation. This was the late 80s, and Tony Buzan's mind maps had just reached Turkey. This led me to produce pages and pages of notes that looked like spiderwebs. Creating those notes have helped me tremendously. I made sense of what I was reading by figuring out which branch they would hang off in my mind maps. I realized my pages were not for storing the information as much as they were for processing it.
It took me another ten years to understand why this works so well. During a stretch of PhD procrastination, I read Manuel Blum's advice to graduate students:
You are all computer scientists.
You know what FINITE AUTOMATA can do.
You know what TURING MACHINES can do.
For example, Finite Automata can add but not multiply.
Turing Machines can compute any computable function.
Turing machines are incredibly more powerful than Finite Automata.
Yet the only difference between a FA and a TM is that
the TM, unlike the FA, has paper and pencil.
Think about it.
It tells you something about the power of writing.
Without writing, you are reduced to a finite automaton.
With writing you have the extraordinary power of a Turing machine.
This is a crisp technical claim about which problems you can solve with writing. If you take the paper away, you downgrade yourself by an entire complexity class!
I think this explains a lot about human history as well. Language let us externalize some of our thinking, and our species got smarter. Writing let us externalize even more of it, and we got smarter again. I suspect this is also why LLMs are as capable as they are: so much of our intelligence is already deposited in language that a model trained on it inherits a good deal of intelligence for free.
Out-of-core execution
When we write, we are externalizing our thinking onto paper, and that is what raises our capability to think bigger thoughts. I liken this to out-of-core execution, or external memory algorithms.
A good example of this for me is reviewing a paper. When I read a paper, I think I understand it, and then I sit down to write the review and find out I understood maybe 60% of it. While writing the review, I end up contextualizing the paper better, and get to understand it fully. And sometimes I even realize the ideas are bigger than the authors noticed, and they connect to many other things.
Sometimes the unearthing goes further than understanding one paper. In 2018, I wrote a quick post about the anatomical similarities between Paxos and Nakamoto consensus. I had not started with that thesis originally. I started writing, and the connections got clearer as I went, and that post became the seed for one of our research papers. This is not uncommon, this is called freewriting. This is what happens when you give the machine a tape.
Writing is also the reason I remember anything. I refer back to my own blog constantly. Because I strained my brain writing those posts, rereading them re-fires the same neurons, and I reconstruct the understanding quickly. In this sense, the blog became my external memory. The thoughts I never wrote down do not stick well, because they were never fully thought.
If you think without writing, you only think you're thinking.
-- Leslie Lamport
Writing in the age of LLMs
Is writing for thinking getting obsolete now since you can now argue anything with a model that has read everything?
Well, the model has read everything except what is in your brain! It knows the literature, the standard framing, and the consensus. But it does not know your half-formed intuition about why the standard framing may be wrong for this particular case. AI made a lot of progress, but I don't trust an LLM to take the creative leap and make a new connection. I think that would require another AI breakthrough. So, if you open the chat immediately on anything, you lose the opportunity to write/think for yourself.
This is a real problem, and it seems to be spreading quickly. We have been celebrating Amazon's six-pager as a great example of writing-for-thinking. Bezos had said: "There is no way to write a six-page, narratively structured memo and not have clear thinking." Unfortunately, with LLMs, that sentence has stopped being true. You can now produce six pages with no thinking at all, and the six pages still look like evidence that the thinking happened.
A model optimizing next-token probability cannot do your thinking. If you trust it to hold the pen, no thinking would get done. As everyone turns their thinking down and becomes a meat-proxy for generated text, if you still keep writing yourself, you would still be thinking for yourself. So writing is now a comparative advantage, and a widening one!
Anyways, enough about why you should write. Here is my advice on how you should write.
Rule 1: Mess up
The first rule of writing club is to produce in bulk with no restraints at all. Don't criticize, don't fix the typos, don't write in full sentences if you don't feel like it. The goal in this phase is to just accumulate text and get to a zeroth draft you can work with.
McPhee argues best for why getting anything down matters so much. Even if you had a bad writing session, it gave your mind something to work with. After you get something down, you can spot problems with it and think of better ways of phrasing things. But none of that happens without the drafted version to react against.
This is also how research papers get written. Open a notes.org file and treat it as a lab notebook: write while you do the work, explore every tangent, produce without any pressure to be presentable. In the Köroğlu post I compared this to the foal fattened in a pitch-black stable before any training begins. The key part of that frame is the stable. During this phase you guard the idea from critics, including and especially yourself. Ideas start out fragile and easily squished. So suppress judgment for a while and see what the thing becomes.
Rule 2: Tidy up
Now you must become the critic, the editor. Forget that you wrote this. Imagine some other guy wrote it, and do a harsh review. Cut ruthlessly. Take delight in deleting text the way you would in deleting code. If you cannot bear to delete a good sentence, move it to a junk buffer at the bottom of the file so you can tell yourself you might bring it back. Spoiler: you won't.
Don't be discouraged by how bad the first draft was. McPhee says the way to do a piece of writing is three or four times over, never once. This is good news. Thinking in multiple drafts takes the pressure off: you don't have to be spontaneous, brilliant on the first pass, or finished today.
I think the reason writing works so well for thinking is that it slows you down while keeping you on the problem. In that extra time, the insights and connections get room to surface. Chunking/abstracting happens in the background. Editing is then the magic trick where you pretend you had an organized thought all along. It is the packaging of the product.
Rule 3: Keep it simple, plain, and succinct
I am an Elements of Style guy. Strunk and White got it right, and their writing still reads fresh decades later. I adore Vonnegut too. His writing is simple as fuck and completely eloquent. It took a lot of balls to write that plainly. Complexity in prose is usually a place to hide sloppy thinking, or a way to weasel out of taking a stance. If you can't say it plainly, you probably haven't thought it through yet. And the way you think it through is by writing/editing.
Rule 4: Write without expectations
Write for yourself. The audience I have in mind is me. If others read it, that is a bonus, but the motivation has to be intrinsic, because the external rewards are far too delayed and too random to steer on. Writing like no one is watching also frees you up, and you can decide afterwards how much to share. Get your dopamine hit from the writing itself, not from people reading or sharing what you wrote, as that is very unreliable.
I have been writing and sharing on this blog since 2011, and around 2016 I started running into people at conferences who read it. I assumed it was a fluke, but it kept happening. Writing with a human voice turns out to get a lot of attention, and I am still surprised by how much. Paradoxically, you seem to get an audience only if you weren't doing it for the audience.
Rule 5: Put it on the schedule
You should stop trying to get inspiration to write and start scheduling it. Waiting for inspiration doesn't work, instead, putting your butt on seat and writing breeds good ideas for writing.
I find that keeping a posting schedule for the blog keeps me in "a writing frame of mind", which is really "a noticing frame of mind". Many weeks I tell myself a topic isn't interesting, but I start writing about it anyway, and then, what do you know, I am startled by the fount of interesting things that surfaces.
I also feel relieved after writing something as the thought is out of my system, I have closure on it, and I have room to think new thoughts. If a week goes by without a post, I feel clogged. So put writing on the calendar and keep it there until it no longer needs to be on the calendar.