a curated list of database news from authoritative sources

November 12, 2026

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

Build a fully serverless recipe-sharing REST API in Go on Amazon Aurora DSQL, AWS Lambda, and Amazon API Gateway. Learn UUID primary keys for even write distribution, short-lived IAM token authentication, application-layer referential integrity, and automatic optimistic-concurrency retries, with a one-command CloudFormation deployment.

I asked Convex's CTO 34 spicy questions

I sat down with James Cowling, co-founder and CTO of Convex, and fired 34 spicy questions at him. These are the answers I keep coming back to, on learning to code, junior devs, vibe coding a production database, code review, hiring and build vs buy.

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 return true for countries that are islands and false for 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

Move a single tenant's data between Amazon Aurora MySQL clusters in two separate AWS accounts with no network route between them, using Aurora cloning, SELECT INTO OUTFILE S3, and LOAD DATA FROM S3. The clusters never connect, and one encrypted Amazon S3 staging bucket is their only shared touchpoint.

Resilient IAM database authentication with Amazon Aurora Global Database writer endpoint

Learn how to combine the Amazon Aurora Global Database writer endpoint with IAM database authentication so that passwordless access survives a cross-Region switchover or failover with no configuration changes. This walkthrough signs the token for the dynamically discovered primary Region and scopes the policy to every Regional cluster resource ID.

Building a multi-tenant CRM with Amazon Aurora DSQL

Learn how to design and implement a multi-tenant CRM on Amazon Aurora DSQL using the pooled tenancy model. Tenant isolation is enforced in a centralized data-access layer, with Aurora DSQL-specific design considerations, and a working proof of concept you can deploy on Amazon ECS Express Mode.

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.

Build an Oracle AWR-style report for RDS/Aurora PostgreSQL

PostgreSQL has no built-in Oracle AWR equivalent. This post shows how to build an AWR-style, snapshot-based performance report for Amazon RDS for PostgreSQL and Amazon Aurora PostgreSQL using pg_stat_statements and pg_cron, producing top-SQL, database-activity, wait-event, and load-profile reports between any two snapshot IDs, entirely in SQL.

Help prevent out-of-memory restarts on Aurora MySQL 8.4

Out-of-memory restarts drop every connection and force an Aurora MySQL cluster restart. Learn how Aurora MySQL 8.4's automatic memory management prevents them: confirm protection is active, read the signals during a memory-pressure event, monitor OOM avoidance with CloudWatch, and decide when to take manual control.

Diagnose Amazon Aurora MySQL performance using CloudWatch Database Insights

When an Aurora MySQL or Amazon RDS for MySQL database slows down, it can be hard to tell why. This post shows how to read DB load in Amazon CloudWatch Database Insights, interpret the most common MySQL wait events, and turn each into a concrete action, so you can fix the real bottleneck instead of scaling up to a larger instance.

Automate Aurora zero-ETL recovery after global failover

After an Amazon Aurora Global Database switchover or failover, the Aurora zero-ETL integration with Amazon Redshift keeps referencing the former primary and stops ingesting. Learn how to automate integration recreation with Amazon EventBridge and AWS Lambda so your analytics pipeline recovers without manual intervention.

How we migrate TBs from Heroku without logical replication

We've equipped our customers with an open source tool that not only quickly transfers your data but also provides a replication-based experience, so that any user can migrate from Heroku without extended downtime or the need to purchase a third-party product.

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.

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?


  1. 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.

November 06, 2026

MongoDB 9.0 vs. 8.0 vs. 8.3: A Quick First Look at Performance

In my previous post I said that MongoDB published some impressive numbers for 9.0, but I couldn’t find any details about how the tests were done. I ended with “always run your own benchmarks”. So I figured I should follow my own advice. Let me be clear up front: this is a first, quick test, … Continued

The post MongoDB 9.0 vs. 8.0 vs. 8.3: A Quick First Look at Performance appeared first on Percona.