November 15, 2026
November 13, 2026
Fix Amazon Aurora MySQL error 63955: Virtual index ID overflow
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.
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 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.
↩