a curated list of database news from authoritative sources

November 15, 2026

November 13, 2026

Fix Amazon Aurora MySQL error 63955: Virtual index ID overflow

An Amazon Aurora MySQL cluster can exhaust its virtual index ID limit, after which any CREATE INDEX that includes a virtual column fails with error 63955 until you rebuild the databases. This post shows how to recognize the error, why backups cannot clear it, and how to run a logical dump-and-restore migration with monitoring to catch it next time.

Improve RDS Proxy multiplexing for PostgreSQL, Part 1: Common causes

RDS Proxy multiplexes many client connections across a small pool of database connections, but connection pinning, pool fragmentation, and uncommitted transactions can silently disable that benefit for PostgreSQL workloads. Part 1 shows how to recognize each condition with Amazon CloudWatch metrics and the fix that restores multiplexing.

Improve RDS Proxy multiplexing for PostgreSQL, Part 2: Diagnose and monitor

Metrics alone can't always pinpoint what is reducing RDS Proxy multiplexing for PostgreSQL. Part 2 shows how to use Amazon CloudWatch Logs Insights with enhanced proxy logs to find the root cause, what healthy multiplexing looks like in metrics and logs, and how to set up CloudWatch alarms that catch regression.

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

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.

Announcing Hegel-clj

I’ve just released hegel-clj, a Clojure interface for the Hegel generative testing library. You can jump right to the docs, but I thought it might be interesting to explain why I want another testing library.

I do a lot of generative testing in Clojure with the excellent test.check. However, using test.check can be frustrating! In particular, complex generators can get stuck during shrinking, leaving me trying to prune down a complex data structure by hand. The culprit is usually bind. If you’re not familiar with test.check, Quickcheck, or other libraries in this family, bind is a generator which draws some random value, then uses that to construct a new generator, which in turn is used to draw some other value.

(require '[clojure.test.check :as tc]
         '[clojure.test.check.generators :as tcg]
         '[clojure.test.check.properties :as tcp])
(tcg/bind ; Take a number from `nat`, a generator of small positive
          ; integers.
          tcg/nat
          ; Now with that number, construct a generator of vectors, each
          ; with that many elements.
          (fn [size]
            (tcg/vector tcg/large-integer size)))

We can also write this using test.check’s let macro, which expands to a series of nested binds under the hood.

(tcg/let [size tcg/nat]
  (tcg/vector tcg/large-integer size))

This leads to the canonical example of poor shrinking in test.check. Let’s try to find a vector containing 42:

(-> (tc/quick-check 1000
      (tcp/for-all [xs (tcg/let [size tcg/nat]
                         (tcg/vector tcg/large-integer size))]
                   (not-any? #{42} xs))
      {:seed 1777986545686})
  :shrunk :smallest first)

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42 0]

That does contain 42, but this vector is enormous. Why are there all these zeroes? Once test.check found this vector, surely it could have shrunk it to just [42], right?

The answer has to do with shrink trees. Test.check generators don’t actually return values like 64. Instead they return a lazily evaluated tree of values: the root might be 64, and its children might be [32 48 ... 63], and each of those children has further children: 32 has children [16, 24, ... 31], and so on. Simplifying a bit, here’s how the tree for large integers is defined:

(defn- shrink-long
  "Given a number x, returns a coll of (x-x, x-x/2, x-x/4, x-x/8, ..., x-1)"
  [^long x]
  (core/let [size (- 64 (Long/numberOfLeadingZeros (Math/abs x)))
             ^longs vals (long-array size)]
    (loop [i 0 n x]
      (if (zero? n)
        vals
        (do
          (aset vals i (- x n))
          (recur (inc i) (quot n 2))))))))

So, if test.check finds that 64 violates some property we’re testing, it tries the first child of 64, 32. Let’s say that passes the test, so test.check tries the next child of 64: 48. If 48 fails, test.check tries its first child 24, and so on. By performing this depth-first search of the tree, and choosing the branches cleverly, test.check can often quickly shrink an integer to a smaller one that still fails the test.

A similar strategy applies to fixed-size vectors. The tree for a vector like [2 2] has children like [1 2] and [2 1], based on the trees for each individual element of the vector. This explains why test.check produced a vector like [0 0 0 ... 42 0]: it found something like [91 23 1034543 ... 42 15], then shrank the first element to zero, then the second, and so on. The only element it couldn’t shrink was the one that caused our property to fail: 42. Because our generator (tcg/vector tcg/large-integer size) always used a fixed size, the tree for that vector always contained size elements—in this case, 39.

Hang on! We didn’t just generate fixed-size vectors. We used bind to draw a size, then built a vector of that size:

(tcg/bind tcg/nat
          (fn [size]
            (tcg/vector tcg/large-integer size)))

What kind of trees does bind produce? The code involves some gnarly higher-order functions, but in essence bind’s trees can shrink in two ways. They can shrink the outer value (the size returned by tcg/nat), or they can shrink the inner value (the vector returned by (tcg/vector ...).

Now imagine bind draws a size of 100, then generates a vector of a hundred elements like [15 32 42 ...]. This contains 42, so test.check begins hunting through the shrink tree. One of the branches it can take shrinks the size parameter, starting with [50, 75, ... 99]. But if bind picks any of those sizes, the vector [15 32 42 ...] is useless—it’s the wrong size! So bind calls our function with 50 to construct a generator of 50-element vectors, and generates a single vector from that generator. This vector will have different random elements, like [69, 45, 1, ...].

If there’s no 42 in the vector of 50 elements, then test.check abandons that branch and moves on to size 75. It draws a new vector of 75 elements like [8 7 42 ...], and since that contains 42, it can continue shrinking. But since 42 is an uncommon integer, and test.check draws new elements for each size, it is very likely that most sizes will not produce a vector with 42, and test.check will get stuck with, say, 39 elements. From there it can shrink each element, producing the comically oversized…

[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 42 0]

For this vector it’s fairly obvious what’s wrong. But for nontrivial test.check generators, there might be a dozen nested levels of bind with complex downstream consequences. Every one of them offers a place for test.check to get stuck, and the smallest failing case can be devilishly tricky to understand. Experienced test.check users try to avoid bind and let—but they’re fundamentally inescapable for many problems.

Over in the Python world, the Hypothesis team took a very different approach to shrinking. Test.check is fundamentally a two-phase system: first it asks a generator to produce a shrink tree. Then, if the root of that tree looks “bad”, it explores the rest of that tree. Instead, Hypothesis generators produce a single value, and if that value looks bad, they re-run the generator and ask it to produce something similar, but smaller. This approach, called internal test-case-reduction, is the foundation of Hegel.

Hegel-clj

Hegel is a new cross-platform library for generative testing. It provides deterministic generators which draw random values from an imperative, structured RNG, as well as a rich family of special values and shrinking tactics. Writing these generators and shrinkers is hard. The idea is that Hegel’s core library will do this hard work once, and simpler wrapper libraries will extend the benefits of that hard work across many languages.

I’ve been working for a few months on hegel-clj, which offers Clojure bindings for Hegel, and I think it’s ready for the folks to try it out. To test something, you call hegel-clj/test!, draw some values from generators, and throw an exception if the test fails. Hegel searches for values that make that body throw, then shrinks them.

(require '[hegel-clj [core :as h] [generator :as g]])
(h/test! {:seed 4}
  (assert (even? (h/draw! (g/integer)))))

{:passed? false
 :status :failed
 :statistics {:total 34
              :valid 5
              :invalid 0
              :overrun 18
              :interesting 11}
 :health-check-failed? false
 :failures ({:draws {"draw_1" 1}
             :exception
             #error {:cause "Assert failed: (even? (h/draw! (g/integer)))"
                     ...}})
 :reproduce-blob "AAEAAAAACgEAAAAB", :caveat nil}

The smallest case Hegel found involved a single draw of the number 1, which caused the assertion (even? 1) to throw.

Generators are pure, composable structures, just like in test.check. Here’s a vector of booleans:

(h/sample 5 (g/boolean))

[false false true true false]

Or a map representing a person, with a name generated by a regular expression and an integer age:

(h/sample 5 (g/hmap {:name (g/regex-str #"[A-Z][a-z]+")
                     :age  (g/integer 0 101)}))

[{:name "Aa", :age 0}
 {:name "Iyjdklppzxhqdnwmd", :age 89}
 {:name "Kslvqorm", :age 64}
 {:name "Fvyybe", :age 45}
 {:name "Puwppxqag", :age 4}]

The higher-order combinators are all there too. You can transform generated values with fmap:

(h/sample 5 (g/fmap keyword (g/string)))

[: :R :👍🏻 :True :çç¦ç¦򜝉ÏÏ򜝉¦¦Ï¦¦ç򜝉Ϧç򜝉çÏ򜝉çç¦]

Or build one generator out of another using bind:

(h/sample 2 (g/bind (fn [size]
                      (g/tuple (g/vector size (g/integer))
                               (g/vector size (g/boolean))))
                    (g/integer 0 3)))

[[[] []]
 [[144229 531621 -364 -1569883 8194954 -913 16 -745]
 [false true false true]]]

Like test.check, we offer a let macro which lets you bind both regular values or generators to variables; if the right-hand side is a generator, it draws a random value from it. And unlike test.check, these generators shrink well. Instead of thirty-nine elements, Hegel can find the much-shorter “bad” vector, [42].

(-> (h/test! {:test-cases 1000
              :seed       1777986545686}
      (h/let [size (g/integer {:min 0, :max 128})
              xs   (g/vector {:size size} (g/integer))]
        (assert (not-any? #{42} xs))))
    :failures first :draws (get "xs"))

[42]

With test.check, if you want to thread multiple values through bind or fmap, you have to build and destructure maps or tuples. Hegel-clj lets you lift arbitrary code into a generator using composite, which makes it easier to build complex generators:

(h/sample 2
  (g/composite
    (h/let [name      (g/string)
            cat-count (g/integer 0 2)
            cats      (g/vector {:size cat-count, :unique? true} (g/string))]
      {:name name     
       :cats cats})))

[{:name "",
  :cats []}
 {:name "𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠",
  :cats ["🇺🇸" "👍🏻"]}]

You can build recursive generators with a promise-style API. The deferred generator provides a blank space for a generator, which can be filled in later:

(let [leaf (g/integer 0 9)
      tree (g/deferred)]
  (g/set-deferred! tree (g/one-of [leaf (g/vector tree)]))
  (mapv prn (h/sample 5 tree)))

0
[[1] 8 [2 []] 5 [] [[4 6 []]]]
[[1] 2]
3
0

And best of all, hegel-clj ships with easy-to-use integration for clojure.test, making generative tests as easy as example-based ones:

(require '[clojure.test :refer :all] '[hegel-clj.clojure-test :refer [with]])

(defn bad-add
  "Adds two integers via floating-point"
  [x y]
  (long (+ (float x) (float y))))

(deftest bad-add-test
  (with {:test-cases 100}
        [x (g/integer)
         y (g/integer)]
    (h/fprn :x x, :y y)
    (is (= (+ x y) (bad-add x y)))))
    
(bad-add-test)

:x 0 :y 16777217

FAIL in (bad-add-test) (form-init7301103820420620476.clj:6)
expected: (= (+ x y) (bad-add x y))
  actual: (not (= 16777217 16777216))

It’s hard to log debugging output with test.check—you’ll have to sift through thousands of test runs with complex inputs, and many of those runs won’t actually trigger the bug you’re searching for. Hegel has the notion of a “final” test case: after it’s found a counterexample, and shrunk it to a smaller version, it comes back and re-runs that case as a part of the final phase. We can use (when (h/final?) ...) to execute some debugging code only during these final cases, or fprn and finfo, which wrap clojure.core/prn and clojure.tools.logging/info respectively. I think there’s a bug in Hegel that causes it to run more final cases than it should, but it’s still a big step up.

Finally, test.check has been around for a while, and like many Clojure projects it has an exceptionally stable API. That’s great for compatibility, but it means some early choices are now fossilized. It’s (fmap gen f), but (bind f gen). If you want an integer you have to remember the difference between nat, int, pos-int, s-pos-int, small-integer, large-integer, large-integer*, etc. Common generators require keyword arguments, like (large-integer* {:min 0, :max 10}). Some generators like hash-map are only callable with varargs, which makes composition tricky. The properties API feels a tad fussy. And many of the ergonomic features you want, like generating strings from a regular expression, or integration with clojure.test, require an extra library like test.chuck. Test.check is really good, but with the benefit of hindsight and a fresh canvas, I’ve tried to design a more streamlined, consistent, and approachable API.

I’ve used test.check for over a decade now and it’s one of my favorite things about Clojure. I want to thank Reid Draper, Gary Fredericks, Alex Miller, and everyone else who’s contributed to the library. I’m also indebted to David MacIver, Liam DeVoe, and Esau Rodriguez from the Hegel team at Antithesis. They took time to talk me through Hegel’s design, answered endless questions about the protocol, and helped fix a whole slew of issues that we found. Y’all rock.

I’m pleased to announce hegel-clj 0.1.1, and I’m starting to use it to test my own projects. I hope it will be useful for you too. ❤️

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.

    ↩