www.bortolotto.eu

Newsfeeds
Planet MySQL
Planet MySQL - https://planet.mysql.com

  • Crafting an Extension Framework for MySQL: a talk from MySQL BR Conf 2026
    I opened my MySQL BR Conf 2026 talk creating a new data type that stock MySQL doesn’t have: ROT13. … OK, admittedly, it’s a silly type. With a ROT13 column, you insert plain strings and read back plain strings, but the table stores scrambled text. … OK, it’s very *very* silly. But the point of this example isn’t to convince you extensions are useful; it’s to show a tiny extension that allows exploring design choices we made in designing the VEF (VillageSQL Extension Framework).What the talk coversSimpler functions and aggregates. VDF’s (VEF-Defined Functions) resemble MySQL UDF’s (User-Defined Functions), but are easier to write. I explore how we made it both more ergonomic in C++ and also how we make it harder to mess up.Types that take parameters. Some types take parameters; e.g., vectors need a length. To show how to handle this in our existing silly example, I generalize ROT13 into ROT(K).The escape path. I show how functions that don’t have tidy, compile-time signatures like printf fit into the VEF. I also describe our design pattern for cases like these. In the talk, I link to a blog post on this blog, which I will also now link here in an example of Linkception: “Happy Path, Escape Hatch, and the Space Between”.Porting a PostgreSQL extension. The talk ends with what happened when VillageSQL's Claude Code skill ported PostgreSQL's hstore extension, and what the port cost.About Daniel BentleyDan is a founding engineer at VillageSQL. He has worked at Google and Twitter, and he did a startup called Tilt that focused on Kubernetes developer tools. He is on a quest to make databases delightful to use by translating complex systems into elegant simplicity.Get started at villagesql.com, read the docs, or talk to us on Discord.

  • MySQL MCP Server v1.8.0 is out
    On paper it’s a boring release. No new tools, no new flags, nothing to reconfigure. In practice it’s one I’m glad I didn’t skip: it closes 37 known vulnerabilities that the server could actually reach, and I found them more or less by accident. It started as a compatibility check I had MySQL 8.4, 9.7 and 26.7 running on my test box, plus MariaDB 12.3. So I pointed the MCP server’s test suite at all of them. Mostly to tick a box before moving on to something more fun. The code was fine. Every version passed. MySQL 26.7 and MariaDB 12.3 join the matrix Every pull request now runs the full integration suite against six servers: MySQL 8.0, 8.4, 9.7 and 26.7 MariaDB 11.4 and 12.3 26.7 is the first Innovation release under Oracle’s new calendar versioning. If you follow MyVector, you know I’ve been chasing it there too. MySQL 9.0 is out of CI. It was a short-lived Innovation release, and 9.7 covers that line now. MySQL 8.0 is staying. Yes, it reached end of life in April. No, I’m not dropping it. Plenty of you are still on 8.0 and haven’t moved to 8.4 yet, and I’d rather keep testing it than pretend otherwise. One more thing turned up along the way. The Sakila tests weren’t running in CI at all: the sample database was never loaded, so they skipped quietly. They run now, on every version. Green CI, 37 vulnerabilities CI was not fine. The vulnerability scan and the linter had been failing quietly for a while. Both were set to informational, so nothing ever turned red. Once the scan ran again, it found 37 vulnerabilities in code the server actually calls. Most were in the Go 1.24 standard library, the rest in the SSH and MCP SDK dependencies. v1.8.0 fixes all of them: Go 1.24 → 1.26 golang.org/x/crypto v0.43.0 → v0.58.0 MCP Go SDK v1.2.0 → v1.8.0 If you use the SSH tunnel: that library jumped 15 minor versions, so I tested it through a bastion before tagging. It still connects, and it still refuses a server whose host key doesn’t match known_hosts. And since the release, govulncheck blocks merges and releases, so this can’t quietly happen again. Release candidates stay out of Homebrew This one’s a bit embarrassing. When I cut v1.7.1, all four release candidates went straight to the Homebrew tap. If you ran brew upgrade that week, you got an RC. Sorry about that. RCs now only show up on the GitHub releases page. brew upgrade only ever gives you a final release. v1.8.0 went out the new way. rc.1 first, smoke-tested against real 8.4, 9.7, 26.7 and MariaDB 12.3 servers, then the final tag. Homebrew users never saw the RC. Things I learned the hard way Two, mostly. First, don’t write [skip ci] in a commit message. Not even when you’re describing a different commit that had it. GitHub doesn’t read for context. My PR sat there with no CI run at all while I wondered what was going on. With a required check on main now, that PR would have stayed stuck until I reworded the commit. Second, the AI code reviewers earned their keep this time. CodeRabbit spotted a startup race in the CI health checks. Codex pointed out that scanning on Linux alone misses Windows-only code. And Copilot noticed that one of my test fixes would have broken the release workflow on the very first tag. I would have found that one at release time, which is the worst time to find anything. Upgrading Breaking change: building from source now needs Go 1.26 or later. Homebrew, Docker and the release binaries aren’t affected, and there’s nothing to reconfigure. Homebrew: brew update && brew upgrade mysql-mcp-server Docker: docker pull ghcr.io/askdba/mysql-mcp-server:1.8.0 Or grab a binary from the v1.8.0 release. Here’s what my Mac said after brew upgrade: $ mysql-mcp-server --version mysql-mcp-server 1.8.0 Build time: 2026-10-17T09:14:37Z Git commit: f946f1d37654b5d8958d45a027a3e68133f01d1a The full list is in the CHANGELOG. Questions, bugs and ideas are welcome on GitHub.

  • Optimize OLTP and OLAP Workloads with MySQL HeatWave Autopilot
    As businesses grow, their databases must support both fast transactions and timely analytics. OLTP workloads handle everyday operations such as orders and payments, while OLAP workloads analyze large datasets for reporting and planning. Without appropriate optimization, growing data volumes and competing resource demands can slow transactions and delay insights. MySQL HeatWave supports online transaction processing […]

  • What’s New in MySQL NDB Cluster 9.x – Better Visibility and Control in MySQL NDB Cluster
    MySQL NDB Cluster 9.x continues to focus on the qualities that matter most in production: clear operational insight, predictable behaviour under failure, efficient application access, and faster recovery. This release series adds practical observability across the cluster, strengthens several exceptional paths, and expands the control available to users. Better visibility into cluster operation Diagnosing a […]

  • Benchmarking vector indexes in MySQL with vector-dev-bench
    PostgreSQL has a rapidly evolving vector ecosystem. MySQL is earlier on that road, but the community is hard at work catching up. MariaDB ships its own HNSW index, Google engineers have shared a ScaNN-based vector index on MySQL's own vector index proposal, Percona is building an HNSW index inside InnoDB, and VillageSQL is building vector search as an extension with vsql-vector. We want vector search on MySQL to be something that teams can choose with confidence (and not have to switch to PostgreSQL to get). Getting there takes more than one good implementation. The teams building these indexes need to measure them the same way, check each other's numbers, and fix what the numbers show. Working with Percona, we published vector-dev-bench. It runs on a laptop, it drives VillageSQL, MariaDB, PostgreSQL with pgvector, and MySQL built from Google's ScaNN pull request, and it gives an answer in minutes. Doing things in the open A benchmark number is only as good as the method behind it. A public method lets anyone rerun numbers, read the exact SQL each engine received, and tell us where the method may be unfair. It also makes collaboration practical. When another team can run the same harness against their own build, we can compare results without a bunch of upfront coordination. With vector-dev-bench, an engine is one entry in a Python dictionary plus a start script, so adding a new one is a small change. We've seen this work already. Percona's vector-bench is a full comparison framework, and we added VillageSQL to our copy of it. Along the way, we noticed that pgvector loaded its rows with COPY while the MySQL engines used INSERT, so the build times were measuring two different kinds of statements. We switched pgvector's incremental build to batched INSERTs so that every engine loads rows the same way. vector-bench adopted the change, and its code credits our commit. That is the kind of exchange we want more of. Two tools for two jobs vector-bench and vector-dev-bench answer different questions, and we use both. vector-bench is built to produce a comparison that you can publish. It runs each engine in Docker, built from pinned sources where it compiles them, runs real datasets such as SIFT, GloVe, and GIST, and measures recall against throughput, build cost, concurrency from 1 to 32 clients, filtered search, and recall after deletes and reinserts. It runs every engine twice, once with identical resources and once with each vendor's recommended settings, and it records the hardware and versions with every result. Its main profile takes about two days, which is the right cost for a number that other people will cite. vector-dev-bench is built for the quick, iterative loop in which you change the index, rebuild it, and want to know whether recall moved. It runs against engines you've already built, does not use containers, and uses synthetic data by default. It builds a 20,000-vector index in about 7 seconds. Its README is plain about the trade-off: the numbers are for relative comparisons, and a published comparison belongs in a framework like vector-bench. vector-dev-bench Measurement vector-dev-bench is three command-line tools over one shared module: Tool What it measures recall_bench.py It measures recall@k and queries per second across a sweep of ef_search values. It exits with an error when recall falls below a threshold (0.95 by default), so a script can use it as a pass/fail check. build_bench.py It measures index build time. --build-threads N builds the index from N client connections at once. rw_bench.py It measures reads during writes. Reader threads run nearest-neighbor queries while writer threads insert into the same index, and the tool reports read throughput second by second along with p50, p95, and p99 latency. Recall is the measure that matters here, because an approximate nearest-neighbor index returns approximately the right answer by design. A test that compares exact output, the way MySQL's own test framework does, fails on a correct index. vector-dev-bench computes the true nearest neighbors itself with NumPy and reports what fraction of them each engine returned. You can also pass --dataset to use a standard ann-benchmarks dataset with its published ground truth. The engines are profiles: Profile Engine vsql_vector VillageSQL with vsql-vector (SVECTOR column, HNSW index) mariadb MariaDB 11.7 or later (MHNSW) pgvector PostgreSQL 15 or later with pgvector google MySQL 9.7.1 built from Google's vector search commit (ScaNN index), which its authors describe as an experimental proof of concept varchar_ctrl A plain VARCHAR column, as a control that separates a server problem from a harness problem VillageSQL, MariaDB, and the Google build all speak the MySQL protocol, and the harness drives all three through the same PyMySQL connection code. The client cost is the same for each engine, but it isn't zero. A gap between two of these engines holds for the machine that measured it, and it can change on different hardware, so rerun it on the hardware you care about before you trust it. pgvector takes a different path. The harness sends each batch of pgvector inserts or queries through the psql command-line client, so the timed window also includes starting psql and opening its connection. The parallel options (--readers and --build-threads) fall back to one serial client, and rw_bench.py does not run against pgvector. A VillageSQL-versus-pgvector number from vector-dev-bench measures the two clients as well as the two engines. Treat it as a hint about where to look, and don't quote it as a verdict on either engine. What a run looks like We ran a recall sweep against VillageSQL 0.0.7 with the vsql-vector-pk build of the extension (whose HNSW index stores each row's primary key), on 20,000 random 32-dimension vectors. The tool builds the index once and then queries it at four ef_search values. The README and RUNBOOK cover building each engine and running the tools. Here is the output from an Apple M5 laptop with 32 GB of memory: build_time_s=7.03 (index built once; sweeping ef_search) ef_search recall@10 qps 50 0.7870 8829.3 100 0.9035 5903.0 200 0.9645 3526.0 400 0.9875 2044.4 This is the curve every HNSW index trades along. A wider search finds more of the true neighbors and answers fewer queries per second. The shape is also a check on the run itself: recall that stays at exactly 1.0 when ef_search drops usually means the optimizer chose a full table scan and the index wasn't used. MariaDB and PostgreSQL run the same sweep through their own profiles, and run_three_way.sh runs all three engines one after another so that only one server is busy at a time. Reads while the index is changing Benchmarks such as ann-benchmarks build the index once and then query a corpus that never changes. Production tables change all the time. rw_bench.py covers that case: it runs readers alone, adds writers for a window in the middle, and then runs readers alone again. This run used 4 readers and 4 writers against the same 20,000-vector index: total reads=47700 writes=2000 rows write_rate=432 rows/s over 4.6s read-only mean=4564 qps read+write mean=2738 qps impact=40% drop read latency (us) p50 p95 p99 max n read-only 797 1399 1805 87018 32038 read+write 1440 1803 1908 8991 10988 The per-second table in the full output shows reads dropping when the writers start at second 4 and recovering once they stop at second 8. For an index that lives inside a transactional engine, this is one of the numbers we care about most, because it shows what a busy table costs the queries that read it. When to use it, and when not to Use vector-dev-bench when you're changing an index and want to know within minutes whether recall, build time, or read throughput moved. Use it as a recall check in a script, since recall_bench.py exits with an error below its threshold. Use it to try an idea against MariaDB or pgvector on the same machine (for pgvector, recall and build time only) before you invest in a full run. Use a full framework such as vector-bench when the number is going to leave the room. That includes anything published, anything that compares absolute latency, and anything that depends on filtered search or recall after deletes. Latency under concurrency counts as absolute latency. A comparison with pgvector needs the full framework most of all, because pgvector is the engine whose client differs. Every number from vector-dev-bench passes through a client, so absolute latency includes the client and the round trip. Synthetic vectors give a trustworthy relative signal on the machine that ran them, but they don't predict recall on real embeddings. Add your engine The repository is Apache-2.0 licensed, and an AGENTS.md file walks a coding agent through running it. If you're building a vector index for MySQL, or for anything that speaks the MySQL or PostgreSQL protocol, add a profile and a start script and send a pull request. If you think our method is unfair to your engine, open an issue and tell us why. Getting vector search on MySQL to the point where teams can pick it with confidence is work for everyone in the MySQL community, and it starts with numbers we can all reproduce. You can get VillageSQL from the website, read how custom indexes work in the custom indexes documentation, and see our Percona Live demo in Vector search in MySQL: an early look at HNSW and custom indexes in VillageSQL.