www.bortolotto.eu

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

  • DuckDB Speed on MySQL with dbtrail, Without a New Storage Engine
    Two recent posts on the Percona blog caught my attention. In Replicating from InnoDB into a DuckDB storage engine and The DuckDB MySQL engine at 500 GB, Evgeniy Patlan wired DuckDB into mysqld as a storage engine, pointed row-based replication at it, and measured it at TPC-H scale factor 500. The numbers are striking: loads 25 times faster than InnoDB, one fifth of the disk, and the full 22-query TPC-H suite done in 186 seconds where InnoDB needed about 28 hours and never finished four of the queries. The posts are also honest about the cost. The work is experimental. The worst bug found was silent data loss on every replicated transaction: the two-phase commit code for mixed-engine transactions dropped prepared DuckDB transactions before they committed, so replication reported success while no rows persisted. The test harness caught it and it was fixed. The row-by-row applier also cannot keep up with bulk writes on the primary. I want to add a second road to the same place, one that changes nothing inside mysqld. Disclosure first: I build dbtrail, the Apache 2.0 open source tool used below, so read this as one more point in the design space, from someone who is not neutral. Everything here ran on stock Percona Server 8.0 from Docker Hub and stock DuckDB from Homebrew, and the commands at the end reproduce all of it. Your binlog is already a columnar feed The storage engine approach puts the column store inside the server, so the server must cooperate: a patched build, a new engine in the commit path, replica tables created by hand with ENGINE=DuckDB. The other road starts from what MySQL already provides. With binlog_format=ROW and binlog_row_image=FULL, the binary log is a complete, ordered feed of every row change, with full before and after images. Any process can consume that feed as a replication client, the way a replica does, and install nothing on the server. dbtrail is that process. It does two things with the feed: It keeps a short, searchable window of recent events in a plain MySQL table, for investigation and recovery. It moves closed hours out to Parquet files, the open columnar format every analytical engine can read. DuckDB reads it natively. The pipeline is mysqld -> binlog -> Parquet -> DuckDB. No patched server, no plugin, nothing new in the commit path. And because the feed is history rather than current state, dbtrail can answer a question no replica can: what did this row look like before, and how do I undo what happened to it? The demo One container for the source, stock image, with the settings any change-capture consumer needs (plus one line to keep the demo’s own index out of the binlog, since source and index share one server here): bash Copy Copied! docker run -d --name demo -p 13310:3306 -e MYSQL_ROOT_PASSWORD=demo \ percona/percona-server:8.0 \ --gtid-mode=ON --enforce-gtid-consistency=ON \ --binlog-rows-query-log-events=ON --binlog-ignore-db=bintrail_index binlog_format=ROW and binlog_row_image=FULL are already the 8.0 defaults. The schema is a small shop: customers, orders, order_items. Three short commands point dbtrail at it (init, snapshot, stream; they are in the last section), and from there the work happens in dbtrail’s web console. Then I replayed a five-hour synthetic workload: 3,265,000 row events in all. 2.7 million INSERTs, 510 thousand UPDATEs, 55 thousand DELETEs. (A tip for demo builders: SET TIMESTAMP = in the writing session backdates the binlog event timestamps, so a fast replay spreads across past hours and you can watch retention behave as it would in real life.) The console’s Status view answers the first question an operator should ask of any change-capture pipeline: did we miss anything? The green banner is a verdict, not a guess: dbtrail tracks continuity across the captured range, and it says plainly that this is not a liveness check. On my MacBook the capture stream held about 18,000 events per second while it tailed the binlog. That answers the applier problem from the first post: dbtrail does not push rows through the storage engine API one at a time, it batches them into an index, so bulk writes on the primary are absorbed rather than queued. From hot partitions to Parquet The index table is range-partitioned by hour. dbtrail archives each closed hour to zstd-compressed Parquet, checks the result, and only then drops the partition. Retention on the expensive tier becomes hours, not months. The demo moved all 3.26 million events out in 14 seconds, and the Status view above already showed the result: 7 archive files, 26 MB. Here is that number next to what the same events cost in InnoDB: Do not take that 100x as a general truth. Synthetic demo data repeats itself and compresses far too well, and the InnoDB figure includes the primary key and secondary indexes that make the hot tier searchable. On production data expect about one order of magnitude. The direction matches what Evgeniy measured at 500 GB, where the DuckDB engine held TPC-H in 26 percent of the raw CSV size and InnoDB needed 135 percent. Column formats fit this data. Row formats do not. One mydumper pass adds the state side: a baseline snapshot of the tables themselves, also stored as Parquet. For the demo shop that was 2.6 million rows in 8 MB, done in under four seconds. dbtrail uses baselines to rebuild full tables and single rows at a point in time; here they also give DuckDB something current to query. Stock DuckDB, no plugins The archive is just files, so the query engine does not have to be dbtrail. One command, bintrail views (bintrail is the name of dbtrail’s CLI binary), writes a single SQL file of DuckDB view definitions over the layout it knows about: an events view across every archived partition, and one state__ view per table of the newest baseline: bash Copy Copied! bintrail views --index-dsn "$IDX" --baseline-dir /data/baselines --out views.sql duckdb D .read views.sql That is the whole integration. dbtrail never opens DuckDB and never runs what it prints; the file is plain SQL you can read before you use it. From there, use any DuckDB you like: the CLI, a notebook, a BI tool’s connector. Same laptop, same data, same queries on both engines: Now the fine print, because a benchmark without it is just marketing. The chart has two groups. The first group queries the event history: those scans hit a 2.8 GB index with the container’s stock 128 MB buffer pool, so InnoDB paid for disk reads, and a bigger pool narrows that gap. The second group runs over current state and is the fair comparison: I grew the pool to 4 GB, both tables sat fully in cache, and the 11x and 20x that remain come from the design of the engine, not from the disk. Both engines returned the same results, down to the last decimal. That is a free integrity check: two independent systems read the same history and agreed. These are laptop numbers, and I did not run TPC-H. For the ceiling of what columnar execution does at 500 GB, read Evgeniy’s second post. My point is the floor: 2.8 GB of freshly rotated history sat on my laptop as 26 MB of open files, and a stock engine answers questions over them in tens of milliseconds. Nobody patched anything to get here. The part a replica cannot do A DuckDB replica holds current state. dbtrail holds what happened. This is the same data the analytics just scanned, now in the console’s Events view, filtered to one order: Three events tell the row’s whole story: created, cancelled, deleted, each with its GTID and the connection that did it. The expanded UPDATE shows the full before and after images dbtrail keeps for every change. Here is the detail that surprises people: when I took that screenshot, the MySQL side of the index held zero rows. Rotation had dropped every partition. The console read the answer from the Parquet tier in under 200 milliseconds. The Undo button works from there too: One click wrote reversal.sql: a script that puts the row back exactly as it was, tagged with the GTID it reverses. Nothing runs on its own; you read the script, then you apply it. The same Restore view takes a whole table and a time window: the demo’s 5,000 deleted orders came back as 5,000 INSERT statements, generated in 0.2 seconds, from files, while the database that held that history no longer existed. A column store fed by your binlog does not have to be a replica. It can be a time machine. Trade-offs, honestly Neither road wins outright. Where the storage engine is better: its DuckDB copy is always current, and it speaks MySQL protocol. A DuckDB engine on a replica runs seconds behind the primary, and existing BI tools connect to it unchanged. dbtrail’s capture also runs seconds behind, and its console and CLI query that fresh index directly. What waits is the copy DuckDB reads: the events view covers the hours already rotated to Parquet, and the state views show the latest baseline, so that side is as current as your rotation and baseline schedule. If you need a BI tool on MySQL protocol reading a complete, current copy, Evgeniy’s architecture, or a product like HeatWave, aims at exactly that. Where staying outside the server is better: risk and history. The hardest place to change a database is the commit path, and a storage engine lives there. The 2PC bug shows the kind of failure that layer produces, and it took a dedicated test harness to find it, because it was silent. A replication client cannot corrupt a commit. Its failure modes are lag and gaps, and dbtrail reports gaps as a first-class verdict rather than hoping. History is the other half: audit, point-in-time rebuilds, and row-level undo all come from the same files the analytics read. Shared constraints. Both roads need ROW format with FULL row images. Both need primary keys to apply or reverse UPDATEs and DELETEs. Both leave the source of truth in InnoDB, untouched. On maturity: the engine posts describe an experiment and say so; dbtrail is released and versioned (v0.65.0 as I write this) but young, and you should question my numbers the same way I question everyone else’s. Both posts and this one agree on the base facts: row stores are the wrong shape for analytical scans, DuckDB fits MySQL-shaped data well, and the open question is where the column store should live. Evgeniy shows what you gain when the server cooperates. dbtrail shows what you get when you leave the server alone. Reproduce it dbtrail binaries are on the releases page; DuckDB comes from your package manager. The web console ships as its own binary, bintrail-console, and can also run capture and console together as one daemon (bintrail-console watch). bash Copy Copied! # 1. A source with binlogs (ROW + FULL are 8.0 defaults) docker run -d --name demo -p 13310:3306 -e MYSQL_ROOT_PASSWORD=demo \ percona/percona-server:8.0 --gtid-mode=ON --enforce-gtid-consistency=ON \ --binlog-rows-query-log-events=ON --binlog-ignore-db=bintrail_index # 2. Create your schema, then point dbtrail at it bintrail init --index-dsn "$IDX" bintrail snapshot --source-dsn "$SRC" --index-dsn "$IDX" --schemas shop bintrail stream --source-dsn "$SRC" --index-dsn "$IDX" --server-id 4444 --schemas shop & bintrail-console serve --index-dsn "$IDX" --baseline-dir /data/baselines & # 3. Run your workload, then tier the history out and take a baseline bintrail rotate --index-dsn "$IDX" --retain 7d --archive-dir /data/archives bintrail dump --source-dsn "$SRC" --output-dir /data/dump --schemas shop bintrail baseline --input /data/dump --output /data/baselines # 4. Hand the whole thing to DuckDB bintrail views --index-dsn "$IDX" --baseline-dir /data/baselines --out views.sql duckdb -c ".read views.sql" \ -c "SELECT table_name, event_type, COUNT(*) FROM events GROUP BY 1,2;" If you try dbtrail, tell me where it fails as much as where it works well. Issues and pull requests are open at github.com/dbtrail/dbtrail, and I am around in the Percona Community Slack.

  • Where can you find MySQL from August through October 2026? 
    The MySQL Community team will continue to be active across conferences, user group meetups, open source events, and regional community activities throughout August, September, and October 2026.  While some of our August events were featured in our previous event update, we’ve included them here again with the latest information. Whether you’re interested in learning about MySQL 9.7 LTS, exploring the latest features in […]

  • MySQL Galera Cluster EOL: Your Practical Paths Forward
    MySQL Galera Cluster reaches end of life on September 30, 2026. This guide compares five practical paths forward - MariaDB Galera Cluster, Percona XtraDB Cluster, self-managed MySQL, managed cloud MySQL and Tungsten Cluster - with the trade-offs, migration effort and long-term risks of each.

  • Tối Ưu Chỉ Mục (Index) Trong MySQL Cho Dev PHP Mới
    Tối ưu chỉ mục (index) trong MySQL là kiến thức cơ bản mà nhiều dev PHP mới hay bỏ qua. Có bạn dev mới ra trường nhắn tin kể cho chúng tôi về một buổi tối khá căng thẳng. Ứng dụng quản lý đơn hàng bạn viết chạy mượt suốt mấy tuần thử nghiệm với vài trăm bản ghi. Nhưng khi khách hàng thật bắt đầu dùng, dữ liệu tăng lên vài chục nghìn dòng. Trang danh sách đơn hàng bỗng dưng tải chậm tới mức khách phàn nàn. Bạn kiểm tra code PHP mãi không thấy lỗi gì. Tới khi một anh senior xem qua mới phát hiện câu truy vấn tìm đơn hàng theo mã khách chưa hề có chỉ mục. Điều này khiến MySQL phải quét qua toàn bộ bảng mỗi lần tìm kiếm. Đây là tình huống khá phổ biến ở nhiều dev PHP mới vào nghề. Chỉ mục trong MySQL là kiến thức nền tảng, nhưng lại thường bị bỏ qua cho tới khi dữ liệu thực tế đủ lớn để bộc lộ vấn đề. Dev PHP Mới Thường Bỏ Qua Tầm Quan Trọng Của Chỉ Mục Trong MySQL Truy vấn chạy chậm dần khi dữ liệu lớn lên là tình huống rất dễ gặp với dev mới. Trong giai đoạn phát triển, dữ liệu thử nghiệm thường chỉ có vài chục tới vài trăm bản ghi. Số lượng này quá ít để bộc lộ vấn đề hiệu năng. Một câu truy vấn không có chỉ mục vẫn chạy nhanh bình thường khi bảng chỉ có vài trăm dòng. Nhưng khi bảng phình lên tới hàng chục nghìn hay hàng trăm nghìn dòng dữ liệu thật, tốc độ truy vấn có thể chậm đi rất nhiều lần. Đây đúng là trường hợp bạn dev chúng tôi kể ở đầu bài đã gặp phải ngay khi ứng dụng lên môi trường thực tế. Thiếu chỉ mục hợp lý khiến hiệu năng ứng dụng giảm sút dần theo thời gian. Đây là hệ quả âm thầm mà nhiều dev mới không nhận ra ngay từ đầu, vì vấn đề tích lũy dần chứ không bộc phát ngay lập tức. Ứng dụng vẫn chạy được, chỉ chậm hơn một chút mỗi tuần khi dữ liệu tăng thêm. Tới một ngưỡng nào đó, người dùng mới thực sự cảm nhận rõ độ trễ và bắt đầu phàn nàn. Lúc đó, việc tìm nguyên nhân gốc và sửa lại thường tốn công sức hơn nhiều so với việc thiết kế đúng chỉ mục ngay từ khi viết truy vấn lần đầu. Chỉ Mục Trong MySQL Hoạt Động Như Thế Nào Bản chất cốt lõi của chỉ mục là giúp cơ sở dữ liệu tìm kiếm nhanh hơn, thay vì quét toàn bộ bảng. Nói dễ hiểu, chỉ mục hoạt động giống như mục lục ở đầu một cuốn sách dày. Không có mục lục, muốn tìm một chương cụ thể bạn phải lật qua từng trang. Có mục lục, bạn tra ngay số trang cần tìm rồi mở thẳng tới đó. Trong MySQL, khi một cột được đánh chỉ mục, cơ sở dữ liệu sẽ tạo ra một cấu trúc dữ liệu riêng, thường gọi là cấu trúc B-Tree. Cấu trúc này giúp tìm kiếm giá trị trong cột đó nhanh hơn rất nhiều so với quét lần lượt từng dòng trong bảng. Thao tác quét toàn bộ bảng như vậy gọi là full table scan, chính là nguyên nhân khiến truy vấn của bạn dev chúng tôi kể ở đầu bài chạy chậm khi dữ liệu tăng lên. Nhiều dev mới chưa để ý rằng cần cân nhắc giữa tốc độ truy vấn và chi phí khi ghi dữ liệu mới. Chỉ mục không phải “càng nhiều càng tốt”. Mỗi khi thêm mới, cập nhật hoặc xóa một dòng dữ liệu, MySQL không chỉ ghi vào bảng chính mà còn phải cập nhật lại toàn bộ các chỉ mục liên quan. Nghĩa là bảng có càng nhiều chỉ mục, thao tác ghi dữ liệu sẽ càng chậm đi một chút. Với những bảng có tần suất ghi rất cao như bảng log truy cập, việc thêm quá nhiều chỉ mục không cần thiết có thể khiến hiệu năng ghi bị ảnh hưởng đáng kể, dù tốc độ đọc có nhanh hơn. Cách Áp Dụng Chỉ Mục Hiệu Quả Cho Dev Mới Xác Định Đúng Cột Cần Đánh Chỉ Mục Xác định đúng cột thường xuyên xuất hiện trong điều kiện truy vấn là bước đầu tiên và quan trọng nhất. Nguyên tắc cơ bản chúng tôi luôn khuyên dev mới áp dụng là ưu tiên đánh chỉ mục cho các nhóm cột sau: Cột thường xuất hiện sau mệnh đề WHERE để lọc dữ liệu. Cột dùng để sắp xếp kết quả bằng ORDER BY. Cột dùng để nối bảng bằng JOIN, ví dụ cột mã khách hàng trong bảng đơn hàng của trường hợp bạn dev chúng tôi kể ở đầu bài. Một công cụ hữu ích để kiểm tra truy vấn có đang dùng chỉ mục hay không chính là lệnh EXPLAIN đặt trước câu truy vấn. Kết quả trả về sẽ cho biết MySQL đang quét toàn bộ bảng hay đang dùng đúng chỉ mục để tìm kiếm. Nhờ vậy, dev có thể tự kiểm tra và tối ưu ngay trong quá trình phát triển, thay vì đợi tới khi có sự cố thực tế. Tránh Tạo Quá Nhiều Chỉ Mục Không Cần Thiết Tránh tạo quá nhiều chỉ mục không cần thiết là lưu ý thứ hai không kém phần quan trọng. Một sai lầm khá phổ biến chúng tôi từng gặp ở dev mới, sau khi hiểu lợi ích của chỉ mục, là đi ngược thái cực. Họ đánh chỉ mục cho hầu hết mọi cột trong bảng vì nghĩ “có chỉ mục là nhanh hơn”. Trong khi đó, nhiều cột gần như không bao giờ dùng để tìm kiếm hay lọc dữ liệu. Việc đánh chỉ mục cho chúng chỉ tốn thêm dung lượng lưu trữ và làm chậm thao tác ghi, mà không mang lại lợi ích tương xứng. Mẹo thực tế chúng tôi thường áp dụng là chỉ đánh chỉ mục cho những cột có tính phân biệt cao, tức là giá trị trong cột đó khá đa dạng giữa các dòng, ví dụ cột mã đơn hàng hay email khách hàng. Ngược lại, cột chỉ có vài giá trị lặp lại như cột trạng thái đơn hàng (thường chỉ ba bốn giá trị cố định) không nên ưu tiên đánh chỉ mục, vì hiệu quả tăng tốc mang lại không đáng kể. Với những dự án website thương mại điện tử có lượng dữ liệu đơn hàng và sản phẩm lớn, tối ưu tốc độ truy vấn cần được tính ngay từ đầu. Việc chọn đúng đơn vị phát triển am hiểu cả thiết kế lẫn tối ưu cơ sở dữ liệu là điều quan trọng. Bạn có thể tham khảo thêm dịch vụ làm website ecommerce để có giải pháp phù hợp cho việc xây dựng nền tảng bán hàng vận hành ổn định. Tài Liệu Tham Khảo Thêm Cho Dev Mới Nếu bạn là dev mới còn đang làm quen với công cụ trực quan để quản lý MySQL, có thể tham khảo thêm MySQL Workbench là gì và tại sao nên cài đặt công cụ này. Công cụ này giúp bạn thao tác với chỉ mục và bảng dữ liệu dễ dàng hơn, thay vì chỉ gõ lệnh dòng lệnh. Nếu muốn nắm chắc kiến thức nền tảng trước khi đi sâu vào tối ưu chỉ mục, bạn có thể xem thêm tổng quan về hệ quản trị cơ sở dữ liệu MySQL để hiểu rõ cách MySQL tổ chức và lưu trữ dữ liệu. Nếu công việc của bạn có liên quan tới cả SQL Server bên cạnh MySQL, hãy tham khảo thêm schema là gì và vai trò của schema trong SQL Server để có thêm góc nhìn so sánh giữa hai hệ quản trị cơ sở dữ liệu phổ biến này. Kết Luận Nắm vững kiến thức cơ bản về chỉ mục giúp dev PHP mới tối ưu hiệu năng ứng dụng ngay từ giai đoạn phát triển đầu tiên. Điều này giúp bạn tránh phải xử lý gấp gáp khi ứng dụng đã lên môi trường thật, như trường hợp bạn dev chúng tôi kể ở đầu bài. Nếu bạn đang viết một tính năng có truy vấn tìm kiếm hoặc lọc dữ liệu, hãy dành vài phút chạy thử lệnh EXPLAIN để kiểm tra xem truy vấn đó có đang dùng chỉ mục hay không. Đây là thói quen nhỏ nhưng có thể giúp bạn tránh được không ít sự cố hiệu năng đáng tiếc về sau. The post Tối Ưu Chỉ Mục (Index) Trong MySQL Cho Dev PHP Mới appeared first on DBAhire.

  • MySQL Galera Cluster EOL: Your Decision Runway and What It Costs to Wait
    MySQL Galera Cluster maintenance ends September 30, 2026, while MySQL 8.0 has already reached end of life. Learn what these overlapping lifecycle changes mean, what risks increase after support ends, and what teams should decide before the Galera EOL deadline.