www.bortolotto.eu

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

  • Devart ODBC Drivers update brings SQL Server 2025, MySQL 9.7, and new Snowflake authentication methods
    The latest Devart ODBC Drivers release is here, adding support for SQL Server 2025, MySQL 9.7.x, and NexusDB 4.75.20. The update also introduces new authentication options, improved data protection, and broader compatibility with development tools, operating systems, and other data sources. The post Devart ODBC Drivers update brings SQL Server 2025, MySQL 9.7, and new Snowflake authentication methods appeared first on Devart Blog.

  • When a Small Change Writes Too Much – How A Linux Kernel Improvement Can Strike You in PXC/Galera Workloads
    We’ve recently been dealing with an interesting customer case where a PXC version upgrade from 5.7 to 8.0 seemed to trigger a completely unexpected problem. Even though the workload and MySQL settings did not change, all PXC members began exhibiting sparse but regular bursts of massive disk writes. Yet the MySQL side workload statistics that we gathered during those bursts did not explain the volume of them at all! Analyzing these bursts further with pidstat and iostat confirmed that the mysqld process was indeed the sole source of excessive disk writes, and digging further showed the responsible threads to be the Galera receiver thread responsible for subscribing to the replication stream, as well as the main connection threads on the writer node, or Galera applier threads on the secondary members. And what’s most important is that there was a huge disparity between InnoDB engine threads writes (redo, undo, tablespace, etc) and binary log writes versus what was accounted to the Galera threads. At first, it looked like during the burst, several GB of write-sets were received and applied, yet InnoDB barely changed any pages. All InnoDB-related write activity was completely flat, so they did not participate in these high IO load spikes at all! An example mysqld per-thread breakdown for a few-second burst looked like this: Thread group Accounted Writes Share applier (Galera write-set appliers, many threads) 13.0 GB 61% galera_recv-0 (Galera receiver thread) 5.2 GB 24% ib_pg_flush* (InnoDB page cleaners) 2.0 GB 9% ib_log_writer (redo log) 0.76 GB 4% Galera_srvc (Galera service thread) 0.29 GB 1% The quick thought on the small redo log vs potentially huge writeset volume was that maybe transactions changing a single column would bloat the binlog events with other large blob/text/json columns, due to binlog_row_image=full, as the ROW image format applies to both Galera writesets and binary logs. But, analyzing the binary logs (with binlogsum) showed moderate and completely flat volume, nothing related to those bursts. Looking for clues, I asked myself, so what are those Galera threads exactly writing? The receiver thread copies all replicated writesets into the Galera cache file(s), but also reclaims the space when the ring wraps (the ring-buffer allocator walks forward from the oldest buffers and discards released ones). Then, the applier or connection threads also update the cache according to the transaction’s state. Still, when there are no spikes with big writesets (MySQL write load stays flat), why would we see such massive IO write bursts only accounted to the Galera threads? And why would that weird behavior start to happen after the PXC upgrade? We could not find any clues in the source code; there were no changes to the Galera plugin or wsrep extensions that would explain these bursts. Later, we found out additional information, which turned out to be crucial – not only the PXC version, but also the OS was upgraded! Now, the previous one: Debian 11, uses Linux 5.10 LTS kernel series, while the upgraded version 12 uses the Linux 6.1 LTS kernel series. And as the main suspect at this point was the Gcache store, how would the new kernel change the way this file is handled, though? It turns out that it does indeed, and it is related to the fact that the Galera plugin uses the mmap() path to handle the cache! Page Folios in Modern Linux Kernels In pursuit of handling large data on machines with big RAM and disk space more efficiently in Linux, in addition to huge pages, something called page folios was introduced in 5.16. What is relevant to this case is that large folios were implemented to also handle the page cache, initially only for the XFS filesystem in kernel 5.18, and yes, the affected cluster kept Galera cache on the XFS partition. Then, large folios support is what makes the kernel 6.1 different from the previous 5.10. OK, but why would “folios” cause bursts, and why do they not affect InnoDB writes, but only the Galera threads? Now, unlike any other data or log files in MySQL (except TempTable storage) or MariaDB, Galera cache files do not use the write() or pwrite() path, but mmap rather. This difference pointed me to some reports complaining about the systemd journal’s excessive IO problems, which turns out to be using mmap() path as well! Funny enough, the problem is not very well known, and the root cause is still being confused to this day, even in the most affected systemd community. Some reports describe the journal IO pressure being higher than expected, but never define the real culprit. The systemd issue that currently covers this problem best is: https://github.com/systemd/systemd/issues/40262 Related kernel thread discussions: https://lwn.net/Articles/1092530/ https://lists.openwall.net/linux-kernel/2026/08/20/1169 And despite many good blog posts about optimizing journal IO, I found only this one that points to the folio impact: https://www.cnblogs.com/32bin/p/22929557 The problem even reached the general tech press, as still unsolved in 2026: https://northeasttimes.com/2026/08/14/a-single-log-line-eats-49kb-of-disk-and-linux-admins-are-furious/ In short: on a large folios-enabled kernel and filesystem, page cache entries are grouped in folios. In early kernel implementations (5.18–6.5), write()/pwrite() paths were also affected, but InnoDB typically uses O_DIRECT for table spaces (so not using page cache at all), and redo and binlog use small appends, which create order-0 (4 kB) small folios. Then in Linux 6.6, a patch was implemented that added per-block dirty tracking, as now iomap records the overlapping filesystem blocks (filesystem-block granularity): see RFCv3 for details. With mmap writes, any update only tracks a whole folio. Thus, writeback page cache iteration, in the mmap case, will sync the whole folio (up to 2MB) instead of the exact fs block (4kB). In other words, the mmap() path cannot benefit from the improved I/O tracking loops in 6.6+. So, yes, the difference may be potentially huge, and small random writes to a mmapped file can induce huge IO bursts when the related page cache gets flushed to disk. There are important variables here: Readahead size via optimal_io_size → read_ahead_kb, per block device, influences the sizes of large folios allocated through it (from 128kB up to even 2MB) Bursts are specifically periodic due to how writeback of dirty data gets invoked – every dirty_writeback_centisecs (default 5 seconds) the kernel checks if the dirty data (tracked per inode) age is old enough via dirty_expire_centisecs (default 30 seconds) – the observed roughly 33-second bursts on the affected PXC nodes, are consistent with this mechanism (although these settings do not guarantee a fixed flush period) when the object, here the Gcache inode, gets flushed when receiving regular writes. Workload characteristics matter a lot – writes accumulated within a single folio will result in way smaller write amplifications as compared to a file being updated in many random places, hence touching many different large folios. The whole concept is damn complex, so let me share a more technical deep dive schematic in a separate document. Bear in mind, I am not a Linux kernel expert, so I hope there are not too many errors. The document is available here: https://przemekmalkowski.github.io/folios-mmap/ Galera Cache Impact Galera’s writeset cache has been historically a mmapped file from the very beginning. And there are a couple of factors that make the large folio-induced write amplifications particularly severe here: The cache file is by default 128MB, but very often a much bigger size is used to provide long enough IST coverage. In addition to sequential appends of new writesets, there are many small and random updates that change their status to RELEASED when the transaction is already applied and ready to be overwritten. Quite often, MySQL data, together with the galera.cache file, is placed on a large storage, like RAID arrays. Specifically for software RAID, the Linux kernel automatically increases the readahead size proportionally to the array geometry and the advertised optimal I/O size. For hardware RAID and dm/LVM stacks, it may be the case too. This leads to even larger folios, and that’s exactly what happened for the said customer case. During the PXC member startup, the whole gcache file is scanned sequentially during the recovery process (enabled by default), which can immediately populate the large page cache areas. Having said that, every PXC/Galera cluster may be affected by extra write amplifications for the galera.cache (and gcache.page.X), if kernel and filesystem versions support large folios. But the impact may be small enough to slip through unnoticed when the default readahead of 128kB is set for the disk block device. The extra IO write pressure may be wrongly assumed to be related to regular (and complex enough) database writes. But when the storage uses bigger readahead, and large folios get to even 2MB in size, the amplification is huge and really difficult to miss. Possible workarounds As currently there is no Linux kernel side solution implemented for the mmap() path, there are a couple of workarounds possible, and they apply to any applications using mmapped files, not only PXC/Galera: Depending on the kernel version, using the EXT4 filesystem may be an option, as folio support was introduced there much later (in 6.16) than in XFS (5.18) – if applicable, this allows us to avoid the said amplifications altogether. It helped in this customer’s case! Lower the readahead size for the disk device – for the price of potentially limited performance, the maximum size of large folios will be impacted by it, making the amplifications smaller. Modify the application mmap calls to use MADV_RANDOM via madvise() hints. It will basically disable readahead via enforced random access. Stop using mmap() path in favor of pwrite() on Linux 6.6+. Finally, use a customized modern kernel, compiled without THP support via the disabled CONFIG_TRANSPARENT_HUGEPAGE option – large folios are not created on recent kernels when THP is not available. (Note that disabling THP in GRUB is not enough to disable large folios!) For PXC, I reported the problem here: https://perconadev.atlassian.net/browse/PXC-5340 How to check if you’re affected Kernel and filesystem: XFS on 5.18+, or ext4 on 6.16+. Readahead size: cat /sys/block/<dev>/queue/{read_ahead_kb,optimal_io_size}. Large mappings: FilePmdMapped in /proc/meminfo or smaps for the gcache mapping, and thp_file_mapped climbing in /proc/vmstat. Bursts: about every 30–35 s (unless different timers in vm.dirty_expire_centisecs and vm.dirty_writeback_centisecs) in iostat, with per-thread pidstat -d charges in up to 2 MB multiples. More on Large Folios Impact Cases During research, I found another interesting story. It turns out that mixing active write workloads using both page cache and bypassing it with O_DIRECT may lead to additional unexpected stalls. This time it was visible with RocksDB, as described here: https://blog.wegenz.in/posts/page-cache-reclaim-foliotune-direct-io-stalemate The article was created by a human. The post When a Small Change Writes Too Much – How A Linux Kernel Improvement Can Strike You in PXC/Galera Workloads appeared first on Percona.

  • Just Because You Can SELECT Everything Doesn’t Mean You Should
    Last week, I shared a meme in the MySQL Group on LinkedIn about using SELECT * in MySQL queries. Today, I’m backing up the punchline with something every joke desperately needs: a technical explanation. We’ve all written queries like this: It’s quick. It’s convenient. It lets you postpone remembering whether the column is called customer_name, […]

  • JavaScript in MySQL: Extending SQL, Not Moving the Application
    At Percona Live Europe 2026, Dmitry Lenev and I presented JavaScript in My Database? Over My Dead Query, the story of component_js_lang, the V8-based JavaScript engine for Percona Server for MySQL 8.4. That experience told us few things: The presentation was seen as “for developers” What most DBA or DevOps heard was “JavaScript in the database” = “application logic in the database” This let most of the people pictured developers shipping their business logic into stored procedures. Order workflows, pricing rules, REST calls, all running inside mysqld.  That picture is wrong, and in this post I want to replace it with the right one, using the same four demos we showed on stage. The fear is reasonable, the target is wrong DBAs have earned their skepticism. Many of us have inherited a schema where thousands of lines of stored procedures were the application: impossible to test, impossible to version, impossible to scale out, and owned by nobody who still works there. So when a new language arrives inside the server, the reflex is to protect the database from becoming an application server again.  We share that reflex. The point of this post is that JavaScript routines, used as designed, do not move us in that direction. They move the opposite way: they make SQL stronger so that less logic needs to be pulled out of the database and re-implemented somewhere else. The engine itself also enforces the boundary. Inside component_js_lang a routine has: No network I/O. No fetch(), no sockets. A routine cannot call your payment provider or another microservice. No file system access. It cannot read configs, keys or files. No Node.js or DOM APIs. No require(), no process, no npm ecosystem. No useful async. Everything runs synchronously inside the statement. You cannot build an application with that toolbox. However you can build very good functions! What JavaScript routines are actually for A JavaScript routine extends the SQL vocabulary. It is a new function you call from SELECT, JOIN, WHERE, GROUP BY or a trigger, exactly like LOWER() or JSON_EXTRACT(). The only new syntax is LANGUAGE JS:CREATE FUNCTION norm_keys(d JSON) RETURNS JSON DETERMINISTIC NO SQL LANGUAGE JS AS $$ const walk = (j) => { if (j === null || typeof j !== 'object') return j if (Array.isArray(j)) return j.map(walk) const out = {} for (const [k, v] of Object.entries(j)) out[k.toLowerCase()] = walk(v) return out } return walk(typeof d === 'string' ? JSON.parse(d) : d) $$;  The rule we kept repeating on stage is simple: SQL owns the set, JavaScript owns the value. SQL is unbeatable at filtering, joining, ordering, windowing and aggregating. Where SQL and its procedural language stop is a short, very specific list: Recursion over a shape not known in advance. JSON_TABLE needs every path as a literal. Arrays. SQL has no array type, so any algorithm that fills a grid has nowhere to live. Big integers. BIGINT stops at 19 digits; an IBAN checksum needs 29. Real iteration with aggregates. A recursive CTE cannot contain an aggregate or window function (ERROR 3575), and a WHILE loop in PL pays an executor round trip on every statement. Rich string handling. UPPER() and LOWER() do not know that McDonald has a capital D. None of these is business logic. They are missing building blocks. JavaScript supplies them and hands the result straight back to SQL. The JavaScript routine lives inside the SQL layer, not beside the application: a query calls it, and it returns a value to that query. Four demos, one pattern Each demo in the pleu_2026 repository follows the same shape: SQL does the set work, a small JavaScript function does the one step SQL cannot phrase, and the decision that matters to the business stays in the application. Demo What SQL could not do The JavaScript piece Result What stays in the application Normalize JSON keys Walk a JSON tree of unknown shape; four partners spell customer.id four ways norm_keys(), 9 lines One report went from 51 lines with four UNION ALL branches to 19 lines with no partner names Which partners to onboard and what the report means Validate input at the door Proper-case names, normalise phones, run an IBAN mod-97 on a 29-digit number proper_name(), clean_email(), clean_phone(), valid_iban(), clean_contact() 460 of 2,000 rows caught, each with a list of what was wrong What to do with a rejected contact: fix, call the customer, quarantine Deduplicate customers Levenshtein distance needs a grid, and SQL has no arrays lev_distance() inside a JOIN predicate Name equality found 21.7% of duplicates; blocking in SQL plus two distances found about 80% at 99.7% precision Whether to merge two records, and how Where should Federico live? Iterate to a geometric median; recursive CTEs refuse aggregates Weiszfeld procedure 11.00 s in procedural SQL vs 0.52 s in JavaScript; 327,033 statements vs 6 Whether Federico actually moves A few details are worth calling out, because they are exactly where DBAs and developers should meet. Dedupe: SQL narrows, JavaScript decides the distance. Comparing 3,000 customers with 2,000 signups is six million pairs. We never let JavaScript see them all. Ordinary join predicates on city and initials cut the work to 21,352 pairs, and only those reach lev_distance(). The function returns a number. Merging two customers is a business decision, and it stays in the application. Validation: the function reports, the policy decides. clean_contact() returns the cleaned values and an array of errors. A trigger uses it to guarantee that nothing dirty lands in contacts, whoever does the insert: web app, mobile app, nightly import, or someone at a mysql prompt at 2am. What happens next to a rejected row is still the application’s job. Federico: three steps, one of them JavaScript. Turning 44,347 raw stops into 16,232 weighted endpoints is two window functions, and nothing beats SQL there. Turning the resulting coordinate into a district is plain joins. JavaScript only runs the inner loop in the middle. Note that this demo uses session.runSql(), so today it runs on MySQL Enterprise; SQL from JavaScript is the top roadmap item for Percona Server. Where we draw the line A JavaScript routine belongs in the database when it makes a data operation possible or cheaper. It belongs in the application when it decides what the business does. In practice we use five questions before accepting a routine: Does it work on values the database already holds? If it needs an external service, a file or a user session, it is application code. Is it deterministic, with no side effects outside the transaction? DETERMINISTIC NO SQL is not only a binlog requirement; it is a good design filter. Is it called from SQL? A good routine sits inside a SELECT, a JOIN, a JSON_TABLE or a trigger. If nothing in SQL calls it, ask why it is in the database. Is the result smaller than the input, or does it save a round trip? Two numbers out instead of 5,000 names, emails and cities is the kind of win we want. Would every client need the same rule? Validation that the web app, the mobile app and the importer all must apply is a data rule. Pricing for a campaign that changes next week is business policy. Belongs in a JS routine Stays in the application Normalising and validating values (keys, names, phones, IBAN) Workflows, approvals, notifications Distance, similarity, checksum and parsing functions Decisions based on those scores: merge, reject, refund The inner loop of a numeric algorithm over rows SQL selected Orchestration across services and external APIs Integrity rules every writer must obey UI, sessions, authentication flows Reshaping JSON so SQL can query it Business rules owned by product and changed often If a routine starts to need configuration tables, feature flags or calls to another system, that is our signal that it has crossed the line. Why developers should ask for it For developers this is not about giving up control of the application. It is about no longer having to pull data out of MySQL just because SQL could not express one step. It is the language you already write. ECMAScript on V8, the same runtime as Chrome and Node.js. JSON columns arrive as native JS objects, no JSON.parse() needed. Fewer round trips. In the commute demo one CALL read 16,232 rows and wrote 2,000 answers with zero rows crossing the network. The speed-up from 11.00 s to 0.52 s came from not crossing the executor boundary 327,033 times, not from JavaScript being a faster language. Less personal data in motion. The dedupe query returns two numbers instead of shipping 5,000 names, emails and cities to an app server, and into its logs. Your GDPR scope stays in the database. One rule, written once. Validation that lives in five clients becomes four out-of-date copies. A function and a trigger are one copy nobody can bypass. Simpler queries in your code. norm_keys() turned a 51-line, partner-specific report into 19 lines. Partner five becomes a data change, not a code change. Pure functions are easy to test. Deterministic, no I/O, no hidden state: the same unit-testing habits you already have. How we suggest developers and DBAs work together: The developer proposes the function, with the SQL that will call it. The DBA reviews it against the five questions above, and checks if the call site uses blocking or filtering so the function runs on as few rows as possible. Grant CREATE_JS_ROUTINE deliberately; it is required on top of CREATE ROUTINE, so who can write JavaScript is an explicit choice. Keep the routine in version control next to the schema migrations. SQL did not get worse, it got a companion JavaScript in MySQL is not an invitation to rebuild the application inside the server.  It is a set of sharp tools for the few places where SQL and PL run out: recursion, arrays, big numbers and tight loops. The set work stays in SQL, the business logic stays in the application, and JavaScript fills the gap between them. If you want to try it, component_js_lang is available as a tech preview in Percona Server for MySQL 8.4.5-5 and later, free and open source, while the equivalent feature from Oracle requires MySQL Enterprise.  You can easily use the code available here Demo code from Percona Live Europe 2026 to perform some tests, just doing cut and paste.  Tell us what you think on the Percona Community Forum or in Jira with the label PS-JS-LANG. Your feedback decides what goes into GA, starting with SQL execution from JavaScript routines. Resources Percona Server js_lang documentation Demo code from Percona Live Europe 2026 Source code on GitHub The post JavaScript in MySQL: Extending SQL, Not Moving the Application appeared first on Percona.

  • JavaScript in MySQL: Extending SQL, Not Moving the Application
    At Percona Live Europe 2026, Dmitry Lenev and I presented JavaScript in My Database? Over My Dead Query, the story of component_js_lang, the V8-based JavaScript engine for Percona Server for MySQL 8.4. That experience told us few things: The presentation was seen as “for developers” What most DBA or DevOps heard was "JavaScript in the database" = "application logic in the database" This let most of the people pictured developers shipping their business logic into stored procedures. Order workflows, pricing rules, REST calls, all running inside mysqld.  That picture is wrong, and in this post I want to replace it with the right one, using the same four demos we showed on stage. The fear is reasonable, the target is wrong DBAs have earned their skepticism. Many of us have inherited a schema where thousands of lines of stored procedures were the application: impossible to test, impossible to version, impossible to scale out, and owned by nobody who still works there. So when a new language arrives inside the server, the reflex is to protect the database from becoming an application server again.    We share that reflex. The point of this post is that JavaScript routines, used as designed, do not move us in that direction. They move the opposite way: they make SQL stronger so that less logic needs to be pulled out of the database and re-implemented somewhere else. The engine itself also enforces the boundary. Inside component_js_lang a routine has: No network I/O. No fetch(), no sockets. A routine cannot call your payment provider or another microservice. No file system access. It cannot read configs, keys or files. No Node.js or DOM APIs. No require(), no process, no npm ecosystem. No useful async. Everything runs synchronously inside the statement. You cannot build an application with that toolbox. However you can build very good functions! What JavaScript routines are actually for A JavaScript routine extends the SQL vocabulary. It is a new function you call from SELECT, JOIN, WHERE, GROUP BY or a trigger, exactly like LOWER() or JSON_EXTRACT(). The only new syntax is LANGUAGE JS: CREATE FUNCTION norm_keys(d JSON) RETURNS JSON DETERMINISTIC NO SQL LANGUAGE JS AS $$ const walk = (j) => { if (j === null || typeof j !== 'object') return j if (Array.isArray(j)) return j.map(walk) const out = {} for (const [k, v] of Object.entries(j)) out[k.toLowerCase()] = walk(v) return out } return walk(typeof d === 'string' ? JSON.parse(d) : d) $$;   The rule we kept repeating on stage is simple: SQL owns the set, JavaScript owns the value. SQL is unbeatable at filtering, joining, ordering, windowing and aggregating. Where SQL and its procedural language stop is a short, very specific list: Recursion over a shape not known in advance. JSON_TABLE needs every path as a literal. Arrays. SQL has no array type, so any algorithm that fills a grid has nowhere to live. Big integers. BIGINT stops at 19 digits; an IBAN checksum needs 29. Real iteration with aggregates. A recursive CTE cannot contain an aggregate or window function (ERROR 3575), and a WHILE loop in PL pays an executor round trip on every statement. Rich string handling. UPPER() and LOWER() do not know that McDonald has a capital D. None of these is business logic. They are missing building blocks. JavaScript supplies them and hands the result straight back to SQL. The JavaScript routine lives inside the SQL layer, not beside the application: a query calls it, and it returns a value to that query. Four demos, one pattern Each demo in the pleu_2026 repository follows the same shape: SQL does the set work, a small JavaScript function does the one step SQL cannot phrase, and the decision that matters to the business stays in the application. Demo What SQL could not do The JavaScript piece Result What stays in the application Normalize JSON keys Walk a JSON tree of unknown shape; four partners spell customer.id four ways norm_keys(), 9 lines One report went from 51 lines with four UNION ALL branches to 19 lines with no partner names Which partners to onboard and what the report means Validate input at the door Proper-case names, normalise phones, run an IBAN mod-97 on a 29-digit number proper_name(), clean_email(), clean_phone(), valid_iban(), clean_contact() 460 of 2,000 rows caught, each with a list of what was wrong What to do with a rejected contact: fix, call the customer, quarantine Deduplicate customers Levenshtein distance needs a grid, and SQL has no arrays lev_distance() inside a JOIN predicate Name equality found 21.7% of duplicates; blocking in SQL plus two distances found about 80% at 99.7% precision Whether to merge two records, and how Where should Federico live? Iterate to a geometric median; recursive CTEs refuse aggregates Weiszfeld procedure 11.00 s in procedural SQL vs 0.52 s in JavaScript; 327,033 statements vs 6 Whether Federico actually moves A few details are worth calling out, because they are exactly where DBAs and developers should meet. Dedupe: SQL narrows, JavaScript decides the distance. Comparing 3,000 customers with 2,000 signups is six million pairs. We never let JavaScript see them all. Ordinary join predicates on city and initials cut the work to 21,352 pairs, and only those reach lev_distance(). The function returns a number. Merging two customers is a business decision, and it stays in the application. Validation: the function reports, the policy decides. clean_contact() returns the cleaned values and an array of errors. A trigger uses it to guarantee that nothing dirty lands in contacts, whoever does the insert: web app, mobile app, nightly import, or someone at a mysql prompt at 2am. What happens next to a rejected row is still the application's job. Federico: three steps, one of them JavaScript. Turning 44,347 raw stops into 16,232 weighted endpoints is two window functions, and nothing beats SQL there. Turning the resulting coordinate into a district is plain joins. JavaScript only runs the inner loop in the middle. Note that this demo uses session.runSql(), so today it runs on MySQL Enterprise; SQL from JavaScript is the top roadmap item for Percona Server. Where we draw the line A JavaScript routine belongs in the database when it makes a data operation possible or cheaper. It belongs in the application when it decides what the business does. In practice we use five questions before accepting a routine: Does it work on values the database already holds? If it needs an external service, a file or a user session, it is application code. Is it deterministic, with no side effects outside the transaction? DETERMINISTIC NO SQL is not only a binlog requirement; it is a good design filter. Is it called from SQL? A good routine sits inside a SELECT, a JOIN, a JSON_TABLE or a trigger. If nothing in SQL calls it, ask why it is in the database. Is the result smaller than the input, or does it save a round trip? Two numbers out instead of 5,000 names, emails and cities is the kind of win we want. Would every client need the same rule? Validation that the web app, the mobile app and the importer all must apply is a data rule. Pricing for a campaign that changes next week is business policy. Belongs in a JS routine Stays in the application Normalising and validating values (keys, names, phones, IBAN) Workflows, approvals, notifications Distance, similarity, checksum and parsing functions Decisions based on those scores: merge, reject, refund The inner loop of a numeric algorithm over rows SQL selected Orchestration across services and external APIs Integrity rules every writer must obey UI, sessions, authentication flows Reshaping JSON so SQL can query it Business rules owned by product and changed often If a routine starts to need configuration tables, feature flags or calls to another system, that is our signal that it has crossed the line. Why developers should ask for it For developers this is not about giving up control of the application. It is about no longer having to pull data out of MySQL just because SQL could not express one step. It is the language you already write. ECMAScript on V8, the same runtime as Chrome and Node.js. JSON columns arrive as native JS objects, no JSON.parse() needed. Fewer round trips. In the commute demo one CALL read 16,232 rows and wrote 2,000 answers with zero rows crossing the network. The speed-up from 11.00 s to 0.52 s came from not crossing the executor boundary 327,033 times, not from JavaScript being a faster language. Less personal data in motion. The dedupe query returns two numbers instead of shipping 5,000 names, emails and cities to an app server, and into its logs. Your GDPR scope stays in the database. One rule, written once. Validation that lives in five clients becomes four out-of-date copies. A function and a trigger are one copy nobody can bypass. Simpler queries in your code. norm_keys() turned a 51-line, partner-specific report into 19 lines. Partner five becomes a data change, not a code change. Pure functions are easy to test. Deterministic, no I/O, no hidden state: the same unit-testing habits you already have. How we suggest developers and DBAs work together: The developer proposes the function, with the SQL that will call it. The DBA reviews it against the five questions above, and checks if the call site uses blocking or filtering so the function runs on as few rows as possible. Grant CREATE_JS_ROUTINE deliberately; it is required on top of CREATE ROUTINE, so who can write JavaScript is an explicit choice. Keep the routine in version control next to the schema migrations. SQL did not get worse, it got a companion JavaScript in MySQL is not an invitation to rebuild the application inside the server.  It is a set of sharp tools for the few places where SQL and PL run out: recursion, arrays, big numbers and tight loops. The set work stays in SQL, the business logic stays in the application, and JavaScript fills the gap between them. If you want to try it, component_js_lang is available as a tech preview in Percona Server for MySQL 8.4.5-5 and later, free and open source, while the equivalent feature from Oracle requires MySQL Enterprise.  You can easily use the code available here Demo code from Percona Live Europe 2026 to perform some tests, just doing cut and paste.  Tell us what you think on the Percona Community Forum or in Jira with the label PS-JS-LANG. Your feedback decides what goes into GA, starting with SQL execution from JavaScript routines. Resources Percona Server js_lang documentation Demo code from Percona Live Europe 2026 Source code on GitHub