<?xml version="1.0" encoding="utf-8"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
      <title>Vitess and MySQL Blog — PlanetScale</title>
      <subtitle>Posts about Vitess and MySQL.</subtitle>
      <link href="https://planetscale.com" />
      <link rel="alternate" type="text/html" hreflang="en" href="https://planetscale.com/blog" />
      <link rel="self" type="application/atom+xml" href="https://planetscale.com/blog/feed.vitess-mysql.atom" />
      <id>https://planetscale.com/blog/feed.vitess-mysql.atom</id>
      <updated>2026-09-28T00:00:00.000Z</updated>

  
      <entry>
        <title>Handling hot shards</title>
        <link href="https://planetscale.com/blog/hot-shards-hotter-tenants" />
        <id>https://planetscale.com/blog/hot-shards-hotter-tenants</id>
        <published>2026-09-28T00:00:00.000Z</published>
        <updated>2026-09-28T00:00:00.000Z</updated>
        
        <author>
          <name>Simeon Griggs</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="neki" />
        
        <category term="postgres" />
        
        <content type="html"><![CDATA[<p>Since the launch of Neki we&#x27;ve talked a lot about sharding with basic examples to demonstrate how Neki splits a table&#x27;s rows evenly across shards. Neki&#x27;s router reads your <a href="https://planetscale.com/blog/what-is-a-data-topology">data topology</a> to handle the placement of writes and to find the correct shard for reads.</p><p>But life in production is never so simple.</p><p>Imagine your application&#x27;s Neki database is sharding on a <code>tenant_id</code> column. Makes sense. You want a nice even distribution of tenants across shards. But your app takes off, congratulations! Now, some tenants are hitting their shard at a greater size or volume than most. New product opportunities emerge that require cross-tenant queries.</p><p>Suddenly an even distribution of <em>tenants</em> is less useful than an even distribution of <em>data</em>.</p><p>You probably just have the wrong shard key, at least for some of your data. No problem, Neki gives you the tools to mitigate increased pressure on shards, and adapt your resharding strategy, without downtime.</p><h2 id="a-story-of-resharding-success"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#a-story-of-resharding-success">A story of resharding success</a></h2><p>While Neki is new, sharding isn&#x27;t. Neki is built by the maintainers of Vitess: a proven, scalable and flexible solution that has a great history of solving these exact problems.</p><p>Slack has published multiple articles (<a href="https://planetscale.com/blog/hot-shards-hotter-tenants#footnotes">1</a>, <a href="https://planetscale.com/blog/hot-shards-hotter-tenants#footnotes">2</a>) and talks (<a href="https://planetscale.com/blog/hot-shards-hotter-tenants#footnotes">3</a>) on how they had to modify their approach to sharding as product demand and requirements changed over time thanks to Slack&#x27;s rapid rise in popularity.</p><p>Their original tidy and <em>obvious</em> way to shard <strong>message</strong> and <strong>channel</strong> data was by the ID of the <strong>workspace</strong> to which they belonged. The logic of which <strong>workspace</strong> belonged to which <strong>shard</strong> was maintained in a cluster dedicated to sharding metadata.</p><p>The assumption was reasonable, that no single customer would ever outgrow the biggest database. But then a workspace of 10s of 1000s of users lands. Then 100s of 1000s. Vertical scaling and isolation bought time, but not enough.</p><p>Over time this became problematic.</p><iframe src="https://planetscale.com/blog/hot-shards-hotter-tenants/iframe#workspace-groups" title="Messages and channels grouped by workspace across two shards" loading="lazy"></iframe><p><em>With per-workspace sharding, a single hot tenant&#x27;s messages table quickly overwhelms the shard</em></p><p>As the product got more successful, some workspaces were far more demanding than others. Workspaces for large enterprises could contain over 100,000 users, which ballooned the initial payload the client application needed. Some <strong>workspaces&#x27; messages</strong> tables were getting too large for any one shard. Cross-workspace messaging became a requirement. A new enterprise grid feature needed to organize multiple workspaces under a single enterprise organization. All of this was being made complicated by the current &quot;shard by workspace&quot; strategy.</p><p>Their solution involved Vitess, a sharding solution for MySQL, to simplify resharding, starting with sharding <strong>messages</strong> by <strong>channel</strong> instead of <strong>workspace</strong>.</p><p>Not every table changed. Each table was explicitly sharded by the column that made the most sense for it: user ID, channel ID, <em>or</em> workspace ID. This created a simpler path to spreading out data load across shards and enabling cross-workspace communication.</p><iframe src="https://planetscale.com/blog/hot-shards-hotter-tenants/iframe#spread" title="Channels stay on two workspace shards. Messages from every workspace land on two message shards." loading="lazy"></iframe><p><em>Sharding the messages table by channel across shards smoothed out load and unlocked new product opportunities</em></p><h3 id="explicit-vs-automatic-sharding"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#explicit-vs-automatic-sharding">Explicit vs automatic sharding</a></h3><p>Among other benefits, shard allocation was no longer hidden in application logic and a separate metadata cluster. It was defined explicitly in Vitess and enforced by VTGate. Neki&#x27;s equivalent is the data topology: a declarative configuration that agents and humans can read to reason about where data is written and where it can be read from. You define the columns on which specific tables are sharded and the range of values each shard receives.</p><p>This is in contrast to automatic sharding solutions, where the database decides placement for you, which can result in unexpected or unpredictable placement of rows.</p><p>Agents love declarative configuration like a data topology because it&#x27;s foolproof to reason about exactly where data will land and why. Which tables are sharded, which key they&#x27;re sharded by, and which shards sharded rows are sharded to are all determined by the data topology.</p><p>At any time, you can add shards and change sharding strategies. After which, <a href="https://planetscale.com/docs/neki/data-migration#reshard-existing-tables">Reshard</a> copies existing rows to new shards as required and streams ongoing writes while the source keeps serving. When you switch traffic, Neki moves reads and writes to the new placement without taking the application offline.</p><h2 id="solving-resharding-with-neki"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#solving-resharding-with-neki">Solving resharding with Neki</a></h2><p>Should you suffer from the same success, and have already migrated to Neki, you&#x27;re in a great position as it gives you the tools to mitigate increased demand and/or adjust your sharding strategy with minimal effort and disruption.</p><p>Here&#x27;s three ways to handle increased demand and requirements. The first two buy you time, the last one is the best long-term solution.</p><h3 id="fix-1-scale-up"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#fix-1-scale-up">Fix 1: Scale up</a></h3><p>If increased demand has your databases hitting their limits, the easy answer is just &quot;add more resources.&quot; You could do that and stop here.</p><p>Each shard in a Neki database is a distinct Postgres cluster with its own resources. Configuration profiles can be distinct or shared across shards. You can temporarily solve your large tenant problem by assigning its shard a unique profile, vertically scaling it, and going about your business.</p><p>On Vitess, <a href="https://planetscale.com/docs/vitess/cluster-configuration/per-shard-sizing">per-shard sizing</a> does the same job by giving a hot shard a larger cluster size than the rest of its keyspace.</p><h3 id="fix-2-isolate-large-tenants"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#fix-2-isolate-large-tenants">Fix 2: Isolate large tenants</a></h3><p>Additionally, if your larger tenants are causing issues for their neighbours, you can isolate the range of tenants on any one shard.</p><p>Here&#x27;s a visual representation of distributing rows across shards. Note that <code>xxhash</code> doesn&#x27;t create a perfectly even balance of this small dataset, but would be relatively even over 1000s or more rows.</p><iframe src="https://planetscale.com/blog/hot-shards-hotter-tenants/iframe" title="Interactive: hash-range shards with a whale tenant isolated into its own key range" loading="lazy"></iframe><p>Since you&#x27;re in control, distribution can be as broad or fine grained as you like. In a simplified sharding example, an even distribution of hashed IDs across two shards would look like this:</p><div class="code-block" data-language="json"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">{</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">  "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key_ranges</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> [</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">    {</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_uid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "shard-a"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">end</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> },</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">    {</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_uid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "shard-b"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> }</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">  ]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>The start and end ranges in the code example above are only matching the first two characters of a hashed ID. You can go much finer and create a tighter range around the whale&#x27;s <code>tenant_id</code>.</p><p>Isolating the whale&#x27;s data will require resharding all tenants&#x27; data. Reshard copies rows onto new shards, so we cannot re-use the original source shards <code>shard-a</code> and <code>shard-b</code>. With three new shards created, the new layout below describes an updated placement to isolate the hot tenant&#x27;s data.</p><div class="code-block" data-language="json"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">{</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">  "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key_ranges</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> [</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">    {</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_uid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "shard-c"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">end</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80a3f1"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> },</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">    {</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_uid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "whale"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80a3f1"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">end</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80a3f2"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> },</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">    {</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">shard_uid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "shard-d"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> "</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "80a3f2"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> }</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">  ]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>However, you&#x27;ve now created an environment where one very large tenant can have one extremely large table. A table so large it too would benefit from being sharded. If CPU demands don&#x27;t get you, storage will. It may be time to make some structural sharding changes.</p><h3 id="fix-3-reshard-some-tables"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#fix-3-reshard-some-tables">Fix 3: Reshard some tables</a></h3><p>Vertical scaling and isolation of a tenant will only get you so far, but neither of the previous two fixes solves the root cause of your problem. Yesterday&#x27;s sharding strategy is unsuitable for today&#x27;s requirements.</p><p>Changing sharding strategy doesn&#x27;t mean sharding or resharding everything. Unless you&#x27;re Meta or Google you probably don&#x27;t need to shard your users table.</p><p>Investigate your application&#x27;s access patterns and query shapes to work out what other dimensions data can be sharded by. Look for tables which are regularly joined by a common column key, reshard so they are kept together.</p><p>Back to Slack&#x27;s example, messages were always queried by channel, never by workspace, and channel ID was already part of the messages table&#x27;s primary key. The right shard key had been there all along. Sharding messages by channel ID made fetching a channel and its messages a single-shard query, even when those messages were authored by users in different workspaces.</p><iframe src="https://planetscale.com/blog/hot-shards-hotter-tenants/iframe#by-channel" title="Before: messages, channels, and users share two workspace shards. After: messages spread onto two new shards, and channels and users stay." loading="lazy"></iframe><p>Cross-channel queries for messages, which would now be cross-shard queries, were limited to administrative or batch operations, not the critical path. This change cooled off hot spots and gave the team a lot more runway in terms of CPU and storage. Meanwhile, keeping the users table together was critical, as searching for &quot;all users in this workspace&quot; was a common access path.</p><p>While resharding isn&#x27;t something you&#x27;ll want to do often, it&#x27;s simpler once you are already within a sharded database. During resharding, Neki will continue to serve queries to existing data until the operation is complete.</p><h2 id="conclusion"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#conclusion">Conclusion</a></h2><p>Landing big customers and growing your product are good problems to have, but how much stress it causes you depends on the foundation you already have in place. With Neki as that foundation, you are choosing something you and your agents can easily understand, change, and adapt to, no matter which dimension your data grows by.</p><p>The longer you wait, the more tables outgrow their original shard key. What could have been one change becomes several at once. Don&#x27;t wait for a perfect future state. Shard the table that hurts today and address the rest as they need it.</p><p>Shard what hurts now.</p><h2 id="footnotes"><a href="https://planetscale.com/blog/hot-shards-hotter-tenants#footnotes">Footnotes</a></h2><ol><li><a href="https://slack.engineering/scaling-datastores-at-slack-with-vitess/">Scaling Datastores at Slack with Vitess</a></li><li><a href="https://slack.engineering/unified-grid-how-we-re-architected-slack-for-our-largest-customers/">Unified Grid: How We Re-Architected Slack for Our Largest Customers</a></li><li><a href="https://www.infoq.com/presentations/slack-scalability-2018/">Scaling Slack</a></li></ol>]]></content>
        <summary><![CDATA[What makes a good sharding strategy today might not work so well tomorrow. What happens when you have one tenant hitting your database orders of magnitude more than any other? Or product managers knocking down your door with new requirements? If you're already on Neki, fortunately these are issues we can solve.]]></summary>
      </entry>
    
      <entry>
        <title>Concurrency vs. Throughput: why more parallelism can make databases slower</title>
        <link href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql" />
        <id>https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql</id>
        <published>2026-08-07T00:00:00.000Z</published>
        <updated>2026-08-07T00:00:00.000Z</updated>
        
        <author>
          <name>Liz van Dijk</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Not long ago we watched a production MySQL database melt down for sixteen minutes.</p><p>The errors started as a trickle, a handful per minute, then fed on themselves:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>minute   errors/min   queries/s</span></span>
<span class="line"><span>  0          5          15,000   &#x3C;- a burst of work arrives</span></span>
<span class="line"><span>  2         60           3,900</span></span>
<span class="line"><span>  4        300           2,500</span></span>
<span class="line"><span>  6        550           2,000</span></span>
<span class="line"><span>  8        900           1,900</span></span>
<span class="line"><span> 10      1,400           1,500   &#x3C;- error peak = throughput trough</span></span>
<span class="line"><span> 12        700           1,700</span></span>
<span class="line"><span> 14        250           2,000</span></span>
<span class="line"><span> 16          0           2,500   &#x3C;- locks released, backlog drained</span></span>
<span class="line"><span> 18          0           8,500   &#x3C;- full recovery, throughput jumps 5x</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>The trigger was fairly mundane: A batch job opened a transaction against a hot table, took row locks, and then held them for fifteen minutes without committing. A common application bug, the kind that eventually sneaks into many large codebases.</p><p>What happened around this long transaction is the interesting bit! The queries piling up behind that transaction were mostly not blocked on its locks at all. They were simple reads, where InnoDB didn&#x27;t need to wait for row locks; it reads a consistent snapshot instead. Building that snapshot means walking back through the version history of every row the open transaction had touched, and that history grew for fifteen straight minutes.</p><p>This caused reads that normally took milliseconds to start blowing through their 90-second execution ceilings. The application retried them in a tight loop. Within minutes, more than ten thousand requests were piled up inside the storage engine. Processing each required reconstructing ever-longer version chains, and the resulting page reads outpaced the buffer pool&#x27;s ability to free memory.</p><p>Now requests that had nothing to do with the locked rows, that touched entirely different tables, began failing too. The otherwise correctly sized buffer pool suddenly became too small to serve the crowd of queries.</p><p>At PlanetScale, we run our MySQL databases with Vitess, whose configured transaction timeout eventually killed the long-running transaction. Its locks were released, and the sixteen-minute backlog drained in about thirty seconds.</p><p>One slow transaction should not take down a database, and much of Vitess&#x27;s plumbing is built around avoiding exactly this situation. So what went wrong? This was caused by the combination of the single long-running query and the ten thousand requests allowed in after.</p><h2 id="migration-from-cloud-sql-to-vitess"><a href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql#migration-from-cloud-sql-to-vitess">Migration from Cloud SQL to Vitess</a></h2><p>Some context on how a seemingly healthy database could end up like this in the first place.</p><p>This workload had recently been migrated onto PlanetScale from Cloud SQL, which ran Managed Connection Pooling, a thread-pool-style layer, in front of the database. A thread pool caps how many statements execute at once, commonly around a thousand, and queues the rest. A client arriving when the pool is full waits for a slot, usually for milliseconds, occasionally for a few hundred milliseconds. This cap can protect InnoDB&#x27;s internals even under extreme load.</p><p>On PlanetScale, every MySQL shard sits behind a Vitess proxy called <a href="https://vitess.io/docs/reference/programs/vttablet/">vttablet</a>. These have a transaction pool, which caps how many transactions can be open against MySQL at once. Unlike a thread pool, when this pool fills, requests wait a set amount of time and then <em>fail with an error</em>.</p><p>For this workload, that small difference made the issue way worse. Errors propagated up an application stack that had never needed to handle them (since the Cloud SQL thread pool queued requests, pool-full errors effectively didn&#x27;t exist there).</p><p>The solution that was initially tried was to <strong>raise the transaction pool cap and increase the timeout</strong>.</p><p>The pool was increased to ten thousand, a number chosen not by sizing but because it made the errors stop. However, this now limited Vitess&#x27;s ability to control the backpressure between the application and the database storage engine.</p><h2 id="why-high-concurrency-reduces-mysql-throughput"><a href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql#why-high-concurrency-reduces-mysql-throughput">Why high concurrency reduces MySQL throughput</a></h2><p>Picture database transactions as items flowing down a conveyor belt (Factorio, anyone?). If they never interacted, throughput would scale in a straight line: twice the items in flight, twice the work done. That is the dream of a perfectly <a href="https://en.wikipedia.org/wiki/Shared-nothing_architecture">shared-nothing</a> system, but rarely is that achieved. Somewhere on the belt there is a junction where multiple conveyor belts meet. In MySQL, this is the equivalent of a hot row, a latch, a CPU run queue, etc.</p><p>Below is a simple playground to visualize how request rate and queuing impact latency. Adjust the <code>ARRIVALS / S</code> slider to see the impact.</p><iframe src="https://planetscale.com/blog/doing-more-with-less/iframe#junction" title="Interactive: rising load against a single junction" loading="lazy"></iframe><p><a href="https://en.wikipedia.org/wiki/Little%27s_law">Little&#x27;s Law</a> describes this well. In steady state, <code>N = X * W</code>. Work-in-flight equals throughput times the time each request spends inside the database. Rearranged, <code>X = N / W</code>.</p><p>Adding in-flight requests raises throughput only as long as query execution time holds steady. If each new arrival stretches the execution/wait time of all the other queries within the database, the denominator now grows along with the numerator.</p><p>Just how badly can that go? <a href="https://www.perfdynamics.com/Manifesto/USLscalability.html">Gunther&#x27;s Universal Scalability Law</a> splits the cost of concurrency into three terms:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>                 γN</span></span>
<span class="line"><span>X(N) = ───────────────────────────</span></span>
<span class="line"><span>        1 + α(N−1) + βN(N−1)</span></span>
<span class="line"><span></span></span></code></pre></div></div><ul><li><code>N</code> is how much work you allow to run at once.</li><li><code>γ</code> is the ideal single-request throughput: the linear scaling coefficient if requests never interacted.</li><li><code>α</code> (contention) is the cost of taking turns for a shared resource.</li><li><code>β</code> (coherency) is the cost of requests making <em>each other</em> slower. Put differently, the work a database must do to keep shared state consistent across everything in flight.</li></ul><p>Note the multiplier <code>N(N−1)</code>, which grows with the number of <em>pairs</em> of requests. When <code>N</code> doubles, this coherency cost roughly quadruples.</p><p>Past a critical point, <code>N_max = √((1−α)/β)</code>, <code>β</code> dominates, and total throughput actually starts to reverse. Gunther calls it retrograde scaling: beyond the peak, every request you admit requires coordination overhead and makes everything slower.</p><iframe src="https://planetscale.com/blog/doing-more-with-less/iframe#usl-curve" title="Interactive: the Universal Scalability Law curve" loading="lazy"></iframe><p>Let&#x27;s look at how the problem from earlier maps to this equation. The row locks were <code>α</code>. The transactions that wanted those rows had to take turns, and no amount of concurrency changed the queue&#x27;s drain rate. <code>β</code>, the real issue, was InnoDB trying to process so many queries at once: ten thousand concurrent snapshot reads, each made more expensive by the version history every <em>other</em> in-flight transaction was generating. The per-request cost rose with the increase in in-flight requests.</p><p>This is why focusing on &quot;lock contention&quot; does not explain the issue. The lock is just one of many potential junctions. It is the damage <em>around</em> the junction, the mutual slowdown of everything admitted past it, that scales with the square of concurrency and turns a slow batch job into a wider outage.</p><h2 id="reducing-pool-size-and-implementing-queuing"><a href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql#reducing-pool-size-and-implementing-queuing">Reducing pool size and implementing queuing</a></h2><p>To solve this, we implemented the reverse of the change that set this up, in both dimensions at once:</p><ol><li>Reduce the Vitess transaction pool size from ten thousand to roughly the thousand the old thread pool used. We now did this consciously and with evidence-backed proof: that workload had run successfully for years with a pool of that size.</li><li>Instead of erroring after waiting, a transaction arriving at a full pool now queues for a slot with a longer timeout.</li></ol><p>In other words, we configured Vitess&#x27;s vttablet layer to mimic the old thread pool&#x27;s behavior.</p><p>For this to be safe, two things must be true. The clients have to tolerate periodic waiting during bursts and the wait itself has to be bounded, so that a truly undersized system announces itself with timeouts at the queue rather than accumulating latency forever. Both are true for this workload.</p><p>The morning after rolling out this new configuration, it met its first test. A traffic burst arrived on a different Vitess shard of the same database, one that handles an order of magnitude more steady traffic. At peak, the pool handled around twenty-five thousand transaction pool slot requests <em>per second</em> against its thousand-odd available slots. Here are the results, in the same view we opened with:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>minute   slot requests/s   errors/min   queries/s</span></span>
<span class="line"><span>  0             3,000           0         58,000</span></span>
<span class="line"><span> 10             9,000           0         61,000</span></span>
<span class="line"><span> 20            26,000           0         60,000   &#x3C;- peak pressure, throughput flat</span></span>
<span class="line"><span> 30            17,000           0         59,000</span></span>
<span class="line"><span> 40             8,000           0         62,000</span></span>
<span class="line"><span> 50             4,000           0         60,000</span></span>
<span class="line"><span> 60             2,000           0         57,000   &#x3C;- burst drained, queue empty</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>During the earlier incident, errors climbed to 1,400 a minute and throughput fell to a tenth of what the workload requested.</p><p>With the new and improved configuration, the application experienced no disruption and QPS didn&#x27;t drop.</p><p>Over the entire day, database health was much better:</p><ul><li><strong>One rejected transaction.</strong> Despite slot requests/s at times jumping as high as 40,000, the queue absorbed effectively everything. Waits sat comfortably within the timeout.</li><li><strong>Fewer than two hundred statements executing inside MySQL at any instant.</strong> InnoDB managed the bursts well. Sixty thousand queries a second through fewer than two hundred concurrent slots is Little&#x27;s Law again: about three milliseconds apiece.</li><li><strong>Lock contention didn&#x27;t change.</strong> The hot-row pattern was just a minor contributor here. We were able to solve the problem without changing the lock contention dynamics.</li></ul><p>This allowed the database to do less work <em>at once</em>, allowing it to do more work in total.</p><p>Below is another playground to visualize the difference between an open request flow and a gated one. Send bursts of traffic, simulate a locking event, and see the difference between a gated and ungated solution with the buttons below.</p><iframe src="https://planetscale.com/blog/doing-more-with-less/iframe#before-after" title="Interactive: same workload, opposite overflow behavior" loading="lazy"></iframe><h2 id="when-to-limit-database-concurrency"><a href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql#when-to-limit-database-concurrency">When to limit database concurrency</a></h2><p>This is not a universal recommendation to tightly limit concurrency. Its applicability must be understood in the context of your workload.</p><ul><li>Workloads built on pessimistic locking and contended shared state.</li><li>Hot rows updated by many workers.</li><li><code>SELECT ... FOR UPDATE</code> on popular keys.</li><li>Long transactions holding locks while they do unrelated work.</li><li>Counters, balances, job queues.</li></ul><p>If that describes your workload, added concurrency might just decrease your throughput!</p><p>The principles described here are not MySQL-specific. Postgres can have similar problems, but also a similar solution: <a href="https://planetscale.com/docs/postgres/connecting/pgbouncer">PgBouncer</a>&#x27;s transaction pooling exists to allow for more client concurrency than server concurrency. This is why our <a href="https://planetscale.com/docs/postgres">Postgres offering</a> comes with a local PgBouncer, and can be configured with dedicated primary and replica PgBouncer instances too.</p><h2 id="building-systems-that-handle-backpressure"><a href="https://planetscale.com/blog/concurrency-vs-throughput-vitess-mysql#building-systems-that-handle-backpressure">Building systems that handle backpressure</a></h2><p><strong>Backpressure can&#x27;t always be avoided, so make conscious choices about the expected behavior.</strong> The answer is almost never to simply raise a limit out of reach, but to fix what happens when that limit is reached.</p><p>Sometimes the fastest thing you can do for a busy system is to simply let less happen at once.</p>]]></content>
        <summary><![CDATA[Increasing throughput sometimes requires reducing parallelism. A look into why this is the case for Vitess + MySQL databases]]></summary>
      </entry>
    
      <entry>
        <title>Announcing Vitess 23</title>
        <link href="https://planetscale.com/blog/announcing-vitess-23" />
        <id>https://planetscale.com/blog/announcing-vitess-23</id>
        <published>2025-11-04T00:00:00.000Z</published>
        <updated>2025-11-04T00:00:00.000Z</updated>
        
        <author>
          <name>Vitess Engineering Team</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>We’re excited to release Vitess 23.0.0 — the latest major version of Vitess — bringing new defaults, better operational tooling, and refined metrics. This release builds on the strong foundation of version 22 and is designed to make deployment and observability smoother, while continuing to scale MySQL workloads horizontally with confidence.</p><hr><h2 id="why-this-release-matters"><a href="https://planetscale.com/blog/announcing-vitess-23#why-this-release-matters">Why this release matters</a></h2><p>For production users of Vitess, this release is meaningful in several ways:</p><ul><li><strong>Upgrading defaults:</strong> Moving to MySQL 8.4 as default future-proofs deployments and signals forward compatibility.</li><li><strong>Better metrics:</strong> The added observability enables deeper insights into transaction routing, shard behavior, and recovery actions — making debugging and alerting more precise.</li><li><strong>Clean-ups &amp; deprecations:</strong> Removing legacy metrics and APIs simplifies monitoring and avoids confusion.</li><li><strong>Operational strength:</strong> Enhanced VTOrc and topology controls reduce risk in large-scale fleets and tighten security boundaries.</li></ul><hr><h2 id="whats-new-in-vitess-23"><a href="https://planetscale.com/blog/announcing-vitess-23#whats-new-in-vitess-23">What’s new in Vitess 23</a></h2><p>Here are some of the standout changes you should know about:</p><h3 id="new-default-versions"><a href="https://planetscale.com/blog/announcing-vitess-23#new-default-versions">New default versions</a></h3><ul><li>The default MySQL version for the <code>vitess/lite:latest</code> image has been bumped from <strong>8.0.40</strong> to <strong>8.4.6</strong>. → <a href="https://github.com/vitessio/vitess/pull/18569">PR #18569</a></li><li>VTGate now advertises MySQL version 8.4.6 by default (instead of 8.0.40). If your backend uses a different version, set the <code>mysql_server_version</code> flag accordingly. → <a href="https://github.com/vitessio/vitess/pull/18568">PR #18568</a></li><li><strong>Important upgrade detail for operator users:</strong> When upgrading from MySQL 8.0 → 8.4 with the Vitess Operator, you must:<ol><li>Add <code>innodb_fast_shutdown=0</code> to your extra .cnf in the YAML.</li><li>Apply the file and wait until all pods are healthy.</li><li>Switch the image to <code>vitess/lite:v23.0.0</code>.</li><li>Remove <code>innodb_fast_shutdown=0</code> and re-apply. This is only required once when crossing 8.0 → 8.4. See the <a href="https://github.com/planetscale/vitess/blob/5164fafb70b43192f4965842ae0a8679f8b2dd83/changelog/23.0/23.0.0/release_notes.md#upgrade-instructions-for-operator-users">official release notes</a>.</li></ol></li></ul><h3 id="new-and-improved-metrics"><a href="https://planetscale.com/blog/announcing-vitess-23#new-and-improved-metrics">New and improved metrics</a></h3><ul><li><strong>VTGate:</strong> new metric <code>TransactionsProcessed</code> (dimensions: Shard, Type) counting transactions processed at VTGate by shard and transaction type. → <a href="https://github.com/vitessio/vitess/pull/18408">PR #18408</a></li><li><strong>VTOrc:</strong> new metric <code>SkippedRecoveries</code> (dimensions: RecoveryName, Keyspace, Shard, Reason) tracking how many recoveries were skipped and why. → <a href="https://github.com/vitessio/vitess/pull/18405">PR #18405</a></li></ul><p>These improvements strengthen observability and help operators track system behavior with finer granularity.</p><h3 id="deprecations-and-removals"><a href="https://planetscale.com/blog/announcing-vitess-23#deprecations-and-removals">Deprecations and removals</a></h3><ul><li><strong>VTOrc metric rename:</strong> <code>DiscoverInstanceTimings</code> → <code>DiscoveryInstanceTimings</code>. → <a href="https://github.com/vitessio/vitess/pull/18406">PR #18406</a></li><li><strong>Removed deprecated VTGate metrics:</strong> <code>QueriesProcessed</code>, <code>QueriesRouted</code>, <code>QueriesProcessedByTable</code>, <code>QueriesRoutedByTable</code>. → <a href="https://github.com/vitessio/vitess/pull/17727">PR #17727</a></li><li><strong>Removed VTOrc API endpoint:</strong> <code>/api/aggregated-discovery-metrics</code>. → <a href="https://github.com/vitessio/vitess/pull/18407">PR #18407</a></li></ul><h3 id="topology-vtorc-enhancements"><a href="https://planetscale.com/blog/announcing-vitess-23#topology-vtorc-enhancements">Topology &amp; VTOrc enhancements</a></h3><ul><li>The <code>--consul_auth_static_file</code> flag now <strong>requires at least one credential</strong> in the provided JSON. → <a href="https://github.com/vitessio/vitess/pull/18409">PR #18409</a></li><li>VTOrc now supports <strong>dynamic control of EmergencyReparentShard-based recoveries.</strong> → <a href="https://github.com/vitessio/vitess/pull/18410">PR #18410</a> These changes improve operational safety and resilience for cluster management.</li></ul><h3 id="vttablet-and-cli-docker-updates"><a href="https://planetscale.com/blog/announcing-vitess-23#vttablet-and-cli-docker-updates">VTTablet and CLI / Docker updates</a></h3><ul><li>Managed MySQL configuration now defaults to <code>caching-sha2-password</code>. → <a href="https://github.com/vitessio/vitess/pull/18403">PR #18403</a></li><li>MySQL timezone environment propagation improved. → <a href="https://github.com/vitessio/vitess/pull/18404">PR #18404</a></li><li>gRPC tabletmanager client error behaviors clarified. → <a href="https://github.com/vitessio/vitess/pull/18402">PR #18402</a></li><li>Docker image workflows and flags updated for consistency. → <a href="https://github.com/vitessio/vitess/pull/18411">PR #18411</a></li></ul><hr><h2 id="upgrade-notes"><a href="https://planetscale.com/blog/announcing-vitess-23#upgrade-notes">Upgrade notes</a></h2><ul><li>Review custom dashboards: if you relied on removed metrics, update them to new ones (<code>TransactionsProcessed</code>, etc.).</li><li>Operator users upgrading 8.0 → 8.4: follow the <a href="https://github.com/planetscale/vitess/blob/5164fafb70b43192f4965842ae0a8679f8b2dd83/changelog/23.0/23.0.0/release_notes.md#upgrade-instructions-for-operator-users">four-step sequence</a>.</li><li>If you override <code>mysql_server_version</code> in VTGate, ensure it matches your backend MySQL version.</li><li>Test changes involving reparenting, recovery, or Consul integration in staging first.</li></ul><hr><h2 id="whats-next"><a href="https://planetscale.com/blog/announcing-vitess-23#whats-next">What’s next</a></h2><p>We continue to evolve Vitess toward:</p><ul><li>Deeper MySQL 8.4 compatibility.</li><li>Expanded observability across VReplication, MoveTables, and Resharding.</li><li>Ongoing Operator improvements for reliability and clarity.</li></ul><hr><h2 id="thanks-and-acknowledgements"><a href="https://planetscale.com/blog/announcing-vitess-23#thanks-and-acknowledgements">Thanks and acknowledgements</a></h2><p>This release was made possible by dozens of contributors from the Vitess community and the PlanetScale team. Thank you for filing bugs, testing RCs, and helping keep Vitess robust and scalable.</p><p>Let’s keep scaling.</p><blockquote><p>“Scale beyond single MySQL instances — without giving up SQL semantics.”</p><p>– The Vitess Team</p></blockquote><hr><p>To explore every detail, see the 👉 <a href="https://github.com/planetscale/vitess/blob/5164fafb70b43192f4965842ae0a8679f8b2dd83/changelog/23.0/23.0.0/release_notes.md">Full Release Notes for Vitess 23.0.0</a></p>]]></content>
        <summary><![CDATA[Vitess 23 is now generally available]]></summary>
      </entry>
    
      <entry>
        <title>Announcing Vitess 22</title>
        <link href="https://planetscale.com/blog/announcing-vitess-22" />
        <id>https://planetscale.com/blog/announcing-vitess-22</id>
        <published>2025-04-29T00:00:00.000Z</published>
        <updated>2025-04-29T00:00:00.000Z</updated>
        
        <author>
          <name>Vitess Engineering Team</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>The Vitess maintainers are happy to announce the release of <a href="https://github.com/vitessio/vitess/releases/tag/v22.0.0">version 22.0.0</a>, along with <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.15.0">version 2.15.0</a> of the Vitess Kubernetes Operator. This release is the first to benefit from a 6-month-long development cycle, after our <a href="https://github.com/vitessio/enhancements/pull/12">recent change to the release cadence</a>.</p><p>Version 22.0.0 comes with significant enhancements to query serving and cluster management. These changes have allowed Vitess to be more performant and easier to operate compared to version 21. This blog post highlights some of the major changes that went into the release. For a more detailed description of the release, please refer to the <a href="https://github.com/vitessio/vitess/releases/tag/v22.0.0">release notes</a>.</p><hr><h2 id="summary"><a href="https://planetscale.com/blog/announcing-vitess-22#summary">Summary</a></h2><ul><li><strong>Query Serving</strong>: Prepared statements and new VTGate metrics</li><li><strong>Cluster Management and VTOrc</strong>: Stalled-disk recovery, improved errant GTID discovery, and VTOrc performance improvements</li><li><strong>Kubernetes Operator</strong>: Automated backups, Kubernetes 1.32 support, better examples</li><li><strong>Performance</strong>: Improvements compared to v21.0.0</li></ul><hr><h2 id="query-serving"><a href="https://planetscale.com/blog/announcing-vitess-22#query-serving">Query Serving</a></h2><p>Vitess 22.0 brings a host of query‑serving enhancements focused on observability, smarter handling of prepared statements, GA support for sharded views, and feature‑complete atomic distributed transactions.</p><h3 id="observability"><a href="https://planetscale.com/blog/announcing-vitess-22#observability">Observability</a></h3><p>Vitess now emits metrics for query execution plans, which help you analyze execution patterns and optimize costly plans. See the <a href="https://vitess.io/docs/22.0/reference/query-serving/metrics/">Query Serving metrics guide</a>.</p><h3 id="prepared-statements"><a href="https://planetscale.com/blog/announcing-vitess-22#prepared-statements">Prepared Statements</a></h3><p>We have overhauled VTGate’s prepare pipeline: prepared statements are cached as raw SQL to skip redundant parsing. Also, deferred execution plans are generated using actual bind values for more optimized, parameter‑aware planning.</p><h3 id="views-and-atomic-distributed-transactions"><a href="https://planetscale.com/blog/announcing-vitess-22#views-and-atomic-distributed-transactions">Views and Atomic Distributed Transactions</a></h3><p>Sharded view support is GA now, and the atomic distributed transaction feature is complete. Learn more in the <a href="https://vitess.io/docs/22.0/reference/features/distributed-transaction/">Distributed Transactions guide</a>.</p><h2 id="cluster-management-and-vtorc"><a href="https://planetscale.com/blog/announcing-vitess-22#cluster-management-and-vtorc">Cluster Management and VTOrc</a></h2><p>There were several performance and usability enhancements to VTOrc in the Vitess 22.0 release. It now operates more efficiently, with fewer topology service calls and better overall resource consumption.</p><p>VTOrc now supports specifying shard ranges in the <code>--keyspaces-to-watch</code> flag, making it easier to configure in large deployments.</p><p>This release also introduces stalled disk recovery to VTOrc, along with improved handling of errant GTID detection. Additionally, vttablets are now prevented from joining the replication topology if they contain errant GTIDs.</p><p>Finally, we’ve added a new semi-sync monitor to VTOrc. This helps detect and unblock vttablets that are stuck waiting for semi-sync ACKs – a situation that can occur if ACKs are lost due to network issues.</p><h2 id="kubernetes-operator"><a href="https://planetscale.com/blog/announcing-vitess-22#kubernetes-operator">Kubernetes Operator</a></h2><p>The v22.0.0 release of Vitess comes along with the v2.15.0 release of our Kubernetes operator. This new release follows the same release cycle as Vitess. As part of this release, the officially supported Kubernetes version is v1.32.</p><p>The original implementation of automated and scheduled backups done in v2.13.0 used an empty pod running <code>vtctldclient BackupShard</code> to execute the backup. This approach had trade-offs: the backup process ran faster but effectively removed a serving tablet from the available tablet pool. In v2.15.0, we have changed the implementation to use a VTBackup pod instead, to be consistent with our own recommendations for production deployments and to reduce impact on availability.</p><p>All our provided examples have been enhanced to illustrate how the <code>VitessOperator</code> and <code>VitessCluster</code> can be run in two different namespaces.</p><p>Please refer to the <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.15.0">operator release notes</a> to learn more about v2.15.0.</p><h2 id="performance"><a href="https://planetscale.com/blog/announcing-vitess-22#performance">Performance</a></h2><p>In this release, we have reduced query latency and cut memory allocations across Vitess through targeted optimizations:</p><ul><li><strong>gRPC Codec upgrade</strong>: Vitess now uses gRPC with Codec v2 and a pooled buffer, yielding a ~3% QPS improvement and ~13% reduction in per‑request allocations.</li><li><strong>Normalizer AST walk reduction</strong>: Merged two normalization AST passes into one, speeding VTGate’s normalizer by ~5% and cutting allocations per operation by ~5.5%.</li><li><strong>AST rewriting optimization</strong>: Combined RewriteAST and Normalizer into a single pass, trimming planner latency by ~2.7% and reducing allocations by ~4.3%.</li><li><strong>Merge‑sort avoidance on single‑shard queries</strong>: VTGate now skips redundant merge sorts when only one shard is involved, eliminating unnecessary CPU work for the common single‑shard case.</li></ul><hr><h2 id="migrate-and-learn-more"><a href="https://planetscale.com/blog/announcing-vitess-22#migrate-and-learn-more">Migrate and Learn More</a></h2><p>To ease migration from a previous version to v22.0.0, it is highly recommended to read the release notes of both <a href="https://github.com/vitessio/vitess/releases/tag/v22.0.0">Vitess</a> and the <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.15.0">Kubernetes Operator</a>. The entire <a href="https://github.com/vitessio/vitess/blob/main/changelog/22.0/22.0.0/changelog.md">changelog</a> for this version is available too.</p><p>It is also recommended to explore our <a href="https://vitess.io/docs/22.0/">documentation for v22.0.0</a>, where you can find step-by-step user guides, best practices, and tips to run Vitess.</p><h2 id="community"><a href="https://planetscale.com/blog/announcing-vitess-22#community">Community</a></h2><p>As an open-source project, we truly appreciate feedback, insights, and contributions from our community. Whether you want to share a story, ask a question, or anything else, you can reach out to us on <a href="https://github.com/vitessio/vitess">GitHub</a> or in our <a href="http://vitess.io/slack">Slack</a>.</p>]]></content>
        <summary><![CDATA[Vitess 22 is now generally available]]></summary>
      </entry>
    
      <entry>
        <title>Faster interpreters in Go: Catching up with C++</title>
        <link href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp" />
        <id>https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp</id>
        <published>2025-03-20T00:00:00.000Z</published>
        <updated>2025-03-20T00:00:00.000Z</updated>
        
        <author>
          <name>Vicent Martí</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>The SQL evaluation engine that ships with Vitess, <a href="https://github.com/vitessio/vitess">the open-source database that powers PlanetScale</a>, was originally implemented as an AST evaluator that used to operate directly on the SQL AST generated by our parser. Over this past year, we&#x27;ve gradually replaced it with a Virtual Machine which, despite being written natively in Go, performs similarly to the original C++ evaluation code in MySQL. Most remarkably, the new Virtual Machine has repeatedly proven itself easier to maintain than the original Go interpreter, even though it&#x27;s orders of magnitude faster. Let&#x27;s review the implementation choices we&#x27;ve made to get these surprising results.</p><h2 id="whats-a-sql-evaluation-engine"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#whats-a-sql-evaluation-engine">What&#x27;s a SQL evaluation engine?</a></h2><p>Vitess has been designed for unlimited horizontal scaling. To accomplish this, all queries to a Vitess cluster must go through a <code>vtgate</code>. Since you can deploy as many <code>vtgate</code> instances as you want, because they’re essentially stateless, this allows you to grow the capacity of your cluster linearly.  The job of each gate is the most complex part of the whole distributed system. It parses the SQL of the incoming queries and creates a shard-aware query plan, which we evaluate in one or many of the shards of the cluster. Then, we aggregate the results of these evaluations, and return them to the user.</p><p>One of the reasons why Vitess works so well in practice (in both performance and in ease of adoption) is that every shard in a cluster is backed by a real MySQL instance. Even the more complex SQL queries can be decomposed into simpler statements that are evaluated in the underlying MySQL database. Hence, the results of these queries always match what you’d expect from querying MySQL directly.</p><p>However, SQL queries in the real world can get <em>really wild</em>. We need to support pretty much every kind of query that a normal MySQL instance supports, but we need to evaluate it across several MySQL instances. This means that sometimes, we don’t get to fall back to MySQL to evaluate all our SQL expressions.</p><p>Think of a rather simple query such as this:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">item_id</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">SUM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">count</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">AVG</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">price</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">AS</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> avg_price</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> inventory</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">state</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">warehouse</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> IN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ? </span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">GROUP BY</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> inventory</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">item_id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">HAVING</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  avg_price </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">></span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 100</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Assuming this query is executed in a sharded Vitess cluster, the inventoried items can exist in any of the shards. Hence, our query planner will prepare a plan that queries all shards in parallel, <a href="https://planetscale.com/blog/grouping-and-aggregations-on-vitess">pushing down part of the aggregation to MySQL</a>, and then we&#x27;ll perform the aggregations (<code>SUM</code> and <code>AVG</code>) locally in the <code>vtgate</code>. The <code>state</code> and <code>warehouse</code> checks in the <code>WHERE</code> clause can and will be executed directly on the MySQL instance that powers each shard. But the last expression, <code>avg_price &gt; 100</code>, applies to the result of the aggregation, which is only available inside Vitess. This is where the Vitess evaluation engine comes in.</p><p>Our evaluation engine is an interpreter that supports the majority of the scalar expressions in the SQL dialect used by MySQL. This does not include high level constructs such as performing a <code>JOIN</code>,  the grouping of a <code>GROUP BY</code>, etc (these are performed directly by the planner, as we’ve seen), but the actual sub-expressions that you’d see as the condition of a <code>WHERE</code> clause, or a <code>GROUP BY</code> clause. Any piece of SQL that cannot be lowered to be executed in MySQL by the planner is evaluated locally in Go by the engine.</p><p>Of course, these SQL sub-expressions are not arbitrarily complex. They are not even Turing complete (as they cannot loop!), so you may think that a statement like <code>avg_price &gt; ?</code> would be trivial to evaluate, but as in most engineering problems, there’s a wealth of nuance when doing these things in the real world.</p><p>SQL is an incredibly dynamic language full of quirks, and the SQL in MySQL, doubly so. We have spent an inordinate amount of time getting every single corner case of SQL evaluation to match exactly MySQL’s behavior. In fact, our <a href="https://github.com/vitessio/vitess/blob/main/go/vt/vtgate/evalengine/testcases/cases.go">test</a> <a href="https://github.com/vitessio/vitess/blob/main/go/vt/vtgate/evalengine/testcases/inputs.go">suite</a> and <a href="https://github.com/vitessio/vitess/tree/main/go/vt/vtgate/evalengine/integration">fuzzer</a> are so comprehensive that we routinely find bugs in the original MySQL evaluation engine, which we have to fix upstream (<a href="https://github.com/mysql/mysql-server/pull/602">like this collation bug</a>, <a href="https://github.com/mysql/mysql-server/pull/517">this issue in the <code>insert</code> SQL function</a> or <a href="https://github.com/mysql/mysql-server/pull/515">this bug when searching substrings</a>). Nonetheless, being fully accurate is not enough. For most queries, these expressions are evaluated once or even more than once <em>for every returned row</em>, so in order to not introduce additional overhead, evaluation needs to be as quick as possible.</p><p>As discussed earlier, the first version of the evaluation engine in Vitess was an AST-based interpreter, operating directly on top of the SQL AST generated by our parser. This was a very straightforward design that allowed us to focus on <em>accuracy</em>, at the expense of performance. Let&#x27;s discuss our new design for replacing this interpreter with a fully fledged virtual machine which is both faster and easier to maintain. Starting with the basics.</p><h2 id="the-shapes-of-an-interpreter"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#the-shapes-of-an-interpreter">The shapes of an interpreter</a></h2><p>For those new to programming language implementations, there are roughly 3 ways to execute a <em>dynamic</em> language at runtime. In increasing level of complexity <em>and</em> performance:</p><ol><li>An AST-based interpreter, where the syntax of the language is parsed into an AST and evaluation is performed by recursively walking each node of the AST and computing the results. <em>(this is the way the <code>evalengine</code> in Vitess used to work!)</em></li><li>A bytecode VM, where the AST is compiled into binary bytecode that can be evaluated by a virtual machine — a piece of code that simulates a CPU, but with higher-level instructions. <em>(this is what we&#x27;ve recently shipped!)</em></li><li>A JIT compiler, in which the bytecode is compiled directly into the host platform&#x27;s native instructions, so it can be executed directly by the CPU without being interpreted by a Virtual Machine. <em>(we&#x27;ll talk about this later!)</em></li></ol><p>The first thing to consider here is whether the upgrade from an AST interpreter to a virtual machine makes sense from a performance point of view. Here’s an intuition: SQL expressions are incredibly dynamic (when it comes to typing), very high level (when it comes to each primitive operation), and with very little control flow (when it comes to evaluation -- SQL expressions don&#x27;t really loop, and conditionals are rare; their flow is always lineal!). This can lead us to believe that there&#x27;s no performance to be squeezed from translating the AST-based evaluation engine into bytecode. The AST is already well suited for high level operations and type-switching!</p><p>This is only <em>superficially</em> true. Lots of programming languages are highly dynamic and they manage to run in bytecode VMs much more efficiently than with an AST interpreter. A clear example of this is the now ancient transition that Ruby did from its original AST interpreter in MRI to YARV, a bytecode VM. Python also did a similar switch very early on. And you can bet that literally no JavaScript engines are using AST evaluation: even though the goal of these engines is to start running JS as soon as possible, they still compile to (very efficient) bytecode interpreters before JIT compilation kicks in.</p><p>So where’s the advantage of a virtual machine versus an AST interpreter? A lot of it boils down to instruction dispatching, which can be made very fast (more on this later!). But it is true that for SQL expressions, we’re actually going to execute very few instructions. Hence, to squeeze performance out of the VM, we’re going to have to come up with new tricks.</p><p>The initial approach I had in mind for our SQL virtual machine was based on <a href="https://publications.sba-research.org/publications/dls10.pdf">Efficient Interpretation using Quickening</a> by Stefan Brunthaler. The idea behind this paper is that dynamic programming languages are very hard to execute efficiently because of the lack of information about types. A simple expression such as <code>a + 1</code> must be interpreted in a completely different way depending on whether <code>a</code> is an integer, a floating point number of even a string. To optimize these operations in practice, the paper suggests rewriting the bytecode from more generic instructions (e.g. the sum operator that needs to figure out the types of the two operands to know how to sum them) into specific static instructions which are specialized for the types they operate on at runtime (e.g. the sum operator that knows that both operands are integers and can sum them right away).</p><p>To do that, a quickening VM needs to figure out <em>at runtime</em> the types of the expressions being evaluated and incrementally rewrite the bytecode into instructions that operate on them. This is very hard to do in practice! But after implementing a good chunk of specialized instructions for the different types of operators in SQL and attempting to runtime rewrite them, I noticed an opportunity to take the idea even further by making it more efficient and, crucially, simpler.</p><p>It turns out that the semantic analysis we perform in Vitess is advanced enough that, through careful integration with the upstream MySQL server and its information schema, it can be used to <em>statically type the AST of the SQL expressions we were executing</em>. This took a lot of effort to implement, but resulted in a big win: since the planner knows the types of the actual inputs that will be used to evaluate each SQL expression, we can derive from those the types of all sub-expressions at compilation time, resulting in byte-code that is already specialized without requiring runtime rewriting.</p><p>Now we just need to implement a Virtual Machine to efficiently interpret the specialized bytecode!</p><h2 id="an-efficient-virtual-machine-in-go"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#an-efficient-virtual-machine-in-go">An efficient Virtual Machine in Go</a></h2><p>Implementing a VM usually involves a lot of complexity. As we’ve explained, you have to write a compiler that processes the input expression AST and generates the corresponding binary instructions (you have to come up with an encoding even!) and <em>afterwards</em> you have to implement the actual VM, which decodes each instruction and performs the corresponding operation. And you have to constantly keep these in sync! Any mismatches between the compiler that emits the bytecode and the VM that executes it are often catastrophic and very hard to debug.</p><p>Historically, a bytecode VM has always been implemented the same way: a big-ass switch statement. You decode an instruction, and switch on its type to jump to the operation that needs to be performed. This is often a performance advantage against AST interpreters, because switching in practice is quite fast (particularly when implemented in C or C++ like most VMs are), and allows execution to happen linearly, without recursion.</p><p>This design, however, also has its fair share of shortcomings. Mike Pall, JIT-master extraordinaire and author of LuaJIT, gives a very insightful rundown of these issues on <a href="http://lua-users.org/lists/lua-l/2011-02/msg00742.html">this mailing list post from 2011</a>. Allow me to summarize for this blog: Besides the fact that the VM&#x27;s instructions need to be kept in-sync with the compiler, the actual performance of the main VM loop in a language with many instructions is not great in practice because compilers usually struggle when compiling massive functions, and these functions <em>are</em> massive. They spill registers all over the place on each branch of the switch, because it&#x27;s hard to tell which branches are hot and which ones are cold. With all the pushing and popping, the jump into the switch&#x27;s branch often looks more like a function call, so a lot of the performance benefits of the virtual machine dissipate.</p><p>Mike was discussing C compilers in that post, but it&#x27;s safe to assume that these problems are the same for a virtual machine implemented in Go. After a lot of testing, I can assure you that they are actually <em>much worse</em> because the Go compiler is not great at optimization. There’s always a trade-off between optimization and fast compile times, and the Go authors have historically opted for the latter.</p><p>One key issue for Go is that often the different branches of the switch statement are jumped to via <em>binary search</em> instead of a jump table. Switch jump table optimization was <a href="https://go-review.googlesource.com/c/go/+/357330">implemented surprisingly late on the compiler</a>, and in practice it is <em>very fiddly</em>, without any way to enforce it. You have to tweak the way the VM&#x27;s instructions are encoded carefully to ensure that you&#x27;re jumping in the VM&#x27;s main loop, and you have no way to reliably check whether your virtual machine’s dispatch code has been properly optimized besides reviewing the generated assembly yourself.</p><p>Clearly, switch-based VM loops are not the state of the art for writing efficient interpreters, neither in Go nor in any other programming language. So what <em>is</em> the state of the art then? Well, when it comes to Go it turns out that there&#x27;s nobody doing fast interpreters right now (at least nobody I can find). The people who are doing interesting work here, such as the <a href="https://github.com/tetratelabs/wazero"><code>wazero</code> WASM implementation</a> are focusing their performance efforts on JIT. So we’re going to have to innovate!</p><p>Outside of Go, the most interesting approach for interpreters implemented in C or C++ is <strong>continuation-style evaluation loops</strong>, as seen <a href="https://blog.reverberate.org/2021/04/21/musttail-efficient-interpreters.html">in this report from 2021 that implements this technique for parsing Protocol Buffers</a>. This involves implementing all the opcodes for the VM as freestanding functions that operate on the VM as an argument, with the return of the function being <em>a callback to the next step of the computation</em>. It does sound like something expensive and, huh, recursive, but the trick is that newer versions of LLVM allow us to mark functions as <em>forcefully</em> tail-called (see: https://en.wikipedia.org/wiki/Tail_call), so the resulting code is not recursively calling the VM loop but instead <em>jumping</em> between the operations and using the free-standing functions as an abstraction to control register placement and spillage. The most recent release of Python 3.14 actually <a href="https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-tail-call">ships an interpreter based on this design</a>, boasting up to 30% improvement when executing Python code.</p><p>Unfortunately, this is not something we can do in Go because as we discussed earlier, the Go compiler is allergic to optimization. It <em>can</em> sometimes emit tail calls, but it needs to be tickled in just the right way, and this implementation simply does not work in practice unless the tail-calls are guaranteed at compilation time. But what if we keep the same design with free-standing functions for each instruction and instead of tail-calling, we forcefully return control to the evaluation loop after each one? This could be implemented very easily by not emitting our compiled program as “byte code”, but instead emitting <strong>a slice of function pointers to each instruction</strong>. The design may be a bit counter-intuitive, but it has a lot of very interesting properties.</p><p>First, the VM becomes trivial! It&#x27;s just a few lines of code, and it doesn&#x27;t have to worry about optimizing any large switch statements. It&#x27;s just repeatedly calling functions one after the other! Here’s a simplified example, but if you check <a href="https://github.com/vitessio/vitess/blob/b05df12741adf1314839694e489e687e7ec6c6ea/go/vt/vtgate/evalengine/vm.go#L73-L98">the actual implementation in Vitess</a> you’ll see that a real virtual machine implementation is hardly more complicated than this.</p><div class="code-block" data-language="go"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*VirtualMachine</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> execute</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">p</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *Program</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">eval</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> error</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">	code</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> :=</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> p</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">code</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">	ip</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> :=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	for</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> ip</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> &#x3C;</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">code</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		ip</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> +=</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> code</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ip</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">](</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		if</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">err</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> !=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">			return</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">err</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">		}</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">	}</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	if</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ==</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		return</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">	}</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	return</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">stack</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">],</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>All we need to return when executing each instruction is the offset for the instruction pointer <code>ip</code>. Most functions return <code>1</code>, which causes the next instruction to be executed, but by returning negative or positive values, you can implement all control flow, including loops and conditionals.</p><p>Besides the greatly simplified virtual machine, the second advantage of this approach is that the compiler <em>also</em> becomes trivial, because there is no bytecode! Instead, the compiler emits the individual instructions directly by pushing &quot;callbacks&quot; into a slice. There are no instruction opcodes to keep track off, no encoding to perform and nothing to keep in sync with the VM. <strong>Developing the compiler means developing the VM simultaneously</strong>, which greatly improves iteration speed and prevents a whole class of bugs that happen often when developing virtual machines.</p><div class="code-block" data-language="go"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*compiler</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> emitPushNull</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">	c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">emit</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *VirtualMachine</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">stack</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> nil</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">++</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		return</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">	})</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>As you may notice, there’s a bit of a hiccup here when it comes to modeling the instructions for a non-trivial language: if there&#x27;s no instruction encoding, then we cannot have instructions with arguments.</p><p>This is a big problem in a language like C (traditionally used to implement most programming language interpreters), which is why this technique is never seen there. But it’s actually not a problem for us,  because the Go compiler actually supports <em>closures</em>! We can emit any instruction we want and the Go compiler will automatically capture its arguments inside the callback. We don&#x27;t have to think about how to encode our arguments in the bytecode, and in fact, our arguments can be as complex as they need to be: the resulting callback will contain a copy of them created by the Go compiler. It&#x27;s essentially a poor man&#x27;s JIT, aided by the compiler, and it works amazingly well in practice, both performance-wise and for ergonomics.</p><p>Check out this compiler method that generates an instruction to push a <code>TEXT</code> SQL object from the input rows into the stack:</p><div class="code-block" data-language="go"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*compiler</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> emitPushColumn_text</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">offset</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> collations</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">TypedCollation</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">	c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">emit</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *VirtualMachine</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">stack</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> newEvalText</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">row</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">offset</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">].</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">Raw</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(),</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> col</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">++</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		return</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">	})</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>Both the offset in the input <code>rows</code> array <em>and</em> the collation for the text are statically baked into the generated instruction!</p><h2 id="almost-statically-typed"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#almost-statically-typed">Almost statically typed</a></h2><p>With the fully static typing for SQL expressions (derived from the type information in the planner) we get to design an extremely efficient virtual machine where every single instruction is specialized for the type of the operands it executes on. This is both the optimal and the simplest design for a VM because we never have to do type switching during evaluation. But we’re dealing with SQL here (or, more accurately, the SQL dialect of MySQL), so not everything is rainbows and unicorns. Very often it’s quite the opposite.</p><p>Let’s consider this wildly complex SQL expression: <code>-inventory.price</code>. That is, the negation of each of the values in the <code>inventory.price</code> column of our query. We know (thanks to our semantic analysis, and the schema tracker) that the type of the <code>inventory.price</code> column is <code>BIGINT</code>. So what could be the type of <code>-inventory.price</code>? Naive readers without experience in the magical world of SQL may believe the resulting type is <code>BIGINT</code>, but that’s not the case in practice!</p><p>The vast majority of the time, the negation of a <code>BIGINT</code> yields indeed another <code>BIGINT</code> value. But when the actual value of the <code>BIGINT</code> is -9223372036854775808 (i.e. the smallest value that can be represented in 64 bits), negating it promotes the value into a <code>DECIMAL</code>, instead of silently truncating it, or returning an error. You can see how this can easily throw a wrench in our statically compiled instructions for our virtual machine. Suddenly the static type checking we’ve computed is no longer valid because the types of the expression no longer depend on the types of the inputs, but on the actual <em>values</em> of the inputs. In order to continue evaluating the result of this negation, we’d always have to type-check again at runtime, defeating the whole point of static typing to begin with.</p><p>To work around this issue, we’re <em>not</em> introducing more type switches at runtime. We’re using a classic trick which can be seen all the time in JIT compiled code and very rarely, if ever, in virtual machines: <strong>de-optimization</strong>. There’s a small list of expressions where corner cases (e.g. overflow) can result in dynamic typing at runtime. Whenever this happens, we simply bail out of executing in our virtual machine and fall back to executing on the old AST evaluator, which has always performed type switching at runtime. This is very similar to what JIT compilers do when they detect that the runtime type of a value no longer matches the generated code they’ve emitted; they fall back from the native code to the virtual machine. In our case, we’re one step behind, falling back from the virtual machine to the AST interpreter, but the performance implications are the same. This design allows us to keep our interpreter executing statically typed code without any type switches at runtime. Here&#x27;s an example of what integer negation looks like when compiled:</p><div class="code-block" data-language="go"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*compiler</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> emitNeg_i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">	c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">emit</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">func</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *VirtualMachine</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">		arg</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> :=</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">stack</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">env</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">sp</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">].(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*evalInt64</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		if</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> arg</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">i</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ==</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> math</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">MinInt64</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">			vm</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">err</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> errDeoptimize</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">		}</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> else</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> {</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">			arg</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">i</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> -</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">arg</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">i</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">		}</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">		return</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">	})</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span></span>
<span class="line"></span></code></pre></div></div><p>There is one significant drawback with this approach, however: the code for the AST interpreter can never be removed from Vitess. But this is, overall, not a bad thing. Just like most advanced language runtimes keep their virtual machine interpreter despite having a JIT compiler, having access to our classic AST interpreter gives us versatility. It can be used when we detect that an expression will be evaluated just <em>once</em> (e.g. when we use the evaluation engine to perform constant folding on a SQL expression). In those cases, the overhead of compiling and then executing on the VM trumps a single-pass evaluation on the AST. Lastly, when it comes to accuracy, being able to fuzz both the AST interpreter and the VM against each other has resulted in an invaluable tool for detecting bugs and corner cases.</p><h2 id="conclusion"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#conclusion">Conclusion</a></h2><p>This technique for virtual machine implementation is not fully novel (I’ve seen it used before for a rules-based authorization engine in the wild!), but as far as I can tell it has never been used in Go. Given the constraints of the language and the compiler, the technique yields spectacular results: the new SQL interpreter in Vitess is just <em>faster</em>. Faster to write, faster to maintain and faster to execute. The benchmarks speak for themselves:</p><h3 id="evalengine-performance-in-vitess-over-time"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#evalengine-performance-in-vitess-over-time">Evalengine performance in Vitess over time</a></h3><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/benchmark-results-D588Aj2C.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/benchmark-results-darkmode-BmvxW3Md.png?auto=compress%2Cformat"><img alt="Benchmark results" src="https://planetscale-images.imgix.net/assets/benchmark-results-D588Aj2C.png?auto=compress%2Cformat" width="2361" height="1295" loading="lazy"></picture></p><p>Here we have a performance comparison of 5 different queries (ranging from very complex to very simple) between three implementations:</p><ol><li><strong>old</strong>, which is the original AST-based dynamic implementation of the <code>evalengine</code>.</li><li><strong>ast</strong>, which is the result of adding static type checking to the virtual machine and using them to partially optimize the AST evaluator.</li><li><strong>vm</strong>, which is the callback-based virtual machine implementation as discussed in this post.</li></ol><h3 id="recent-results-compared-with-mysql"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#recent-results-compared-with-mysql">Recent results compared with MySQL</a></h3><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/benchmark-results-with-mysql-CvMaQMIM.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/benchmark-results-with-mysql-darkmode-CDCX_2gs.png?auto=compress%2Cformat"><img alt="Benchmark results With MySQL" src="https://planetscale-images.imgix.net/assets/benchmark-results-with-mysql-CvMaQMIM.png?auto=compress%2Cformat" width="2361" height="1295" loading="lazy"></picture></p><p>This is the current performance of our evaluation engine pitted against the native C++ implementation in MySQL. Note that measuring the time that MySQL spends in evaluation is very tricky; these are not the total reponse times for a query, but the result of manual instrumentation in the <code>mysqld</code> server to ensure a fair comparison.</p><details><summary>Raw benchmark data</summary><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>                                      │     ast      │                 vm                  │                  mysql                   │</span></span>
<span class="line"><span>                                      │    sec/op    │   sec/op     vs base                │    sec/op     vs base                    │</span></span>
<span class="line"><span>CompilerExpressions/complex_arith-32    162.75n ± 1%   50.77n ± 1%  -68.81% (p=0.000 n=10)   49.40n ±  5%  -69.64% (p=0.000 n=10+184)</span></span>
<span class="line"><span>CompilerExpressions/comparison_i64-32    30.30n ± 2%   16.95n ± 1%  -44.08% (p=0.000 n=10)   26.93n ± 22%  -11.12% (p=0.000 n=10+11)</span></span>
<span class="line"><span>CompilerExpressions/comparison_u64-32    30.57n ± 3%   17.49n ± 1%  -42.78% (p=0.000 n=10)   18.80n ±  9%  -38.53% (p=0.000 n=10+16)</span></span>
<span class="line"><span>CompilerExpressions/comparison_dec-32    70.75n ± 1%   52.58n ± 2%  -25.68% (p=0.000 n=10)   46.59n ±  5%  -34.14% (p=0.000 n=10+14)</span></span>
<span class="line"><span>CompilerExpressions/comparison_f-32      53.05n ± 1%   25.65n ± 1%  -51.64% (p=0.000 n=10)   27.75n ± 23%  -47.69% (p=0.000 n=10)</span></span>
<span class="line"><span>geomean                                  56.30n        28.94n       -48.60%                  31.76n        -43.58%</span></span>
<span class="line"><span></span></span>
<span class="line"><span>                                      │    ast     │                   vm                    │</span></span>
<span class="line"><span>                                      │    B/op    │    B/op     vs base                     │</span></span>
<span class="line"><span>CompilerExpressions/complex_arith-32    96.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_i64-32   16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_u64-32   16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_dec-32   64.00 ± 0%   40.00 ± 0%   -37.50% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_f-32     16.00 ± 0%    0.00 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span></span></span>
<span class="line"><span>                                      │    ast     │                   vm                    │</span></span>
<span class="line"><span>                                      │ allocs/op  │ allocs/op   vs base                     │</span></span>
<span class="line"><span>CompilerExpressions/complex_arith-32    9.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_i64-32   1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_u64-32   1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_dec-32   3.000 ± 0%   2.000 ± 0%   -33.33% (p=0.000 n=10)</span></span>
<span class="line"><span>CompilerExpressions/comparison_f-32     2.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)</span></span>
<span class="line"><span></span></span></code></pre></div></div></details><p>The results are stark: the pre-compiled SQL expressions when ran in the new VM are up to 20x times faster than the first implementation of SQL evaluation in Vitess, and for most cases, we&#x27;ve caught up with the performance of the C++ implementation in MySQL. One further detail which is not shown on the graphs, but can be seen on the raw benchmark data, is that the new virtual machine <strong>does not allocate memory</strong> to perform evaluation — a very nice side effect of the fully specialized instructions thanks to the static type checking.</p><p>Overall, we consider getting in the same performance ballpark as MySQL&#x27;s C++ evaluation engine as a huge engineering success, particularly when the resulting implementation is so easy to maintain. There will always be a performance gap between Go and C++, arising from the trade-off of quality vs compilation speed in the Go compiler, and from the semantics of the language itself, but as we show here, this gap is not insurmountable. With expertise and careful design, it is possible to reap the many benefits of developing and deploying Go services without paying the performance penalty inherent in the language. In this specific case, we got there by having the capacity to perform semantic analysis and statically typing SQL expressions (something which MySQL does not do), and by choosing an efficient virtual machine design that uses the strengths of Go instead of fighting its limitations.</p><h3 id="addendum-so-why-not-jit"><a href="https://planetscale.com/blog/faster-interpreters-in-go-catching-up-with-cpp#addendum-so-why-not-jit">Addendum: So why not JIT?</a></h3><p>Inquiring minds may be wondering: what&#x27;s next? Are we doing JIT compilation next? The answer is no. Although this design for a compiler and VM looks like an exceptional starting point for implementing a full JIT compiler <em>in theory</em>, in practice the trade-off between optimization and complexity doesn&#x27;t make sense. JIT compilers are important for programming languages where their bytecode operations can be optimized into a very low level of abstraction (e.g. where an &quot;add&quot; operator only has to perform a native x64 <code>ADD</code>). In these cases, the overhead of dispatching instructions becomes so dominant that replacing the VM&#x27;s loop with a block of JITted code makes a significant performance difference. However, for SQL expressions, and even after our specialization pass, most of the operations remain extremely high level (things like &quot;match this JSON object with a path&quot; or &quot;add two fixed-width decimals together&quot;). The overhead of instruction dispatch, as measured in our benchmarks, is less than 20% (and can possibly be optimized further in the VM&#x27;s loop). 20% is not the number you&#x27;re targetting before you start messing around with raw assembly for a JIT. So at this point my intuition is that JIT compilation would be a needlessly complex dead optimization.</p>]]></content>
        <summary><![CDATA[A novel technique for implementing dynamic language interpreters in Go, applied to the Vitess SQL evaluation engine]]></summary>
      </entry>
    
      <entry>
        <title>Introducing sharding on PlanetScale with workflows</title>
        <link href="https://planetscale.com/blog/introducing-workflows-on-planetscale" />
        <id>https://planetscale.com/blog/introducing-workflows-on-planetscale</id>
        <published>2024-11-07T10:00:00.000Z</published>
        <updated>2024-11-07T10:00:00.000Z</updated>
        
        <author>
          <name>Ben Dicken</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>We just released our new workflows functionality, which provides you with recipes that run a series of predefined steps to perform actions on your database. Our first workflow enables you to horizontally scale your databases by moving tables to a sharded keyspace, all from within the PlanetScale dashboard.</p><p>If you&#x27;re familiar with Vitess, this first workflow is similar to <a href="https://vitess.io/docs/reference/vreplication/movetables/"><code>MoveTables</code></a>, a critical component to managing your data distribution in Vitess. We are excited to now offer this functionality directly in PlanetScale.</p><p>Workflows functionality is available on all <a href="https://planetscale.com/pricing">PlanetScale plans</a>.</p><h2 id="why-did-we-build-this"><a href="https://planetscale.com/blog/introducing-workflows-on-planetscale#why-did-we-build-this">Why did we build this?</a></h2><p>At PlanetScale, our goal is to be the most reliable and scalable platform for running relational databases. One of the keys to achieving this is sharding — a <a href="https://developer.squareup.com/blog/abstracting-sharding-with-vitess-and-distributed-deadlocks/">proven architecture</a> used by some of the web&#x27;s largest properties to <a href="https://www.etsy.com/codeascraft/scaling-etsy-payments-with-vitess-part-1--the-data-model">scale their databases</a> to <a href="https://slack.engineering/scaling-datastores-at-slack-with-vitess/">several millions of queries per second</a>.</p><p>Up until recently, much of the functionality for creating and configuring sharded databases was only accessible via our Enterprise plan.</p><p>The recent release of the <a href="https://planetscale.com/docs/vitess/cluster-configuration">Cluster configuration</a> functionality allowed users to create their own sharded keyspaces as well as configure custom VSchema and routing rules. With the addition of <strong>workflows</strong>, you can also easily migrate existing data to sharded keyspaces and smoothly switch production traffic between them with no downtime.</p><p>Over the past few decades, many companies have faced significant challenges scaling their databases, especially at the point where a single server cannot handle all traffic. Being able to easily and safely transition to a sharded environment shifts this phase of a company&#x27;s existence from an extreme pain point to a smooth, well-tuned process. We want to empower our users with the tools they need for scaling their database to meet any demands.</p><p>Let&#x27;s take a look at how this works.</p><h2 id="how-to-shard-your-tables-with-planetscale"><a href="https://planetscale.com/blog/introducing-workflows-on-planetscale#how-to-shard-your-tables-with-planetscale">How to shard your tables with PlanetScale</a></h2><p>You can refer to the <a href="https://planetscale.com/docs/vitess/sharding/sharding-quickstart">Sharding quickstart</a> for a full end-to-end tutorial. If you prefer video walkthroughs, check out our latest video:</p><iframe allow="autoplay; fullscreen; picture-in-picture" src="https://www.youtube-nocookie.com/embed/m5eW34nfNtQ?rel=0&color=white" title="Tutorial: Horizontal sharding with PlanetScale"></iframe><p>Before starting a workflow, you&#x27;ll want to ensure you have a sharded keyspace set up in addition to your unsharded source keyspace. You can view, modify, and create keyspaces from our recently-released <a href="https://planetscale.com/docs/vitess/cluster-configuration">Cluster configuration</a> interface. Navigate to “<strong>Clusters</strong>” and create keyspaces as needed. In this example database, we already have two such keyspaces: <code>gymtracker</code> and <code>gymtracker-sharded</code>:</p><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-cluster-configuration-Cgjxk2aQ.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-cluster-configuration-darkmode-CqoLPotE.png?auto=compress%2Cformat"><img alt="The cluster configuration UI with two keyspaces" src="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-cluster-configuration-Cgjxk2aQ.png?auto=compress%2Cformat" width="4310" height="2554" loading="lazy"></picture></p><p>You can use a workflow to move one or more tables from the unsharded keyspace into a sharded one. To start a workflow, navigate to the “<strong>Workflow</strong>” UI:</p><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflows-CjrKJkJt.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflows-darkmode-BHtR-dDj.png?auto=compress%2Cformat"><img alt="Workflows section of a PlanetScale database" src="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflows-CjrKJkJt.png?auto=compress%2Cformat" width="4310" height="2554" loading="lazy"></picture></p><p>Click “<strong>New workflow</strong>” to start a new workflow. In this menu, you&#x27;ll be asked to give the workflow a name, select the source and target keyspaces, and select the table(s) you want to move.</p><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-setup-COSC2_20.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-setup-darkmode-CllcJxdn.png?auto=compress%2Cformat"><img alt="Configuring a workflow" src="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-setup-COSC2_20.png?auto=compress%2Cformat" width="3670" height="2312" loading="lazy"></picture></p><p>After setting everything up, click “<strong>Validate</strong>”. PlanetScale will not allow you to start the workflow until all validation checks pass.</p><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-validation-success-BKpaDWs2.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-validation-success-darkmode-CpvU8NUb.png?auto=compress%2Cformat"><img alt="Configuring a workflow" src="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-validation-success-BKpaDWs2.png?auto=compress%2Cformat" width="3670" height="2312" loading="lazy"></picture></p><p>After clicking “<strong>Create workflow</strong>”, you enter into the &quot;copying phase&quot; and can monitor the progress of the workflow. Initially, PlanetScale will migrate your data from the source keyspace to the target. After the initial bulk migration completes, it will continue to replicate any new rows that come in to the target. When you&#x27;re ready to proceed, we recommend you first <strong>Verify data</strong> (to ensure everything migrated correctly), and then you can <strong>Switch traffic</strong>.</p><p><picture class="block"><source media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-running-skzqfZxJ.png?auto=compress%2Cformat"><source media="(prefers-color-scheme: dark)" srcSet="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-running-darkmode-DAU1G8_f.png?auto=compress%2Cformat"><img alt="Configuring a workflow" src="https://planetscale-images.imgix.net/assets/introducing-workflows-on-planetscale-workflow-running-skzqfZxJ.png?auto=compress%2Cformat" width="3670" height="1830" loading="lazy"></picture></p><p>After switching traffic, the new sharded table is configured to handle all traffic for the migrated tables! We also give you an option to switch traffic back to the unsharded database, providing an escape hatch in case any unexpected problems arise from the sharded configuration.</p><p>With just a few clicks, you can create a sharded keyspace with however many shard you&#x27;d like, move existing tables to that keyspace, and switch production traffic to be served from the sharded keyspace.</p><h2 id="vitess-workflows"><a href="https://planetscale.com/blog/introducing-workflows-on-planetscale#vitess-workflows">Vitess workflows</a></h2><p>Every PlanetScale database is powered by Vitess — which supports a number of workflows to facilitate managing your database cluster. These include:</p><ul><li><a href="https://vitess.io/docs/reference/vreplication/movetables/"><strong><code>MoveTables</code></strong></a> — Allows you to move tables between keyspaces (between logical databases within your cluster)</li><li><a href="https://vitess.io/docs/reference/vreplication/reshard/"><strong><code>Reshard</code></strong></a> — Facilitates modifying the way that your data is sharded. Allows you to spread your data across more shards or less shards, depending on demand.</li><li><a href="https://vitess.io/docs/reference/vreplication/materialize/"><strong><code>Materialize</code></strong></a> — Allows you to create copies, aggregations, or views of the tables in your Vitess cluster.</li><li><a href="https://vitess.io/docs/user-guides/configuration-advanced/createlookupvindex/"><strong><code>LookupVindex</code></strong></a> — Helps with the creation and population of Lookup Vindexes (lookup index tables to help queries execute faster).</li><li><a href="https://vitess.io/docs/reference/vreplication/migrate/"><strong><code>Migrate</code></strong></a> — Allows you to move tables between distinct Vitess clusters.</li></ul><p>For this first release, we focused on supporting <code>MoveTables</code>, specifically when migrating a table from an unsharded keyspace to a sharded one. We believe that this is one of the most important workflows for our users, as it unlocks the ability to horizontally scale existing unsharded databases with minimal friction.</p><p>We intend to support more types of workflows in the future. For example, the ability to reshard is also important to allow users to self-manage their growing database systems on PlanetScale. We also plan to integrate the <code>Migration</code> workflow into PlanetScale to help with migrations from outside sources.</p><h2 id="workflows-resources"><a href="https://planetscale.com/blog/introducing-workflows-on-planetscale#workflows-resources">Workflows resources</a></h2><p>We have a number of resources to help you get up and running with PlanetScale workflows.</p><p>We also have detailed documentation that walks you through important concepts and instructions for running your own unsharded to sharded workflows.</p><ul><li><a href="https://planetscale.com/docs/vitess/sharding/sharding-quickstart">Sharding quickstart</a></li><li><a href="https://planetscale.com/docs/vitess/scaling/workflows">Workflows</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/avoiding-cross-shard-queries">Avoiding cross-shard queries</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/vindexes">Vindexes</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/sharding-workflow-state-reference">Sharding workflow state reference</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/pre-sharding-checklist">Pre-sharding checklist</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/targeting-correct-keyspace">Targeting the correct keyspace</a></li><li><a href="https://planetscale.com/docs/vitess/sharding/keyspaces">What is a keyspace?</a></li></ul><p>If you have questions or feedback about workflows, <a href="https://planetscale.com/contact">contact us</a>. We&#x27;d love to chat.</p>]]></content>
        <summary><![CDATA[Run Vitess workflows right from within PlanetScale. Migrate data from unsharded to sharded keyspaces, manage traffic cutover, and easily revert when problems arise.]]></summary>
      </entry>
    
      <entry>
        <title>Announcing Vitess 21</title>
        <link href="https://planetscale.com/blog/announcing-vitess-21" />
        <id>https://planetscale.com/blog/announcing-vitess-21</id>
        <published>2024-10-29T09:01:00.000Z</published>
        <updated>2024-10-29T09:01:00.000Z</updated>
        
        <author>
          <name>Vitess Engineering Team</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>We&#x27;re delighted to announce the release of <a href="https://github.com/vitessio/vitess/releases/tag/v21.0.0">Vitess 21</a> along with <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.14.0">version 2.14.0</a> of the Vitess Kubernetes Operator.</p><p>Version 21 focuses on enhancing query compatibility, improving cluster management, and expanding VReplication capabilities, with experimental support for atomic distributed transactions and recursive CTEs. Key features include reference table materialization, multi-metric throttler support, and enhanced online DDL functionality. Backup and restore processes benefit from a new <strong>mysqlshell</strong> engine, while <strong>vexplain</strong> now offers detailed execution traces and schema analysis. The Vitess Kubernetes Operator introduces horizontal autoscaling for VTGate pods and Kubernetes 1.31 support, improving overall scalability and deployment flexibility.</p><iframe allow="autoplay; fullscreen; picture-in-picture" src="https://www.youtube-nocookie.com/embed/rRONgRoJVbo?rel=0&color=white" title="What&#x27;s new in Vitess 21?"></iframe><h2 id="whats-new-in-vitess-21"><a href="https://planetscale.com/blog/announcing-vitess-21#whats-new-in-vitess-21">What&#x27;s New in Vitess 21</a></h2><ul><li><strong>Query Compatibility</strong>: Experimental support for atomic distributed transactions and recursive CTEs</li><li><strong>VReplication</strong>: Reference table materialization, dynamic workflow configuration</li><li><strong>Cluster Management and VTOrc</strong>: More metrics in VTOrc to track errant GTIDs</li><li><strong>Throttler</strong>: Multi-metric support</li><li><strong>Online DDL</strong>: Various improvements</li><li><strong>Backup &amp; Restore</strong>: Experimental mysqlshell engine</li><li><strong>Vitess Operator</strong>: VTGate scaling, image customization, Kubernetes 1.31 support</li><li><strong>VTAdmin</strong>: VReplication workflow creation and management, distributed transaction management</li><li><strong>VExplain</strong>: <strong>vexplain trace</strong> for detailed query execution insights, <strong>vexplain keys</strong> for analyzing <a href="https://planetscale.com/sharding">sharding</a> key usage and optimizing query performance</li></ul><h2 id="query-compatibility"><a href="https://planetscale.com/blog/announcing-vitess-21#query-compatibility">Query Compatibility</a></h2><h4 id="atomic-distributed-transactions"><a href="https://planetscale.com/blog/announcing-vitess-21#atomic-distributed-transactions">Atomic Distributed Transactions</a></h4><p>We’re reintroducing atomic distributed transactions with a revamped, more resilient design. This feature now offers deeper integration with core Vitess components and workflows, such as Online DDL and VReplication (including operations like <strong>MoveTables</strong> and <strong>Reshard</strong>). We have also greatly simplified the configuration required to use atomic distributed transactions. This feature is currently in an experimental state, and we encourage you to explore it and share your feedback to help us improve it further.</p><h4 id="recursive-common-table-expressions-ctes"><a href="https://planetscale.com/blog/announcing-vitess-21#recursive-common-table-expressions-ctes">Recursive Common Table Expressions (CTEs)</a></h4><p>Vitess 21 introduces experimental support for recursive CTEs, allowing more complex hierarchical queries and graph traversals. This feature enhances query flexibility, particularly for managing parent-child relationships like organizational structures or tree-like data. As this functionality is still experimental, we encourage you to explore it and provide feedback to help us improve it further.</p><h2 id="cluster-management-and-vtorc"><a href="https://planetscale.com/blog/announcing-vitess-21#cluster-management-and-vtorc">Cluster Management and VTOrc</a></h2><p>We have added a new metric in VTOrc that shows the count of errant GTIDs in all the tablets for better visibility and alerting. This will help operators track and manage errant GTIDs across the cluster.</p><h2 id="vreplication"><a href="https://planetscale.com/blog/announcing-vitess-21#vreplication">VReplication</a></h2><h4 id="reference-table-materialization"><a href="https://planetscale.com/blog/announcing-vitess-21#reference-table-materialization">Reference Table Materialization</a></h4><p>Vitess provides <strong>Reference Tables</strong> as a mechanism to replicate commonly used lookup tables from an unsharded keyspace into all shards in a sharded keyspace. Such tables might be used to hold lists of countries, states, zip codes, etc., which are commonly used in joins with other tables in the sharded keyspace. Using reference tables allows Vitess to execute joins in parallel on each shard, thus avoiding cross-shard joins. Previously, we recommended creating Materialize workflows for reference tables but did not provide an easy way to do so. In v21, we have added explicit support to the <strong>Materialize</strong> command to replicate a set of reference tables into a sharded keyspace.</p><h4 id="dynamic-workflow-configuration"><a href="https://planetscale.com/blog/announcing-vitess-21#dynamic-workflow-configuration">Dynamic Workflow Configuration</a></h4><p>Previously, many configuration options for VReplication workflows were controlled by VTTablet flags. This meant that any change required restarting all VTTablets. We now allow these to be overridden while creating a workflow or updated dynamically once the workflow is in progress.</p><h2 id="throttler-multi-metric-support"><a href="https://planetscale.com/blog/announcing-vitess-21#throttler-multi-metric-support">Throttler: Multi-metric Support</a></h2><p>The tablet throttler has been redesigned with <a href="https://vitess.io/docs/21.0/reference/features/tablet-throttler/">new multi-metric support</a>. With this, the throttler now handles more than just replication lag or custom queries, but instead can work with multiple metrics at the same time, and check for different metrics for different clients or for different workflows. This gives users better control over the throttler, allowing them to fine-tune its behavior based on their specific production requirements.</p><p>Several new metrics have been introduced in v21, with plans to expand the list of available metrics in later versions.</p><p>The multi-metric throttler in v21 is backward compatible with the v20 throttler. It is possible to have a v20 primary tablet collecting throttler data from a v21 replica tablet, and vice versa. This backward compatibility will be removed in v22, where all tablet throttlers will be expected to communicate multi-metric data.</p><p>Other key throttler changes:</p><ul><li>With the above, the sub-flags <code>--check-as-check-self</code> and <code>--check-as-check-shard</code> to the <code>UpdateThrottlerConfig</code> command are deprecated and slated to be removed in a future version. Similarly, <code>SHOW VITESS_THROTTLER STATUS</code> and <code>SHOW VITESS_THROTTLED_APPS</code> queries, and all <code>/throttler/</code> API access points (with the exception of <code>/throttler/check</code>) are deprecated and slated to be removed in v22.</li><li>When enabled, the throttler ensures it leases heartbeat updates, even if heartbeat configuration is otherwise unset. In other words, the throttler overrides the configuration when it requires heartbeat information.</li><li>Throttler check response now includes a human-readable summary detailing exactly why a request was rejected (if rejected).</li></ul><h2 id="online-ddl"><a href="https://planetscale.com/blog/announcing-vitess-21#online-ddl">Online DDL</a></h2><p>Several bug fixes and improvements, including:</p><ul><li>Added support for the <code>ALTER VITESS_MIGRATION CLEANUP ALL</code> command.</li><li>More <code>INSTANT</code> DDL scenario analysis, going further beyond the documented limitations.</li><li>In schema changes where columns change charsets, Online DDL now converts the text programmatically rather than using a <code>CONVERT(... USING utf8mb4)</code> clause, thereby improving performance when such columns are part of the Primary Key or the iteration key.</li><li>Internally, more of the schema and diff analysis is now delegated to <code>schemadiff</code> library, which means more programmatic power and better testability.</li><li>Fixes for self-referencing foreign key tables (only relevant when using the PlanetScale MySQL build).</li></ul><h2 id="backup-restore"><a href="https://planetscale.com/blog/announcing-vitess-21#backup-restore">Backup &amp; Restore</a></h2><p>Introducing an experimental <a href="https://vitess.io/docs/21.0/user-guides/operating-vitess/backup-and-restore/creating-a-backup/#using-mysqlshell-experimental">mysqlshell engine</a>. With this engine, it is possible to run logical backups and restores. The mysqlshell engine can be used to create full backups, incremental backups, and point-in-time recoveries. It is also available to use with the Vitess Kubernetes Operator.</p><p>The <strong>mysqlshell</strong> engine work was contributed by the Slack engineering team.</p><h2 id="vexplain-enhancements"><a href="https://planetscale.com/blog/announcing-vitess-21#vexplain-enhancements">VExplain Enhancements</a></h2><h4 id="vexplain-trace"><a href="https://planetscale.com/blog/announcing-vitess-21#vexplain-trace">VExplain Trace</a></h4><p>The new <strong>vexplain trace</strong> command provides deeper insights into query execution paths by capturing detailed execution traces. This helps developers and DBAs analyze performance bottlenecks, review query plans, and gain visibility into how Vitess processes queries across distributed nodes. The trace output is delivered as a JSON object, making it easy to integrate with external analysis tools.</p><h4 id="vexplain-keys"><a href="https://planetscale.com/blog/announcing-vitess-21#vexplain-keys">VExplain Keys</a></h4><p>The new <strong>vexplain keys</strong> feature helps you analyze how your queries interact with your schema, showing which columns are used in filters, groupings, and joins across tables. This tool is especially useful for identifying candidate columns for indexing, <a href="https://planetscale.com/sharding">sharding</a>, or optimization, whether you’re using Vitess or a standalone MySQL setup. By providing a clear view of column usage, <strong>vexplain keys</strong> makes it easier to fine-tune your database for better performance, regardless of your backend infrastructure.</p><h2 id="vitess-kubernetes-operator"><a href="https://planetscale.com/blog/announcing-vitess-21#vitess-kubernetes-operator">Vitess Kubernetes Operator</a></h2><p>Vitess v21.0.0 comes with a companion release of the <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.14.0">vitess-operator v2.14.0</a>. In v2.14, we have added the ability to horizontally scale the VTGate deployment using an HPA. We have upgraded the supported version of Kubernetes to the latest version (v1.31). We have added a feature that allows users to select Docker images on a per-keyspace basis instead of a single setting for the entire cluster.</p><h2 id="vtadmin"><a href="https://planetscale.com/blog/announcing-vitess-21#vtadmin">VTAdmin</a></h2><p>New VTAdmin pages have been added for creating, monitoring, and managing VReplication Workflows. We have also added a dashboard to view and conclude distributed transactions.</p><h2 id="vitess-and-the-community"><a href="https://planetscale.com/blog/announcing-vitess-21#vitess-and-the-community">Vitess and the Community</a></h2><p>As an open-source project, Vitess thrives on the contributions, insights, and feedback from the community. Your experiences and input are invaluable in shaping the future of Vitess. We encourage you to share your stories and ask questions on <a href="https://github.com/vitessio/vitess">GitHub</a> or in our <a href="http://vitess.io/slack">Slack community</a>.</p><h2 id="getting-started"><a href="https://planetscale.com/blog/announcing-vitess-21#getting-started">Getting Started</a></h2><p>For a seamless transition to <a href="https://github.com/vitessio/vitess/releases/tag/v21.0.0">Vitess 21</a>, we highly recommend reviewing the <a href="https://github.com/vitessio/vitess/tree/main/changelog/21.0">detailed release notes</a>. Additionally, you can explore our documentation for guides, best practices, and tips to make the most of Vitess 21. Whether you&#x27;re upgrading from a previous version or running Vitess for the first time, our resources are designed to support you every step of the way.</p><p>Thank you for your support and contributions to the Vitess project!</p>]]></content>
        <summary><![CDATA[Vitess 21 is now generally available.]]></summary>
      </entry>
    
      <entry>
        <title>Zero downtime migrations at petabyte scale</title>
        <link href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale" />
        <id>https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale</id>
        <published>2024-08-13T00:00:00.000Z</published>
        <updated>2024-08-13T00:00:00.000Z</updated>
        
        <author>
          <name>Matt Lord</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Scaling challenges, frequent maintenance tasks, constant fear of when it&#x27;s going to all fall over, and quickly rising costs — these are all common issues that teams will put up with just to avoid the task of migrating a database that has outgrown its home.</p><p>And with good reason. There is a lot of risk in moving from one database server or system to another: extended downtime, data loss, version incompatibilities, and errors no one could have even predicted.</p><p>But what if I told you it doesn&#x27;t <em>have</em> to be this difficult? At PlanetScale, we regularly migrate databases in the terabyte and even petabyte range to our platform — all without any downtime. Our process allows us to do heavy testing with production traffic prior to cutover and even reverse the migration back to the external database if needed.</p><p>To give you a sense of the scale involved, you can see the size of one of the larger data sets — after migrating <em>only a subset of the databases</em> to date — that originated from migrations here:</p><p><picture class="block"><img alt="Managed Migrations" src="https://planetscale.com/assets-2024/blog/content/zero-downtime-migrations-at-petabyte-scale/large-managed-imported-data.png" loading="lazy"></picture></p><p>In this article, we&#x27;ll take a look under the hood to show you how our migration process works and what allows us to handle huge data migrations with no downtime.</p><h2 id="data-migrations"><a href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale#data-migrations">Data migrations</a></h2><p>Before we get into the specifics, let&#x27;s cover the high level problem and lay out the relevant terminology so that we can better understand and appreciate the details.</p><p>A data migration is the process of moving data from one database server or system to another — with the goal of transferring the responsibility for housing and serving this data from the old system to the new at a future point in time, often called the point of cutover. Each database vendor and system will have some paved roads for this process. For example MySQL has <a href="https://dev.mysql.com/doc/refman/en/mysqldump.html"><code>mysqldump</code></a> and <a href="https://dev.mysql.com/doc/refman/en/mysqlimport.html"><code>mysqlimport</code></a> while PostgreSQL has <a href="https://www.postgresql.org/docs/current/app-pgdump.html"><code>pg_dump</code></a> and <a href="https://www.postgresql.org/docs/current/app-pgrestore.html"><code>pg_restore</code></a> for logical data copies. And both products have methods for setting up replication from the old to the new system if the new system is of the same type as the old (e.g. MySQL to MySQL). Common migration examples being upgrading across several major versions of the same database system (e.g. PostgreSQL 13 to 16), or moving from one database system to another (e.g. PostgreSQL to MySQL or vice versa).</p><h3 id="how-data-migrations-work"><a href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale#how-data-migrations-work">How data migrations work</a></h3><p>At a high level, this is the general flow for data migrations:</p><ol><li><p>Take a snapshot of the data in the old system.</p><ul><li>This can be done with a logical backup or a physical (volume/filesystem/block device) snapshot, with a logical backup being decoupled from the storage and offering the ability to migrate across different databases and storage types.</li></ul></li><li><p>Restore the snapshot to the new system.</p><ul><li>Either replaying the SQL statements in the logical backup or restoring the physical snapshot to the new system</li></ul></li><li><p>Verify the new system.</p><ul><li>Perform at least some basic checks to ensure the new system is able to read and serve the data we expect to see.</li></ul></li><li><p>Cutover application traffic to the new system.</p><ul><li>Have all applicable applications start sending read and write traffic to the new system.</li></ul></li><li><p>Decommission the old system.</p><ul><li>Once you&#x27;re satisfied with the new system behavior, you can completely shut down and optionally archive the old system.</li></ul></li></ol><p>The simplest of processes would incur extensive downtime — and at least write unavailability — which covers all of these steps so that the system is read-only from the time that step 1 starts to the time that step 4 is complete. That can take hours, days, or even weeks. So this is not always acceptable for production systems, especially those of considerable size and which are expected to be available 24/7. This also offers no way to revert the cutover step if for some reason things go poorly afterward — e.g. some of your common queries may now throw errors or perform much worse than before.</p><p>An ideal process would allow us to perform all of these steps without any downtime and offer the ability to revert the cutover step if necessary — i.e. to support cutting over traffic in both directions until we&#x27;re satisfied with the end state (and only at some point after that would we decommission the old system). The process which PlanetScale uses has been very intentionally designed to meet these criteria! Now that we&#x27;ve laid the groundwork, we can start to dive into the specifics.</p><h2 id="data-migrations-at-planetscale"><a href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale#data-migrations-at-planetscale">Data migrations at PlanetScale</a></h2><p>The process of migrating data at PlanetScale is done this way at a higher level (you can see user guides in the <a href="https://planetscale.com/docs/vitess/imports/database-imports">PlanetScale documentation</a>, for example, the <a href="https://planetscale.com/docs/vitess/imports/aws-rds-migration-guide">AWS RDS migration guide</a>):</p><ol><li><p>Take a consistent non-locking snapshot of the data which includes the metadata needed to replicate changes that happen after our snapshot was taken. We will be replicating changes throughout the migration process.</p><ul><li>This is critical as it means that the availability of your database and the applications using it are not affected by the migration process — thus no downtime.</li></ul></li><li><p>Once the data has been copied to the new system, we continue to replicate changes from the old system to the new system so that the new system is ready for cutover at any point.</p><ul><li>We replicate changes from the old database instance to the PlanetScale database in order to keep our consistent snapshot from step 1 up to date and in sync with the original system — thus no downtime.</li></ul></li><li><p>Run a <a href="https://vitess.io/docs/reference/vreplication/vdiff/"><code>VDiff</code></a> to verify that all of the data has been correctly copied and that the new system is in sync with the old, identifying any discrepancies that need to be addressed before the cutover.</p><ul><li>This is a critical step that ensures nothing went wrong during the migration process and all of the data was copied correctly and the systems are in sync — providing the confidence to move on with the eventual cutover.</li></ul></li><li><p>At some point between steps 1 and 4, the <a href="https://planetscale.com/docs/vitess/tutorials/connect-any-application">application starts sending traffic to PlanetScale</a> rather than directly to the old system. PlanetScale then continues to route traffic back to the old system until we&#x27;re ready to cutover.</p><ul><li><p>This can be done any time before we perform the cutover in step 6. This allows you to test how your application behaves when PlanetScale is its target, and most importantly, it allows the eventual cutover to be fast and transparent as PlanetScale manages the routing — thus no downtime.</p><div class="mb-3 border p-3 border-orange-500"><p><span class="bg-orange-500 px-sm text-white dark:text-black">Warning</span></p><p>You will typically want to take this step as you get closer to the potential cutover point. That is because at this pre-cutover stage your query route is Application-&gt;PlanetScale-&gt;OldDB with the query response following that same path in reverse. This will incur a significant performance cost — all the more so if the application was previously connecting to the original database using a fast local connection (unix domain socket, loopback device, etc). It&#x27;s for this same reason that you should not attempt to do application side performance comparisions using PlanetScale, comparing it to the usage of your original database, until you&#x27;ve done the cutover. This is because in this temporary pre-cutover stage the bulk of the total execution time will be spent in network round trips for well optimized queries.</p><p>Please note, however, that when using <a href="https://planetscale.com/docs/vitess/managed">PlanetScale Managed</a> — as most customers at this scale would be doing — the additional network cost would be minimal as the application and PlanetScale database would typically be in the same cloud vendor account and sharing the same physical locations (regions and availability zones) and physical networks in those locations.</p></div></li></ul></li><li><p>You can remain in this state as long as necessary for you to prepare for the application cutover and perform additional testing of the application and database system.</p></li><li><p>Transparently cutover application traffic to the new system so that application traffic is going to PlanetScale.</p><ul><li>During this process we ensure that there is no data loss or drift.</li><li>Incoming queries are paused/buffered while performing the system changes for the traffic switch.</li><li>Once the cutover is complete — which would typically take less than 1 second — the paused/buffered queries are executed and the system is back to normal operation.</li><li>Reverse replication is put in place so that if for any reason we need to revert the cutover, we can do so without data loss or downtime (this can be done back and forth as many times as necessary).</li></ul><p>The query buffering done here is the last part that allows the entire migration to be done without any downtime.</p></li><li><p>Whenever you are confident in the system and no longer require the need to cut traffic back over to your old system, you can finish or complete the migration.</p><ul><li>This is the final step in the migration process and is the point at which you can decommission your old system whenever you like.</li></ul></li></ol><p>At no point during this process is the old system down or are application users aware that anything out of the ordinary is occurring. The only thing one would typically notice is the slight spike in query latency at the point of cutover where we briefly pause the incoming queries so that we can route them to the correct side of the migration (old vs new) once the cutover work is done.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>When you&#x27;ve reached a certain scale — <a href="https://vitess.io/docs/overview/scalability-philosophy/#small-instances">where a database is larger than 250GiB being the recommendation in Vitess</a> — <a href="https://planetscale.com/blog/what-is-database-sharding-and-how-does-it-work">horizontally sharding your database</a> is the best way to continue scaling without incurring exponentially more expensive hardware related costs and affecting the performance of queries and various operations (e.g. schema changes and backups). This is a key feature of Vitess and PlanetScale and it is typical to shard a database <em>as part of the data migration</em>. So e.g. you can have an unsharded MySQL database that we then split into N shards as part of the data migration into PlanetScale. In fact, being able to do this is a common reason for migrating to PlanetScale in the first place.</p></div><h2 id="a-deep-dive-into-the-technical-details"><a href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale#a-deep-dive-into-the-technical-details">A deep dive into the technical details</a></h2><p>PlanetScale is a database-as-a-service offering a &quot;modern MySQL&quot; developer experience, built around <a href="https://vitess.io/">Vitess</a>. Vitess offers a suite of related tools and primitives related to data migrations called <a href="https://vitess.io/docs/reference/vreplication/vreplication/">VReplication</a>.</p><p>The PlanetScale feature built around that set of Vitess primitives for imports is called (not surprisingly) <a href="https://planetscale.com/docs/vitess/imports/database-imports">Database Imports</a>. This feature provides the web and CLI based user interfaces around VReplication&#x27;s <a href="https://vitess.io/docs/reference/vreplication/movetables/"><code>MoveTables</code> workflow</a> for the consistent data copy (streaming rows) and the replication (streaming binary log events).</p><p>If we walk through the process of a data migration at PlanetScale again at a lower level (the steps are somewhat simplified here with certain details and optional behaviors left out, but it covers the main points):</p><ol><li><p>Copy the existing data by taking a consistent non-locking snapshot of the data which includes the metadata needed to replicate changes that happen after our snapshot was taken — which we will be doing throughout the migration process.</p><p>a. Each of the N <a href="https://vitess.io/docs/concepts/shard/">shards</a> in the PlanetScale database cluster has a <a href="https://vitess.io/docs/concepts/tablet/#tablet-types"><code>PRIMARY</code> tablet</a>. Those connect to the <a href="https://vitess.io/docs/user-guides/configuration-advanced/unmanaged-tablet/">unmanaged tablet</a> that is placed in front of the external MySQL instance (the old system) and initiates a stream where we issue a <a href="https://dev.mysql.com/doc/refman/en/lock-tables.html"><code>LOCK TABLE &lt;tbl&gt; READ</code></a> query to make it read-only just long enough to issue <a href="https://dev.mysql.com/doc/refman/en/commit.html"><code>START TRANSACTION WITH CONSISTENT SNAPSHOT</code></a> and read the <a href="https://dev.mysql.com/doc/refman/en/replication-options-gtids.html#sysvar_gtid_executed"><code>@@global.GTID_EXECUTED</code></a> value, then releasing the lock. At this point we have a consistent snapshot of the table data and the <a href="https://dev.mysql.com/doc/refman/en/replication-gtids.html"><code>GTID</code> set or &quot;position&quot;</a> metadata to go along with it so that we can replicate changes to the table that have occurred since our snapshot was taken.</p><p>b. We then start reading all of the rows from our snapshot — at a logical point in time — ordering the results by the <code>PRIMARY KEY</code> (PK) columns in the table (if there are none then we will use the best PK equivalent, meaning non-null unique key) so that we can read from the <a href="https://dev.mysql.com/doc/refman/en/innodb-index-types.html">clustered index</a> immediately as we are then reading the records in order and do not need to formulate the entire result set and order it with a <a href="https://dev.mysql.com/doc/refman/en/order-by-optimization.html"><code>filesort</code></a> before we can start streaming rows. The source <a href="https://vitess.io/docs/concepts/tablet/">tablet</a> then has N streams, where each stream is going to a target shard&#x27;s <code>PRIMARY</code> tablet, and each stream filters out any rows from our query results that are not going to the stream&#x27;s target <a href="https://vitess.io/docs/concepts/shard/">shard</a> based on the sharding scheme defined for the table. The applicable rows are then sent to the target shard&#x27;s <code>PRIMARY</code> tablet where they are inserted into the table and metadata for the stream is updated there (in the sidecar database&#x27;s <a href="https://github.com/vitessio/vitess/blob/main/go/vt/sidecardb/schema/vreplication/vreplication.sql"><code>vreplication</code></a> and <a href="https://github.com/vitessio/vitess/blob/main/go/vt/sidecardb/schema/vreplication/copy_state.sql"><code>copy_state</code></a> tables) so that we can continue where we left off when the copy is interrupted for any reason.</p><p><picture class="block"><img alt="VReplication Flow" src="https://planetscale.com/assets-2024/blog/content/zero-downtime-migrations-at-petabyte-scale/vreplication-flow.png" loading="lazy"></picture></p><p>The streams continue to copy rows until we&#x27;ve completed copying all of the rows in our snapshot or we hit the configured <a href="https://vitess.io/docs/reference/vreplication/flags/#vreplication_copy_phase_duration">copy phase cycle duration</a> (see <a href="https://vitess.io/docs/reference/vreplication/internal/life-of-a-stream/">Life of a Stream</a> for more details) — in which case we will pause the row copy work and catch up on all of the changes that have happened to the rows we&#x27;ve copied so far by streaming the applicable binary log events. The binlog events are filtered in each stream by the destination shard and whether or not the change is applicable to a row we&#x27;ve copied as otherwise we&#x27;ll get a later version of the row in a subsequent copy phase cycle. This regular catchup step is important to ensure that we don&#x27;t complete the row copy only to then be unable to replicate from where we left off because the source MySQL instance no longer has binary log events that we need <a href="https://dev.mysql.com/doc/refman/en/purge-binary-logs.html">as they have been purged</a> — in which case we would be forced to start the entire migration over again. This also happens to improve the performance of the overall operation as we are replicating the minimal events needed to ensure eventual consistency.</p><p>c. We do this table by table (serially), across all of the streams (the streams running concurrently), until we&#x27;re done copying the initial table data.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>As you can imagine, executing all of this work on your current live database instance can be somewhat heavy or expensive and potentially interfere with your live application traffic and its overall performance (and there is that brief window in step a where we take a read-only table level lock to get a <code>GTID</code> set/position to go along with the consistent snapshot of the table). It&#x27;s for this reason that we recommend you setup a standard MySQL replica — if you don&#x27;t already have one — and use that as the source MySQL instance for the migration. This is another key factor that ensures we not only avoid downtime, but we avoid any impact whatsoever on the live production system that is currently serving your application data.</p></div></li><li><p>Once the data has been copied to the new system, we continue to replicate changes from the old database instance to the PlanetScale database so that we are ready for the cutover at any point.</p><p>Again, there is a stream from the source (MySQL and unmanaged tablet pair) to each target shard&#x27;s <code>PRIMARY</code> tablet. For each stream, the <a href="https://vitess.io/docs/concepts/tablet/">tablet</a> on the source will connect to the external MySQL instance and initiate a <a href="https://dev.mysql.com/doc/dev/mysql-server/latest/my__command_8h.html"><code>COM_BINLOG_DUMP_GTID</code> protocol command</a>, providing the <code>GTID</code> executed snapshot that the migration/workflow has which corresponds to where we were when the copy phase completed (see <a href="https://vitess.io/docs/reference/vreplication/internal/life-of-a-stream/">Life of a Stream</a> for more details). Each replication stream then continues to filter those binlog events based on the sharding scheme and forward them to the target shard&#x27;s <code>PRIMARY</code> tablet where the changes are applied and persistent metadata is stored (in the sidecar database&#x27;s <a href="https://github.com/vitessio/vitess/blob/main/go/vt/sidecardb/schema/vreplication/vreplication.sql"><code>vreplication</code> table</a>) to record the associated <code>GTID</code> set/position so that no matter what happens, we can restart our work and pick up where we left off. We continue to do this until we&#x27;re ready to cutover.</p></li><li><p>Run a <a href="https://vitess.io/docs/reference/vreplication/vdiff/"><code>VDiff</code></a> to verify that all of the data has been correctly copied and that the new system is in sync with the old, identifying any discrepancies that need to be addressed before the cutover (see <a href="https://vitess.io/blog/2022-11-22-vdiff-v2/">Introducing VDiff V2</a> for more details) — also done without incurring any downtime.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>Each table in the workflow is diffed serially before the <code>VDiff</code> is complete. So all steps below are done for each table in the workflow.</p></div><p>a. We first get a named lock on the workflow in the target keyspace — in the <a href="https://vitess.io/docs/concepts/topology-service/">topology server</a> — to prevent any concurrent changes to the workflow while we are initializing the <code>VDiff</code> as we will be manipulating the workflow to stop it, update it, and then restart it. Once we have the named lock on the workflow we stop the workflow for the table diff initialization done in steps a through e.</p><p>b. We then connect to the source (MySQL and unmanaged tablet pair) and initiate a consistent snapshot there to use for the comparision in the same way that we did in step 1a for the data copy. At this point we have the snapshot we need on the source side.</p><p>c. We then use the <code>GTID</code> position/snapshot from step b to start the stream on each target <code>PRIMARY</code> tablet until it has reached that given position and then it stops (this is the same thing a standard MySQL instance does for <a href="https://dev.mysql.com/doc/refman/en/start-replica.html"><code>START REPLICA UNTIL</code></a>). On each target shard we then setup a consistent snapshot just as we did on the source side. Now we have a consistent snapshot of the table on the source instance and each target shard that we can use for the data comparison.</p><p>d. Now we restart the VReplication workflow so that it can continue replicating changes from the source instance to the target shards.</p><p>e. At this point, we are done manipulating the workflow for the table diff, and we release the named lock on the workflow in the target keyspace taken in step a.</p><p>f. We then execute a full table scan on the source instance and all target shards, comparing the streamed results as we go along, noting any discrepancies as they are encountered (a row missing on either side or a row with different values) — the state of the diff being persisted in the sidecar database&#x27;s <a href="https://github.com/vitessio/vitess/tree/main/go/vt/sidecardb/schema/vdiff"><code>VDiff</code> tables</a> on each target shard.</p><p>You can follow the progress as it goes, which includes an ETA, and when it&#x27;s done you can see a detailed report which notes if any discrepancies were found and providing details on what those differences were — allowing you to address them before the cutover (see the <a href="https://vitess.io/docs/reference/programs/vtctldclient/vtctldclient_vdiff/vtctldclient_vdiff_show/"><code>VDiff show</code> command</a>).</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>The <code>VDiff</code> will choose <code>REPLICA</code> tablets by default on the source and target, for the data streaming (the work is still orchestrated by and the state still stored on the target <code>PRIMARY</code> tablets), to prevent any impact on the live production system. The <code>VDiff</code> is also fault-tolerant — it will automatically pick up where it left off if any error is encountered — and <a href="https://vitess.io/docs/reference/programs/vtctldclient/vtctldclient_vdiff/vtctldclient_vdiff_resume/">it can be done in an incremental fashion</a> so that if e.g. you are in the pre-cutover state for many weeks or even months, you can run an initial <code>VDiff</code>, and then resume that one as you get closer to the cutover point.</p><p>While it is not <strong>required</strong> that this step is taken, it is highly recommended that at least one <code>VDiff</code> is run before the cutover to ensure that the data has been copied correctly and that the new system is in sync with the old.</p></div></li><li><p>At some point between steps 1 and 4, the application starts sending traffic to PlanetScale rather than directly to their old system. PlanetScale then continues to route traffic back to the old system until we&#x27;re ready to cutover.</p><ul><li><a href="https://vitess.io/docs/reference/features/schema-routing-rules/">Schema routing rules</a> are put in place so that during the migration, queries against the tables being migrated will be routed to the correct destination — the external MySQL instance (old system) or the PlanetScale database (new system) depending on where we are in the migration process. When the migration starts, these rules ensure that all queries are sent to the source keyspace (old system) and they are updated accordingly when traffic is cutover along with if and when the cutover is reversed.</li></ul></li><li><p>You can remain in this state as long as necessary as you prepare for the application cutover and perform additional testing of the application and database system.</p></li><li><p>Transparently cutover application traffic to the new system as application traffic is going through PlanetScale and PlanetScale will now route traffic to the new internal system rather than the old external one.</p><p>a. Under the hood, the <a href="https://vitess.io/docs/reference/vreplication/movetables/#switchtraffic"><code>MoveTables SwitchTraffic</code> command</a> is executed for the migration workflow.</p><p>b. It will first do some pre-checks to ensure that the traffic switch should succeed, such as checking the overall health of the tablets involved, the replication lag for the workflow (as the workflow has to fully catch up with the source before we can do the traffic switch and there&#x27;s a timeout for that since this should be a brief period of time as the queries are being buffered), and other necessary state across the cluster. If everything looks good then we will proceed with the actual traffic switch.</p><p>c. We ensure that there are viable <code>PRIMARY</code> tablets in the source <a href="https://vitess.io/docs/concepts/keyspace/">keyspace</a> necessary to setup the reverse VReplication workflow which we will put in place when the traffic switch is complete so that the old system continues to stay in sync with the new and we can cut the traffic back over to the old system if needed for any reason. This offers even more flexibility and confidence as if any unexpected errors or performance issues occur (keep in mind that you may be going from one MySQL, or even MariaDB, version to another and from an unsharded database to a sharded one) then you can quickly <a href="https://vitess.io/docs/reference/vreplication/movetables/#reversetraffic">revert the cutover</a> and investigate the issue. Then once the issues are addressed you can attempt to cut the traffic back over to the new system again — without the pressure of the system being down or needing to complete the final cutover by any particular time.</p><p>d. We take a lock on the source and target <a href="https://vitess.io/docs/concepts/keyspace/">keyspace</a> in the <a href="https://vitess.io/docs/concepts/topology-service/">topology server</a> to prevent concurrent changes to these keyspaces in the cluster, along with a named lock on the workflow in the target keyspace to prevent concurrent changes to the workflow itself.</p><p>e. We stop writes on the source <a href="https://vitess.io/docs/concepts/keyspace/">keyspace</a> and begin buffering the incoming queries (see <a href="https://vitess.io/docs/reference/features/vtgate-buffering/">VTGate Buffering</a> for more details) so that they can be executed on the target keyspace once the traffic switch is complete.</p><p>f. We wait for replication in the workflow to fully catch up so that the target keyspace has every write performed against the source and nothing is lost.</p><p>g. We create a reverse VReplication workflow that will replicate changes from the target keyspace (new system) back to the source keyspace (old system). This is the workflow that ensures that the old system is kept in sync with writes to the new system in case we need to revert the cutover for any reason (using the <a href="https://vitess.io/docs/reference/vreplication/movetables/#reversetraffic"><code>MoveTables ReverseTraffic</code> command</a>).</p><p>h. We initialize any <a href="https://vitess.io/docs/reference/features/vitess-sequences/">Vitess Sequences</a> that are being used in the target keyspace. This is done to seamlessly replace <a href="https://dev.mysql.com/doc/refman/en/example-auto-increment.html"><code>auto_increment</code></a> usage, when the tables are being sharded as part of the migration, to provide the same functionality of auto generating incrementing unique values in a sharded environment.</p><p>i. We allow writes to the target keyspace.</p><p>j. We update the <a href="https://vitess.io/docs/reference/features/schema-routing-rules/">schema routing rules</a> so that any queries against the tables being migrated will now be routed to the target keyspace (new system).</p><p>k. We start the reverse VReplication workflow created in step g.</p><p>l. We mark the original VReplication workflow as <code>Frozen</code> so that it is hidden and cannot be manipulated but we retain that information and state.</p><p>m. We release the keyspace and named locks taken in step a.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>You can remain in this state for as long as you like. It&#x27;s only when you are 100% confident in the migration and no longer need the option of cutting traffic back over to the old system that you can proceed to complete the migration, which uses the <a href="https://vitess.io/docs/reference/vreplication/movetables/#complete"><code>MoveTables complete</code> command</a>, to clean up the workflow and all of its migration related artifacts that were put in place (such as the routing rules).</p></div><p>See <a href="https://vitess.io/docs/reference/vreplication/internal/cutover/">How Traffic Is Switched</a> for additional details.</p></li></ol><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>All of this work is done in a fault-tolerant way. This means that anything can fail throughout this process and the system will be able to recover and continue where it left off. This is critical for data imports at a certain scale where things can take many hours, days, or even weeks to complete and the likelihood of encountering some type of error — even an ephemeral network or connection related error across the fleet of processes involved in the migration — becomes increasingly likely.</p></div><h2 id="conclusion"><a href="https://planetscale.com/blog/zero-downtime-migrations-at-petabyte-scale#conclusion">Conclusion</a></h2><p>Data migrations are a critical part of the lifecycle of any database system. They are sometimes necessary for upgrading to new versions of your existing database system, sharding your existing database system, or moving to an entirely new database system. You&#x27;ve likely been involved in past migrations that have caused downtime or other issues and may be thinking about the next migration you need to do and how you can avoid those issues.</p><p>In walking through how we perform data migrations at PlanetScale we hope that you can see ways to improve your own data migrations and avoid various pitfalls and issues that can lead to undesirable outcomes. We&#x27;re happy to help you with your next data migration — directly as a customer through our work, or indirectly as a member of our shared database community through the sharing of information and practices as we&#x27;ve done here.</p><p>Happy migrations!</p>]]></content>
        <summary><![CDATA[Data migrations are a critical part of the database lifecycle, and are sometimes necessary for version upgrades, sharding, or moving to a new platform. In many cases, migrations are painful and error-prone. In this article, we walk through how migrations are performed at PlanetScale, and offer advice on how to improve the migration experience.]]></summary>
      </entry>
    
      <entry>
        <title>Building data pipelines with Vitess</title>
        <link href="https://planetscale.com/blog/building-data-pipelines-with-vitess" />
        <id>https://planetscale.com/blog/building-data-pipelines-with-vitess</id>
        <published>2024-07-29T15:00:00.000Z</published>
        <updated>2024-07-29T15:00:00.000Z</updated>
        
        <author>
          <name>Matt Lord</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Vitess is a popular <a href="https://www.cncf.io/projects/vitess/">CNCF project</a> that is used to scale some of the largest MySQL installations in the world — by <a href="https://vitess.io/">companies like Slack, Square, Shopify, and GitHub</a>. It provides <a href="https://en.wikipedia.org/wiki/Shard_(database_architecture)">sharding</a>, <a href="https://vitess.io/docs/reference/features/connection-pools/">connection pooling</a>, and many other <a href="https://vitess.io/docs/reference/features/">features that make it easy to scale MySQL horizontally</a>.</p><p>Vitess and MySQL are ideally suited for use as an <a href="https://aws.amazon.com/compare/the-difference-between-olap-and-oltp">Online Transaction Processing (OLTP)</a> system — where the end-user interacts directly with the system and fast response times are essential as they get product and service information, generating critical business records such as orders, user profiles, and more. They are not optimized for <a href="https://aws.amazon.com/compare/the-difference-between-olap-and-oltp">Online Analytical Processing (OLAP)</a> workloads and other use cases and needs that you will encounter as your product, company, and data needs grow. This is where <a href="https://www.confluent.io/learn/change-data-capture/">Change Data Capture (CDC)</a>, AKA <a href="https://aws.amazon.com/what-is/etl/">ETL or Extract-Transform-Load</a>, and <a href="https://www.ibm.com/topics/data-pipeline">Data Pipelines</a> more generally come into play as they allow you to maintain in-sync copies of data across various systems that serve specific needs, with CDC being a technique used to track changes in a database and propagate them to other systems. This is useful for a variety of use cases, including data replication, data warehousing, and data integration. This allows you to e.g. maintain a <a href="https://cloud.google.com/learn/what-is-a-data-warehouse">Data Warehouse</a> and/or <a href="https://azure.microsoft.com/en-us/resources/cloud-computing-dictionary/what-is-a-data-lake">Data Lake</a> for analytics and reporting purposes (e.g. quarterly and yearly sales reports), or to integrate data with other systems where you need to work with data that is initially created and updated by your OLTP system.</p><h2 id="vitess-primitives"><a href="https://planetscale.com/blog/building-data-pipelines-with-vitess#vitess-primitives">Vitess primitives</a></h2><p>Vitess has a number of primitives or building blocks that make it easy to build your data pipelines. These are features of <a href="https://vitess.io/docs/reference/vreplication/vreplication/">VReplication</a>, a powerful system that allows for various types of data replication and transformation. For CDC and similar use cases, VReplication provides the <a href="https://vitess.io/docs/reference/vreplication/vstream/">VStream API</a> in <a href="https://vitess.io/docs/concepts/vtgate/">VTGates</a> (Vitess Gateways) that allows you to stream changes from a Vitess cluster in real-time.</p><p>This low-level <a href="https://vitess.io/docs/reference/vreplication/vstream/">VStream</a> primitive is then used by popular CDC tools like <a href="https://debezium.io/">Debezium</a> to <a href="https://debezium.io/documentation/reference/connectors/vitess.html">capture changes in Vitess and propagate them</a> to other systems. <a href="https://planetscale.com/">PlanetScale</a> also uses the VStream API to build the <a href="https://planetscale.com/blog/extract-load-and-transform-your-data-with-planetscale-connect">Connect feature</a>, using additional open source drivers for popular CDC/ETL services such as <a href="https://airbyte.com/">Airbyte</a> (<a href="https://github.com/planetscale/airbyte-source">source</a>) and <a href="https://www.fivetran.com/">Fivetran</a> (<a href="https://github.com/planetscale/fivetran-source">source</a>).</p><h3 id="a-look-under-the-hood-at-vstream"><a href="https://planetscale.com/blog/building-data-pipelines-with-vitess#a-look-under-the-hood-at-vstream">A look under the hood at VStream</a></h3><p><a href="https://github.com/vitessio/vitess/blob/eb29999a3f4730b2ebe2e0abe1d1996ce2d861d9/proto/queryservice.proto#L103-L113">VStream</a> is a low-level component, provided via <a href="https://grpc.io/">gRPC</a>, that is used internally by <a href="https://vitess.io/docs/reference/vreplication/vreplication/">VReplication</a> to replicate data within Vitess for various workflow types such as <a href="https://vitess.io/docs/reference/vreplication/movetables/"><code>MoveTables</code></a> and <a href="https://vitess.io/docs/reference/vreplication/reshard/"><code>Reshard</code></a> — typically from one <a href="https://vitess.io/docs/concepts/tablet/">VTTablet</a> to another. The <a href="https://github.com/vitessio/vitess/blob/eb29999a3f4730b2ebe2e0abe1d1996ce2d861d9/proto/vtgateservice.proto#L55-L56">VTGate VStream RPC</a> leverages this low-level component to stream data from the <a href="https://vitess.io/docs/concepts/shard/">Shards</a> within a Vitess <a href="https://vitess.io/docs/concepts/keyspace/">Keyspace</a>, providing a single unified change stream spanning the logical database which may consist of hundreds or even thousands of shards. You can see a <a href="https://github.com/vitessio/vitess/blob/main/examples/local/vstream_client.go">simple example client that uses the VStream API directly here</a>.</p><p>This is what the output looks like, with commands that you can run yourself if you are interested in the lower-level aspects (not necessary if you&#x27;re going to use an existing connector/driver such as the <a href="https://debezium.io/documentation/reference/connectors/vitess.html">Debezium Connector for Vitess</a>):</p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">git</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> clone</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> git@github.com:vitessio/vitess.git</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cd</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> vitess</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">git</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> checkout</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> main</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">make</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> build</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cd</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> examples/local</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">./101_initial_cluster.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> mysql</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> &#x3C;</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> ../common/insert_commerce_data.sql</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./201_customer_tablets.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./202_move_tables.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./203_switch_reads.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./204_switch_writes.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./205_clean_commerce.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./301_customer_sharded.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./302_new_shards.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./303_reshard.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./304_switch_reads.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./305_switch_writes.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./306_down_shard_0.sh</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ./307_delete_shard_0.sh</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">go</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> run</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> vstream_client.go</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1"># In another terminal, connecting to the VTGate that was started</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">for</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> i</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> {</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">1..10}</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">;</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> do</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">  command</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> mysql</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-no-defaults</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">h</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 127.0.0.1</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">P</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 15306</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> customer</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">e</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "insert into customer (email) values ('</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">${</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">}</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">@foo.com')"</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">done</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1"># Cleanup whenever you're done testing</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">./401_teardown.sh</span></span>
<span class="line"></span></code></pre></div></div><p>The VStream client will output the changes that are being streamed from the VTGate that look like this — first snapshotting the current state of the <code>customer</code> table in the sharded <code>customer</code> keyspace, before then streaming the subsequent changes to the table as they happen in real-time:</p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">go</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> run</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> vstream_client.go</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:FIELD field_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:20 charset:63 flags:53251 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"bigint"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VARBINARY table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:128 charset:63 flags:128 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"varbinary(128)"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> enum_set_string_values:</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">true</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:ROW row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:1 lengths:14 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"4dan@domain.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_p_ks:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> lastpk:{fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 charset:63 flags:53251} rows:{lengths:1 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"4"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}}}}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:COPY_COMPLETED keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:FIELD field_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:20 charset:63 flags:53251 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"bigint"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VARBINARY table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:128 charset:63 flags:128 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"varbinary(128)"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> enum_set_string_values:</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">true</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:ROW row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:1 lengths:16 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"1alice@domain.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:1 lengths:14 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"2bob@domain.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:1 lengths:18 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"3charlie@domain.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:1 lengths:14 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"5eve@domain.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_p_ks:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> lastpk:{fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 charset:63 flags:53251} rows:{lengths:1 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"5"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}}}} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:COPY_COMPLETED keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COPY_COMPLETED</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456166536000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:FIELD timestamp:1720544456 field_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:20 charset:63 flags:53251 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"bigint"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VARBINARY table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:128 charset:63 flags:128 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"varbinary(128)"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} current_time:1720544456168488000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10001@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456168646000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-59"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-58"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456168652000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456182035000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:FIELD timestamp:1720544456 field_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:INT64 table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer_id"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:20 charset:63 flags:53251 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"bigint"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} fields:{name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VARBINARY table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_table:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"vt_customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> org_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"email"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> column_length:128 charset:63 flags:128 column_type:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"varbinary(128)"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} current_time:1720544456183630000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10012@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456183642000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-59"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-59"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456183649000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456197796000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10023@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456197810000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-59"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456197814000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456211383000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10034@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456211392000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456211398000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456224248000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10045@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456224258000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-61"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456224261000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456237018000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10056@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456237029000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-62"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456237031000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456249777000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10067@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456250142000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-63"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456250150000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456263391000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10078@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456263407000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-60"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-64"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456263411000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456276388000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:9 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"10089@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456276398000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-61"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-64"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456276402000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">type:BEGIN timestamp:1720544456 current_time:1720544456289697000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:ROW timestamp:1720544456 row_event:{table_name:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer.customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> row_changes:{after:{lengths:4 lengths:10 values:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"100910@foo.com"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> flags:1} current_time:1720544456289711000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:VGTID vgtid:{shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/90a3e2d2-3e14-11ef-bb33-30b3ef9417b6:1-62"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">} shard_gtids:{keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"80-"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> gtid:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"MySQL56/97257a80-3e14-11ef-b24b-2eaacc019d10:1-64"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">}} keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> type:COMMIT timestamp:1720544456 current_time:1720544456289714000 keyspace:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"customer"</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> shard:</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"-80"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"></span></code></pre></div></div><p>If you are interested in additional lower-level details, you can check out the <a href="https://vitess.io/docs/reference/vreplication/vstream/">VStream API documentation</a>.</p><h2 id="an-example-setup"><a href="https://planetscale.com/blog/building-data-pipelines-with-vitess#an-example-setup">An example setup</a></h2><p>You could use a similar setup to <a href="https://debezium.io/blog/2017/09/25/streaming-to-another-database/">the one described here</a>, but using the <a href="https://debezium.io/documentation/reference/connectors/vitess.html">Debezium Connector for Vitess</a> rather than the <a href="https://debezium.io/documentation/reference/connectors/mysql.html">Debezium Connector for MySQL</a> and an <a href="https://aws.amazon.com/redshift/">AWS RedShift</a> instance rather than PostgreSQL as the target (with <a href="https://docs.aws.amazon.com/redshift/latest/dg/c_redshift-and-postgres-sql.html">RedShift being based on PostgreSQL</a>). This also demonstrates the general rule that in setting these kinds of systems up you would use a Vitess variant of the connector/driver rather than the MySQL one — with things otherwise being the same.</p><p>Happy streaming!</p>]]></content>
        <summary><![CDATA[Learn the basics of Change Data Capture (CDC) and how to leverage Vitess VStream API to build data pipelines.]]></summary>
      </entry>
    
      <entry>
        <title>The State of Online Schema Migrations in MySQL</title>
        <link href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql" />
        <id>https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql</id>
        <published>2024-07-23T00:00:00.000Z</published>
        <updated>2024-07-23T00:00:00.000Z</updated>
        
        <author>
          <name>Shlomi Noach</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>How do you run non-blocking schema changes in MySQL? This is an eternal question. With a plethora of 3rd party solutions and with recent advancements in MySQL, it&#x27;s difficult to track which solution is preferable for a given schema migration. In this post, we provide a high level overview of the state of MySQL online schema migrations in 2024. We limit the discussion to <code>ALTER TABLE</code> statements, as other DDL statements are <em>typically</em> fast (<code>DROP TABLE</code> is somewhat of an exception, but out of scope of this post).</p><p>We&#x27;ll first examine the native MySQL options: <code>INPLACE</code> and <code>INSTANT</code>. For reference, see <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html">Online DDL Operations</a> MySQL 8.0 documentation.</p><h2 id="inplace-aka-innodb-online-ddl"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#inplace-aka-innodb-online-ddl"><code>INPLACE</code>, aka InnoDB Online DDL</a></h2><p>This is MySQL&#x27;s first take on non-blocking schema changes. <em>Some</em> types of <code>ALTER TABLE</code> (see above link for exhaustive list of supported changes) are eligible to run with <code>ALGORITHM=INPLACE</code>. An <code>INPLACE</code> schema change is technically non-blocking, with quite a few caveats:</p><ul><li>On the server where the query is submitted, normally the primary database server, DML queries (<code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>, ...) are non-blocking and may proceed to execute. Other DDL statements will block, and that&#x27;s expected.</li><li>The operation is resource greedy: the MySQL server will use as much CPU and disk I/O to complete the change as it can. This can and will impact performance on busy servers.</li><li>It requires extra disk space, up to as much disk space as the original table.</li><li>It is uninterruptible. The only way to abort is to kill the query aggressively. This then leads to a further massive cleanup operation, consuming more disk I/O.</li><li>On replica servers, the operation is NOT non-blocking. Meaning if the <code>ALTER TABLE</code> took 3 hours on the primary server, then from the moment it completes you can expect replication to stall while applying that same change for the next 3 hours or so, creating a massive 3 hour lag.</li></ul><p>The replication issue is a deal breaker for most. One way around it is to run the <code>ALTER TABLE</code> on the primary with <code>SQL_LOG_BIN=0</code> so that it does not replicate. Then, run it similarly individually on each replica. This technique works, but can be the cause for inconsistencies. Did you track all servers? What if you subsequently restore (or bootstrap) a server from backup, where the change never took place? How do you track that? Moreover, this technique will take <code>n</code> times longer to complete, as you need to run the change individually for each server. You may parallelize some of the work, but probably not all of it.</p><h3 id="inplace-conclusions"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#inplace-conclusions"><code>INPLACE</code> conclusions</a></h3><p>For these reasons we find that <code>INPLACE</code> is not a good option for non-blocking changes.</p><h2 id="instant-schema-changes"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#instant-schema-changes"><code>INSTANT</code> schema changes</a></h2><p>Instant schema changes are almost a holy grail in the world of databases, and where it works, it&#x27;s the next best thing after pizza (pending any bugs). MySQL offers support for <em>some</em> schema changes to run with <code>ALGORITHM=INSTANT</code>. <a href="https://dev.mysql.com/blog-archive/mysql-8-0-innodb-now-supports-instant-add-column/">Originally contributed to MySQL by Tencent</a> six years ago, <code>INSTANT</code> DDL only supported a single type of change: <code>ADD COLUMN</code>. Later on MySQL added support for more changes, like expanding an <code>enum</code> column, or adding and dropping <code>VIRTUAL</code> columns. Recently (one year ago), MySQL <code>8.0.29</code> added support for arbitrary <code>ADD COLUMN</code> and <code>DROP COLUMN</code> support.</p><p><code>INSTANT</code> truly runs instantly. It does not need to copy a table, does not need extra disk space, does not hammer the CPU. There&#x27;s nothing to interrupt because the operation terminates before you&#x27;ve blinked. It also runs instantly on the replicas.</p><p>It sounds perfect! And it mostly is, where supported, and with a bit of nuance. Consider again the documentation for supported operations. Looking closely, you can see these types of supported changes:</p><ul><li>Changing a column default value.</li><li>Adding/removing <code>VIRTUAL</code> columns.</li><li>Modify an <code>enum</code> definition.</li><li>And more.</li></ul><p>What&#x27;s shared to these changes is that they&#x27;re all metadata changes. They do not affect existing rows, do not modify the data, do not restructure the table, do not affect indexes. <code>ADD COLUMN</code> &amp; <code>DROP COLUMN</code> are the only supported changes that actually affect table data or how the data is structured. As another caveat, you cannot <code>DROP COLUMN</code> using <code>INSTANT</code> DDL if that column participates in an index.</p><p>So you <em>can</em> change a column&#x27;s default value from <code>0</code> to <code>1</code>, but you <em>cannot</em> make a nullable column non-nullable. You <em>can</em> add and drop <code>GENERATED VIRTUAL</code> columns, but <em>cannot</em> add and drop <code>GENERATED STORED</code> columns. You <em>can</em> modify an <code>enum</code> definition, but you <em>cannot</em> modify a column&#x27;s type from <code>int</code> to <code>bigint</code>.</p><p>The list of unsupported changes includes:</p><ul><li>Changing a column&#x27;s data type.</li><li>Adding a column with non-literal default value.</li><li>Adding indexes.</li><li>Modifying a <code>PRIMARY KEY</code> definition.</li><li>Adding/removing foreign keys.</li><li>Changing a table&#x27;s character set.</li><li>Making partitioning changes.</li></ul><p>Which is to say, there&#x27;s a long way to go before <code>INSTANT</code> DDL can satisfy the common needs of schema changes. Where possible, <code>INSTANT</code> DDL is wonderful, and in many situations is the preferable and recommended way to go.</p><h3 id="instant-risks"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#instant-risks"><code>INSTANT</code> risks</a></h3><p><code>INSTANT</code> first appears to be risk-free. In most situations, it is! Even if you make a mistake, it can be corrected with a counter-<code>INSTANT</code> operation. That&#x27;s true for most changes, except when data is destroyed. At this time, the one destructive statement support by <code>INSTANT</code> DDL is <code>DROP COLUMN</code> (for &quot;real&quot;, non <code>VIRTUAL</code> columns).</p><p>Dropping a column has two main risks to it:</p><ol><li>The obvious risk of losing important data, if executed prematurely or accidentally.</li><li>The risk of breaking existing queries.</li></ol><p>Losing data can obviously be a massive incident, the cause for outage and for long hours or days of recovery. But why is this an <code>INSTANT</code> risk in particular? It&#x27;s the same damage whether <code>INSTANT</code> or not, right?</p><p>The answer is with the human behavior of always choosing <code>INSTANT</code> where possible. You&#x27;re an instant away from destroying your data, and with no barriers to hold you back, nor a mechanism (short of backups and delayed replicas) to take you back to safety. We&#x27;ll discuss this shortly as we introduce the concept of Revertibility (specifically in <a href="https://vitess.io/">Vitess</a>).</p><p>How about breaking existing queries? Maybe the data is truly expendable, or safely aggregated elsewhere, but perhaps a bunch of <code>SELECT</code> or <code>INSERT</code> queries still reference the column?</p><p>MySQL offers <a href="https://dev.mysql.com/doc/refman/8.0/en/invisible-columns.html">invisible columns</a> as a means to emulate how your table might look like without a given column. However, it is limited. It only affects queries that do not explicitly use the column name, such as <code>SELECT * FROM my_table ....</code> or <code>INSERT INTO  my_table VALUE (...)</code>. But any <code>SELECT the_column FROM my_table</code> query still has full access to columns. In today&#x27;s world, <code>SELECT *</code> and blind <code>INSERT</code> queries are not as common. Frameworks, tooling, and modern engineering paradigms all tend to be explicit and fully qualified. Invisible columns does not help here.</p><p>If dropping the column did cause queries to break, you will then need to either fix all the queries, or attempt to re-introduce the column. Let&#x27;s now discuss revertibility.</p><h3 id="revertibility"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#revertibility">Revertibility</a></h3><p>What are your options for undoing a change? For switching back to the previous schema? Let&#x27;s illustrate using two simple examples.</p><p>Say your change was to <code>ALTER TABLE my_table ADD COLUMN name ...</code>. This looks harmless, and yet can cause downtime. <code>name</code> can be a common column <em>name</em>. Queries selecting <code>name</code> in a multi-table statement, such as <code>SELECT name, value FROM my_table JOIN another_table USING ...</code>, could fail due to the new ambiguity of <code>name</code> column.</p><p>The anti-change for <code>ADD COLUMN</code> is a <code>DROP COLUMN</code>, and since both are supported by <code>INSTANT</code> DDL, chances are you&#x27;ll be able to recover quickly and relatively safely.</p><p>What if your change was a <code>DROP COLUMN</code>? Lost data aside, what is the anti-change you&#x27;d apply to restore the previous schema? Not only data was lost, but also metadata. What was the column type? Length? Was it nullable? That information cannot be inferred unless you <em>have</em> the previous schema. In all likelihood, you use version control to manage your schema and are thus able to extract the previous definition. It is worth pointing out, though, that crafting the anti-change of a schema migration is nontrivial.</p><h3 id="instant-conclusions"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#instant-conclusions"><code>INSTANT</code> conclusions</a></h3><p>Where possible, <code>INSTANT</code> is often the best approach for making online schema changes. However, it is too limited at this time and does not support the majority of common schema changes. It does not provide revertibility in case of data destruction. The MySQL team does not publish concrete plans for <code>INSTANT</code> DDL support in future versions of MySQL.</p><h2 id="solutions-external-to-mysql"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#solutions-external-to-mysql">Solutions external to MySQL</a></h2><p>A number of 3rd party tools is available today for running online schema changes for MySQL. We will focus on <a href="https://vitess.io/docs/user-guides/schema-changes/managed-online-schema-changes/">Vitess</a>, the technology behind PlanetScale&#x27;s <a href="https://planetscale.com/docs/vitess/schema-changes">non-blocking schema changes</a>. Other 3rd party tools include <a href="https://github.com/github/gh-ost">gh-ost</a>, <a href="https://www.percona.com/doc/percona-toolkit/3.0/pt-online-schema-change.html">pt-online-schema-change</a>, recent newcomer <a href="https://github.com/cashapp/spirit">spirit</a>, and others.</p><p>These tools all share a <a href="https://planetscale.com/docs/vitess/schema-changes/how-online-schema-change-tools-work">similar basic design</a>, but <a href="https://planetscale.com/docs/vitess/schema-changes/online-schema-change-tools-comparison">operate differently</a>. The major characteristics share to all are:</p><ul><li>They mimic an <code>ALTER TABLE</code> by creating a <em>shadow</em> table with the new schema and slowly copying over data.</li><li>They can and often will take longer time to complete as compared with a native MySQL <code>ALTER TABLE</code>.</li><li>They require extra disk space, about as much as the existing table (less if you consider fragmentation, more if you&#x27;re adding bloated indexes, etc.)</li><li>They cause binary log bloating (essentially the entire table content goes through the binary logs).</li><li>They respect production workload, and will pause or throttle as needed so as to give way to production traffic (hence they&#x27;re likely to run longer).</li><li>They operate in small batches of changes, hence are able to keep replication lag to a minimum (and throttle based on lag).</li><li>They are interruptible: the operation can be aborted at no immediate cost (cleanup can be done at a later stage).</li><li>They are capable of handling almost every single kind of schema change.<ul><li>Most have foreign key limitations.</li><li>Some partitioning options are not recommended, or are plain incorrect to run using these tools.</li></ul></li></ul><p>To put it out of the way: if your table has a <code>color enum(&#x27;red&#x27;,&#x27;green&#x27;,&#x27;blue&#x27;)</code> column, and you want to add a new enum value, making it <code>color enum(&#x27;red&#x27;,&#x27;green&#x27;,&#x27;blue&#x27;,&#x27;orange&#x27;)</code>, you&#x27;re better off using <code>INSTANT</code> DDL. There are a handful such cases, that are supported by <code>INSTANT</code> DDL as mentioned above, and where it just doesn&#x27;t make sense to spend hours of migration.</p><p>However, for the (still vast) majority of changes, these are still the go-to solutions. First, of course, we&#x27;ve already established that neither <code>INSTANT</code> nor <code>INPLACE</code> cover all types of changes (they cover a minority of possible changes). But this also leads to an emerging behavior: maintaining two different techniques in your flow/automation creates more complexity. If you already have to use one of the 3rd party solutions, you may as well use it all the time.</p><p>Both <code>vitess</code> and <code>spirit</code> go an extra mile and can auto detect when a migration can be fulfilled using <code>INSTANT</code> DDL, which means you don&#x27;t need to think about it or be aware of which particular version supports which changes.</p><p><code>vitess</code> further <a href="https://vitess.io/docs/user-guides/schema-changes/revertible-migrations/">supports revertibility</a> as first class citizen, able to not only revert back to the original schema, but also to preserve the would-be lost data, while still accounting for any newly added, updated, or removed data since the change.</p><h3 id="note-on-partitioning"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#note-on-partitioning">Note on partitioning</a></h3><p>Partitioning is a strange beast, and implemented in MySQL by creating a &quot;small&quot; table per partition. As such, operations on partitions are really operations on sets of tables. Some partitioning related changes should <em>only</em> be served by MySQL. Such is a <code>DROP PARTITION</code> statement for e.g. <code>RANGE</code> partitioned table. Some other partitioning changes are <em>better</em> served by MySQL, and some are best served by online schema change tools.</p><h3 id="3rd-party-conclusions"><a href="https://planetscale.com/blog/state-of-online-schema-migrations-in-mysql#3rd-party-conclusions">3rd party conclusions</a></h3><p>Most use cases are best served (or only well served) by 3rd party online schema change tools, and those are still the way to go for the foreseeable future.</p>]]></content>
        <summary><![CDATA[Learn about the options for running non-blocking schema changes natively to MySQL, using Vitess, or other tools]]></summary>
      </entry>
    
      <entry>
        <title>Optimizing aggregation in the Vitess query planner</title>
        <link href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner" />
        <id>https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner</id>
        <published>2024-07-22T00:00:00.000Z</published>
        <updated>2024-07-22T00:00:00.000Z</updated>
        
        <author>
          <name>Andres Taylor</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<h2 id="introduction"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#introduction">Introduction</a></h2><p>I recently encountered an intriguing bug. A user reported that their query was causing <a href="https://vitess.io/docs/concepts/vtgate/">VTGate</a> to fetch a large amount of data, sometimes resulting in an Out Of Memory (OOM) error. For a deeper understanding of grouping and aggregations on Vitess, I recommend reading this <a href="https://planetscale.com/blog/grouping-and-aggregations-on-vitess">prior blog post</a>.</p><h2 id="the-query"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#the-query">The Query</a></h2><p>The problematic query was:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> sum</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">user</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">type</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    join</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user_extra </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> user</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">team_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> user_extra</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">group by</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> user_extra</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">order by</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> user_extra</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>The planner was unable to delegate aggregation to MySQL, leading to the fetching of a significant amount of data for aggregation.</p><h2 id="planning-and-tree-rewriting"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#planning-and-tree-rewriting">Planning and Tree Rewriting</a></h2><p>During the planning phase, we perform extensive tree rewriting to push as much work down under Routes as possible. This involves repeatedly rewriting the tree until no further changes occur during a full pass of the tree, a state known as the fixed-point. The goal of this rewriting process is to optimize query execution by pushing operations closer to the data.</p><h3 id="initial-plan"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#initial-plan">Initial Plan</a></h3><p>The first plan after horizon expansion looked like this:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>Ordering (user_extra.id)</span></span>
<span class="line"><span>└── Aggregator (ORG sum(`user`.type), user_extra.id group by user_extra.id)</span></span>
<span class="line"><span>    └── ApplyJoin on [`user`.team_id | :user_team_id = user_extra.id | `user`.team_id = user_extra.id]</span></span>
<span class="line"><span>        ├── Route (Scatter on user)</span></span>
<span class="line"><span>        │   └── Table (user.user)</span></span>
<span class="line"><span>        └── Route (Scatter on user)</span></span>
<span class="line"><span>            └── Filter (:user_team_id = user_extra.id)</span></span>
<span class="line"><span>                └── Table (user.user_extra)</span></span>
<span class="line"><span></span></span></code></pre></div></div><h2 id="trying-to-optimize-the-plan"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#trying-to-optimize-the-plan">Trying to Optimize the Plan</a></h2><p>We don&#x27;t split aggregation between MySQL and VTGate in the initial phases, so we couldn&#x27;t immediately push down the aggregation through the join. However, we can push down ordering under the aggregation.</p><h3 id="pushing-ordering-under-aggregation"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#pushing-ordering-under-aggregation">Pushing Ordering Under Aggregation</a></h3><p>By pushing ordering under aggregation, the plan changes to:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>Aggregator (ORG sum(`user`.type), user_extra.id group by user_extra.id)</span></span>
<span class="line"><span>└── Ordering (user_extra.id)</span></span>
<span class="line"><span>    └── ApplyJoin on `user`.team_id = user_extra.id</span></span>
<span class="line"><span>...</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>We can&#x27;t push the ordering further down since it&#x27;s sorted by the right hand side of the join. Ordering can only be pushed down to the left hand side. This leaves us in an unfortunate situation - ordering is blocking the aggregator from being pushed down, which means we have to fetch all that data, <em>and</em> sort it to do the aggregation.</p><h3 id="the-solution"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#the-solution">The Solution</a></h3><p>The solution I typically use in these situations involves leveraging the phases we have in the planner.</p><h3 id="phases"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#phases">Phases</a></h3><p>We have several phases that run sequentially. After completing a phase, we run the push-down rewriters, then move to the next phase, and so on.</p><p>Rewriters perform one of two functions:</p><ol><li>Running a rewriter over the plan to perform a specific task. For example, the &quot;pull DISTINCT from UNION&quot; rewriter extracts the DISTINCT part from UNION and uses a separate operator for it.</li><li>Controlling when push-down rewriters are enabled. Some rewriters only turn on after reaching a certain phase.</li></ol><p>By delaying the &quot;ordering under aggregation&quot; rewriter until the &quot;split aggregation&quot; phase, we can push down the aggregation under the join. This doesn&#x27;t stop the &quot;ordering under aggregation&quot; rewriter from doing its job, it just has to wait a bit before doing it.</p><p>The final tree looks like this:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>Aggregator (sum(`user`.type) group by user_extra.col)</span></span>
<span class="line"><span>└── Projection (sum(`user`.type) * count(*), user_extra.col)</span></span>
<span class="line"><span>    └── Ordering (user_extra.col)</span></span>
<span class="line"><span>        └── ApplyJoin (on [`user`.team_id = user_extra.id])</span></span>
<span class="line"><span>            ├── Route (Scatter on user)</span></span>
<span class="line"><span>            │   └── Aggregator (sum(type) group by team_id)</span></span>
<span class="line"><span>            │       └── Table (user)</span></span>
<span class="line"><span>            └── Route (Scatter on user_extra)</span></span>
<span class="line"><span>                └── Aggregator (count(*) group by user_extra.col)</span></span>
<span class="line"><span>                    └── Filter (:user_team_id = user_extra.id)</span></span>
<span class="line"><span>                        └── Table (user_extra)</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Most of the aggregation has been pushed down to MySQL, and at the VTGate level, we are left with only SUMming the SUMs we get from each shard.</p><h2 id="conclusion"><a href="https://planetscale.com/blog/optimizing-aggregation-in-the-vitess-query-planner#conclusion">Conclusion</a></h2><p>This optimization demonstrates the complexity of query planning and the importance of efficient tree rewriting in Vitess. By carefully pushing operations closer to the data, we can significantly improve query performance and resource utilization.</p><p>For more details on the implementation, you can check out the <a href="https://github.com/vitessio/vitess/pull/16278">pull request on GitHub</a> that addresses this optimization.</p>]]></content>
        <summary><![CDATA[The Vitess query planner takes multiple passes over a query plan to optimize it as much as possible before execution. A recent tricky bug report led to an improvement in how the optimizer functions.]]></summary>
      </entry>
    
      <entry>
        <title>Announcing Vitess 20</title>
        <link href="https://planetscale.com/blog/announcing-vitess-20" />
        <id>https://planetscale.com/blog/announcing-vitess-20</id>
        <published>2024-06-27T09:01:00.000Z</published>
        <updated>2024-06-27T09:01:00.000Z</updated>
        
        <author>
          <name>Vitess Engineering Team</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>We&#x27;re delighted to announce the release of <a href="https://github.com/vitessio/vitess/releases/tag/v20.0.0">Vitess 20</a> along with <a href="https://github.com/planetscale/vitess-operator/releases/tag/v2.13.0">version 2.13.0</a> of the Vitess Kubernetes Operator.</p><p>Version 20 focuses on improving the usability and maturity of existing features while continuing to build on the solid foundation of scalability and performance established in previous versions. Our commitment remains steadfast in providing a powerful, scalable, and reliable solution for your database scaling needs.</p><iframe allow="autoplay; fullscreen; picture-in-picture" src="https://www.youtube-nocookie.com/embed/qio6tULBGa4?rel=0&color=white" title="What&#x27;s new in Vitess 20"></iframe><h2 id="whats-new-in-vitess-20"><a href="https://planetscale.com/blog/announcing-vitess-20#whats-new-in-vitess-20">What&#x27;s new in Vitess 20</a></h2><ul><li><strong>Query Compatibility</strong>: enhanced DML support including improved query compatibility, Vindex hints, and extended support for various sharded <code>update</code> and <code>delete</code> operations.</li><li><strong>VReplication</strong>: multi-tenant imports (experimental).</li><li><strong>Online DDL</strong>: improved support for various schema change scenarios, dropping support for <code>gh-ost</code>.</li><li><strong>Vitess Operator</strong>: automated and scheduled backups.</li></ul><p>Let&#x27;s dive deeper into some key highlights of this release.</p><h2 id="query-compatibility"><a href="https://planetscale.com/blog/announcing-vitess-20#query-compatibility">Query compatibility</a></h2><p>The latest Vitess release enhances DML support with features like <a href="https://vitess.io/docs/user-guides/vschema-guide/vindex-hints/">Vindex hints</a>, sharded updates with limits, multi-table updates, and advanced delete operations.</p><p>Vindex hints enable users to influence shard routing:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> VINDEX (hash_user_id, secondary_vindex) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 123</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> order </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">IGNORE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> VINDEX (range_order_id) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> order_date </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '2021-01-01'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Sharded updates with limits are now supported:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SET</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">foo</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'abc'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">bar</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 23</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> WHERE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">baz</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 5</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Multi-table updates and multi-target updates enhance flexibility:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t3 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> SET</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">baz</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'abc'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">apa</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 23</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> WHERE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">foo</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 5</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">bar</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 7</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> SET</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">foo</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'abc'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">bar</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 23</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Advanced delete operations with subqueries and multi-target support are included:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DELETE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">IN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> foo </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 32</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> bar </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 43</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DELETE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1, t3 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t3 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> t3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>These features provide greater control and efficiency for managing sharded data. For more details, please refer to the <a href="https://vitess.io/docs/">Vitess</a> and MySQL documentation.</p><h2 id="vreplication-multi-tenant-imports-experimental"><a href="https://planetscale.com/blog/announcing-vitess-20#vreplication-multi-tenant-imports-experimental">VReplication: multi-tenant imports (experimental)</a></h2><p>Many web-scale applications use a multi-tenant architecture where each tenant has their own database (with identical schemas). There are several challenges with this approach — like provisioning and scaling potentially tens of thousands of databases and uniformly updating database schemas across them.</p><p>A sharded Vitess <a href="https://vitess.io/docs/concepts/keyspace/">keyspace</a> is a great option for such a system with a single logical database serving all tenants. Vitess 20 adds support for importing data from such a multi-tenant setup into a single Vitess <a href="https://vitess.io/docs/concepts/keyspace/">keyspace</a>, with new <a href="https://vitess.io/docs/reference/programs/vtctldclient/vtctldclient_movetables/vtctldclient_movetables_create/"><code>--shards</code> and <code>--tenant-id</code> flags</a> for the <a href="https://vitess.io/docs/reference/vreplication/movetables/">MoveTables workflow</a>. You would run one such workflow for each tenant, with imported tenants being served by the Vitess cluster.</p><h2 id="online-ddl-improvements"><a href="https://planetscale.com/blog/announcing-vitess-20#online-ddl-improvements">Online DDL improvements</a></h2><p>Vitess migrations now support <code>enum</code> definition reordering. Vitess opts to use <code>enum</code>s by alias (their string representation) rather than by ordinal value (the internal integer representation).</p><p>Vitess now has better analysis for <code>INSTANT</code> DDL scenarios, enabled with the <code>--prefer-instant-ddl</code> DDL <a href="https://vitess.io/docs/user-guides/schema-changes/ddl-strategy-flags/">strategy flag</a>. It is able to predict whether a migration can be fulfilled by the <code>INSTANT</code> algorithm and use this algorithm if so.</p><p>It also improves support for range partitioning migrations, and opts to use direct partitioning queries over Online DDL where appropriate.</p><p>VDiffs can now be run on Online DDL workflows that are still in progress (i.e., not yet cut-over).</p><p>Release 20.0 drops support for <code>gh-ost</code> for Online DDL, as we continue to invest in <code>vitess</code> migrations based on VReplication. The <code>gh-ost</code> strategy is still recognized; however:</p><ul><li>Vttablet binaries no longer bundle the <code>gh-ost</code> binary. The user should provide their own <code>gh-ost</code> binary, and supply <code>vttablet --gh-ost-path</code>.</li><li>Vitess no longer tests <code>gh-ost</code> in CI/end-to-end tests.</li></ul><h2 id="vitess-operator"><a href="https://planetscale.com/blog/announcing-vitess-20#vitess-operator">Vitess-operator</a></h2><p>Automated and scheduled backups are now available as an experimental feature in v2.13.0. We have added a <a href="https://vitess.io/docs/user-guides/operating-vitess/backup-and-restore/scheduled-backups/">new user guide</a> for this feature.</p><h2 id="vitess-and-the-community"><a href="https://planetscale.com/blog/announcing-vitess-20#vitess-and-the-community">Vitess and the community</a></h2><p>As an open-source project, Vitess thrives on the contributions, insights, and feedback from the community. Your experiences and input are invaluable in shaping the future of Vitess. We encourage you to share your stories and ask questions on <a href="https://github.com/vitessio/vitess">GitHub</a> or in the <a href="http://vitess.io/slack">Slack Vitess community</a>.</p><h3 id="getting-started"><a href="https://planetscale.com/blog/announcing-vitess-20#getting-started">Getting started</a></h3><p>For a seamless transition to <a href="https://github.com/vitessio/vitess/releases/tag/v20.0.0">Vitess 20</a>, we highly recommend reviewing the <a href="https://github.com/vitessio/vitess/blob/main/changelog/20.0/20.0.0/release_notes.md">detailed release notes</a>. Additionally, you can explore <a href="https://vitess.io/docs/20.0/">the Vitess documentation</a> for guides, best practices, and tips to make the most of Vitess 20. Whether you&#x27;re upgrading from a previous version or running Vitess for the first time, our resources are designed to support you every step of the way.</p><p>Thank you for your support and contributions to the Vitess project!</p>]]></content>
        <summary><![CDATA[Vitess 20 is now generally available.]]></summary>
      </entry>
    
      <entry>
        <title>Self-managed Vitess vs Managed Vitess with PlanetScale</title>
        <link href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale" />
        <id>https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale</id>
        <published>2024-05-24T14:00:00.000Z</published>
        <updated>2024-05-24T14:00:00.000Z</updated>
        
        <author>
          <name>Holly Guevara</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>People often ask us — why would I use PlanetScale when Vitess is open source? Surely I can just run it myself? What added benefits do I get with PlanetScale? In this article, we&#x27;re going to answer all of those questions and more.</p><h2 id="planetscales-relationship-with-vitess"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#planetscales-relationship-with-vitess">PlanetScale’s relationship with Vitess</a></h2><p>Let’s start by first defining the relationship between PlanetScale and Vitess.</p><h3 id="history-of-vitess"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#history-of-vitess">History of Vitess</a></h3><p>Vitess was created at YouTube in 2010 to scale their MySQL instances, primarily surrounding issues they faced with heavy write traffic and connection limitations. In 2015, Vitess was donated to the CNCF, and it achieved graduated status in 2018. Since then, Vitess has been adopted by hundreds of the largest sites on the internet to scale their MySQL clusters. Among these companies are Slack, Etsy, GitHub, HubSpot, Shopify, Square, Pinterest, and more.</p><h3 id="planetscale-on-vitess"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#planetscale-on-vitess">PlanetScale on Vitess</a></h3><p>PlanetScale was founded in 2018 by the original co-creators of Vitess from YouTube. PlanetScale is the <a href="https://vitess.devstats.cncf.io/d/5/companies-table">largest contributor to the Vitess codebase</a>, and we employ roughly 75% of the Vitess maintainers.</p><p>Our vision for PlanetScale has always been to create the database platform that much of our staff wish they had during their <a href="https://planetscale.com/about">many years</a> operating sites at massive scale. Essentially, making it as easy as possible to create and maintain databases — at any scale. Of course, a crucial part of that is ensuring the underlying technology itself can handle scale. Vitess was the perfect choice, but the mission doesn’t end there.</p><p>PlanetScale aims to make Vitess <strong>and</strong> MySQL easy to use, operate, and maintain. To do this, we have made it as easy as a click of a button to spin up a new Vitess cluster. We also put a lot of care into crafting the perfect developer experience. We have introduced features like database branching, deploy requests, and more to make online schema changes an afterthought.</p><h2 id="resources-required-to-run-and-maintain-vitess"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#resources-required-to-run-and-maintain-vitess">Resources required to run and maintain Vitess</a></h2><p>Companies that we talk to that are at the start of their Vitess implementation journey often ask us what resources are required to set up and maintain Vitess clusters on your own. The PlanetScale team has extensive experience running thousands of MySQL instances in Vitess clusters, not just at PlanetScale, but also in their previous roles at other companies.</p><p>In this section, I’ll walk through some of our findings about the cost, time, and other requirements involved to run and manage Vitess. I&#x27;ll also highlight some public Vitess-user testimonials to help paint a better picture of this in practice.</p><h3 id="time-to-implement"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#time-to-implement">Time to implement</a></h3><p>As you can imagine, the time it takes to implement Vitess varies widely. There is often a non-trivial amount of prep work that you have to do to &quot;ready&quot; your system to use Vitess. You can take a look through the <a href="https://vitess.io/docs/reference/compatibility/mysql-compatibility/">MySQL compatibility documentation</a> to get an idea of what kind of prep work you may need to do. For example, we commonly see stored procedures and CTEs as blockers. The Vitess team is constantly working to close the gap between full compatibility, and has made great strides recently, but it is a good idea to look through the documentation to make sure you are not heavily using any unsupported MySQL features.</p><p>Once you do this underlying work, then the fun really begins — implementation. This involves identifying which tables (if any) you will shard, planning the sharding scheme, server provisioning, mapping out initial resource allocation, replica usage, etc. Next, you&#x27;ll perform the actual <a href="https://vitess.io/docs/user-guides/migration/migrate-data/">migration</a>, which can be just as time consuming if you run into issues.</p><p>Let’s look at some examples of companies who have implemented Vitess on their own.</p><h4 id="slack"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#slack">Slack</a></h4><p>The team over at Slack wrote a <a href="https://slack.engineering/scaling-datastores-at-slack-with-vitess/">blog post</a> about their experience implementing and working with Vitess. It is a fantastic read, so I highly recommend reading through the full post.</p><p>In short, their journey to implementing and fully moving over to Vitess began in July of 2017. It wasn’t until late 2020 that they were fully migrated over. Those three years of migration of course required a lot of resources and focus on this project, first starting with a PoC, then mapping out and implementing the migration, contributing changes to the Vitess codebase, and more.</p><p>To start, they decided to build a proof of concept — getting a small feature into production using Vitess.</p><blockquote><p>We decided to build a prototype demonstrating that we can migrate data from our traditional architecture to Vitess and that Vitess would deliver on its promise. Of course, adopting a new datastore at Slack scale is not an easy task. It required a significant amount of effort to set up all the new infrastructure in place.</p></blockquote><p>The authors go on to detail some of the work that went into this. Remember, this is just for a tiny service (relative to Slack&#x27;s total datastores).</p><blockquote><p>Our goal was to build a working end-to-end use case of Vitess in production for a small feature: integrating an RSS feed into a Slack channel. It required us to rework many of our operational processes for provisioning deployments, service discovery, backup/restore, topology management, credentials, and more. We also needed to develop new application integration points to route queries to Vitess, a generic backfill system for cloning the existing tables while performing double-writes from the application, and a parallel double-read diffing system so we were sure that the Vitess-powered tables had the same semantics as our legacy databases.</p></blockquote><p>The PoC required a ton of upfront work, but proved to be the correct choice for scalability, so they decided to proceed with the full migration.</p><blockquote><p>However, it was worth it: the application performed correctly using the new system, it had much better performance characteristics, and operating and scaling the cluster was simpler. Equally importantly, Vitess delivered on the promise of resilience and reliability. This initial migration gave us the confidence we needed to continue our investment in the project.</p></blockquote><p><picture class="block"><img alt="Slack Vitess implementation journey" src="https://planetscale.com/assets-2024/blog/content/vitess-vs-planetscale/slack-vitess-journey.png" loading="lazy"></picture></p><p><em>Source: <a href="https://slack.engineering/scaling-datastores-at-slack-with-vitess">https://slack.engineering/scaling-datastores-at-slack-with-vitess</a></em></p><p>Overall, their journey took a total of 3 years and required extensive work from the team. To get Vitess working for Slack, they even became one of the <a href="https://vitess.devstats.cncf.io/d/5/companies-table?orgId=1&var-period_name=Last%20decade&var-metric=contributions">largest contributors to the Vitess repo</a>.</p><blockquote><p>There are many other stories to tell in these 3 years of migrations. Going from 0% to 99% adoption also meant going from 0 QPS to the 2.3 M QPS we serve today. Choosing appropriate sharding keys, retrofitting our existing application to work well with Vitess, and changes to operate Vitess at scale were necessary and each step along the way we learned something new.</p></blockquote><h4 id="square"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#square">Square</a></h4><p>The team over at Square <a href="https://developer.squareup.com/blog/operating-vitess/">wrote a blog series</a> detailing their experience implementing Vitess. The first post is subtitled “Ripping Vitess apart and putting it back together. And the first sentence in the last part of the series sets the stage for what was to come with their implementation:</p><blockquote><p>It has been quite the challenge bringing Vitess online over our existing MySQL database, then sharding and operating it at greater scale over time.</p></blockquote><p>Back in 2016, Square&#x27;s Cash product was growing quickly. They knew they needed to find a new solution, and fast.</p><blockquote><p>Cash was growing tremendously and struggled to stay up during peak traffic. We were running through the classic playbook for scaling out: caching, moving out historical data, replica reads, buying expensive hardware. But it wasn’t enough. Each one of these things bought us time, but we were also growing really fast and we needed a solution that would let us scale out infinitely. We had one final item left in the playbook.</p><p>We had to shard.</p></blockquote><p>Once they started researching, they stumbled upon Vitess. They, like Slack and most Vitess users, had to first deal with some underlying prep work before they could begin the migration. Once that was complete, they ran into unexpected issues, some of which they had to solve while in production: <a href="https://developer.squareup.com/blog/abstracting-sharding-with-vitess-and-distributed-deadlocks/">deadlocks</a> that caused outages, handling <a href="https://developer.squareup.com/blog/cross-shard-queries-lookup-tables/#legacy-code-has-scatter-queries-and-thats-a-problem">scatter queries</a>, keeping <a href="https://developer.squareup.com/blog/sharding-cash/">transactions ACID</a>, <a href="https://developer.squareup.com/blog/shard-splits-with-consistent-snapshots/">resharding</a>, and much more.</p><blockquote><p>Sharding Cash’s database with Vitess was a massive undertaking that set us up for the future, but it was just the start of the journey.</p></blockquote><blockquote><p>Other users of Vitess (like YouTube) can make different trade offs — maybe dropping a comment every once in a while isn’t the end of the world for them. But not us. So the first thing we had to do was change our application code so that it wouldn’t do cross shard transactions in critical money-processing portions of the code.</p></blockquote><p>Although a long, arduous process, it was an essential step in scaling to the next level. And their efforts proved it was worth it, as they knew Vitess was the correct long-term solution to set them up for near-infinite scale.</p><h4 id="running-vitess-on-your-own"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#running-vitess-on-your-own">Running Vitess on your own</a></h4><p>Now, you may not be quite at Slack or Square scale (yet!), but as you can see, implementing and maintaining a system like Vitess isn’t exactly a hands-off task.</p><p>There are of course several companies out there that have deployed and continue to maintain Vitess on their own, so don&#x27;t shy away from it if you&#x27;re set on implementing Vitess yourself. You can find some excellent resources on the <a href="https://vitess.io/docs/">Vitess website</a> to support you through your work. And if you do hit any roadblocks, the <a href="https://vitess.io/community/">Vitess community</a> is fantastic. The <a href="https://vitess.io/slack">Vitess Slack channel</a> has over 4,000 members at the time of writing this, and they are always happy to provide guidance if needed.</p><p>So, in short, it <em>can</em> take a long time to get all the pieces in production, but it really depends on the complexity of your application and the resources you have available to dedicate to the work.</p><p>Regardless, it&#x27;s always nice to have a team with Vitess expertise on-hand to assist you with the process. Especially because making an infrastructure change of this magnitude can be downright <em>scary</em>.</p><blockquote><p>After I run this command we will have completed the first (out of many many) shard splits.</p><p>It’s not reversible without very significant data loss.</p><p>I am utterly terrified.</p><p>I stand up and pace around the room. Is this really it? Have we thought about everything? What if I missed something? What if I screw everything up?</p><p>-<a href="https://developer.squareup.com/blog/sharding-cash/">Jon Tirsen</a></p></blockquote><p>If you&#x27;re interested, here are some additional stories about companies implementing and working with Vitess:</p><ul><li><a href="https://vinted.engineering/2023/04/27/vinted-vitess-voyage-chapter-1-autumn-is-coming/">Vinted Vitess Voyage</a></li><li><a href="https://shopify.engineering/horizontally-scaling-the-rails-backend-of-shop-app-with-vitess">Horizontally scaling the Rails backend of Shop app with Vitess</a></li><li><a href="https://product.hubspot.com/blog/improving-reliability">HubSpot Improving Reliability: Building a Vitess Balancer to Minimize MySQL Downtime</a></li><li><a href="https://product.hubspot.com/blog/hubspot-upgrades-mysql">How HubSpot Upgraded a Thousand MySQL Clusters at Once</a></li><li><a href="https://research.activision.com/publications/2023/04/Scaling-Databases-at-Activision">Activision’s Journey to Scaling Databases with Vitess</a></li></ul><h3 id="ongoing-management-tasks"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#ongoing-management-tasks">Ongoing management tasks</a></h3><p>Preparing for and implementing Vitess is one thing, but the work doesn&#x27;t end there. PlanetScale has huge teams of engineers dedicated to managing the infrastructure that our customers run on. Version upgrades, running backups, Kubernetes cluster maintenance, 24/7 monitoring, managing integrations, and much more. If you&#x27;re going to run Vitess on your own, you&#x27;ll need to have a team on hand to manage all of this.</p><h4 id="version-upgrades"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#version-upgrades">Version upgrades</a></h4><p>One important thing to keep in mind is that the Vitess team typically releases 2-3 major versions <em>per year</em>. This means it&#x27;s easy to fall behind, especially when you have a smaller team managing your Vitess instances. We frequently onboard customers that are 3+ versions behind who just didn&#x27;t have the time to keep up with testing and implementing the upgrades.</p><p>One nice thing about running on PlanetScale&#x27;s infrastructure is that we completely handle all of the version upgrades for you — without any downtime. In addition to the Vitess updates, we also handle MySQL updates as well.</p><h4 id="resharding"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#resharding">Resharding</a></h4><p>Once you&#x27;re in production, your app will likely still continue to grow. Teams sometimes end up facing the challenge of <a href="https://vitess.io/docs/user-guides/configuration-advanced/resharding/">resharding</a> much sooner than anticipated. With PlanetScale&#x27;s support, we are very hands-on in assisting you with this operation.</p><h3 id="cost"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#cost">Cost</a></h3><p>Much of the cost that comes with running Vitess amounts to the team size and time spent implementing and maintaining it. Machine-wise, you may end up saving a bit of money running on Vitess, as you can use smaller machines for each partitioned shard than you would if you just vertically scaled. This also allows you to have more predictable infrastructure pricing, as you aren&#x27;t experiencing such large jumps to scale up.</p><p>To give a ballpark number, for smaller/mid-sized applications running Vitess (200GB-1TB), we usually see teams of around 3-6 DBAs focused on day-to-day database operations. Plus more (closer to 10) for the initial implementation. If you have a much larger application (1+ TB), you&#x27;re looking at at least 10 people at all times.</p><p>With a PlanetScale Enterprise plan, the raw infrastructure costs are usually in line with what you&#x27;d pay running Vitess on your own. Apart from your cloud bill for the infrastructure, you&#x27;ll pay PlanetScale for management and enterprise support. The PlanetScale costs scale with the resources and storage that you provision.</p><p>The time and additional people costs you save by having our team manage everything for you typically makes PlanetScale a more cost-effective solution than running Vitess on your own.</p><blockquote><p>We are very happy with our decision to migrate to PlanetScale Metal which enabled us to achieve the rare outcome of improvements in performance, cost, and reliability – a win for our customers and our business.</p><p>Aaron Young, Engineering Manager @ Block</p></blockquote><h2 id="what-additional-features-do-you-get-with-planetscale"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#what-additional-features-do-you-get-with-planetscale">What additional features do you get with PlanetScale?</a></h2><p>While we do run on Vitess under the hood, that’s not <em>all</em> you get with PlanetScale. We give you the Vitess cluster with a bunch of add-ons on top.</p><h3 id="vitess-maintenance-and-support"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#vitess-maintenance-and-support">Vitess maintenance and support</a></h3><p>A <a href="https://planetscale.com/docs/vitess/managed">PlanetScale-managed Vitess cluster</a> gives you much more than Vitess under the hood. One of the biggest perks is your access to in-house Vitess support and expertise. Our support team truly becomes an extension of your own team.</p><p>Our <a href="https://planetscale.com/docs/support#enterprise">Enterprise support package</a> gives you direct access to our team through a shared private Slack channel and recurring video calls (if desired). You&#x27;re able to ask us any questions you have about performance, architecture, query optimization, and whatever else you need. We work hard to make sure that all of our customers are successful and have a great experience on PlanetScale.</p><p>You&#x27;ll also get hands-on assistance at every step of the migration process. We are in close contact with you starting with the proof of concept, onto planning out the sharding scheme, planning and implementing the migration, performing version upgrades, and continuing to support you throughout our entire relationship — well beyond getting into production. We even hold the pager for you, often detecting and mitigating any issues well before your team is even aware they existed.</p><blockquote><p>Databases are hard. We would rather PlanetScale manage them. We wanted the support PlanetScale offers because they are the experts in the field. We’ve seen this come to fruition in our relationship.</p><p>-<a href="https://planetscale.com/case-studies/myfitnesspal">Chris Karper, VP of Engineering at MyFitnessPal</a></p></blockquote><p>Another huge perk is our close relationship with Vitess. Because we employ around 75% of the Vitess maintainers and are the largest contributors to the repository, we have a huge wealth of knowledge and a large concentration of Vitess expertise.</p><h3 id="dashboard-for-branching-deploy-requests-and-more"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#dashboard-for-branching-deploy-requests-and-more">Dashboard for branching, deploy requests, and more</a></h3><p>Because PlanetScale’s mission is to make it as easy as possible to manage your database clusters at any size, we put a lot of thought and care into our dashboard to make it simple to leverage both Vitess and PlanetScale features.</p><p><picture class="block"><img alt="PlanetScale dashboard" src="https://planetscale.com/assets-2024/blog/content/vitess-vs-planetscale/planetscale-dashboard.png" loading="lazy"></picture></p><p>Vitess comes with <a href="https://vitess.io/docs/user-guides/schema-changes/ddl-strategies/">built-in support for online schema changes</a>. However, you cannot do schema changes through the VTAdmin UI. With PlanetScale, you get access to deploy requests. Deploy requests are used to make safe, reviewable, diffable schema changes to your database.</p><p>This enables your team to work quickly, efficiently, and <a href="https://planetscale.com/blog/safely-making-database-schema-changes">safely</a>.</p><h3 id="planetscale-global-network"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#planetscale-global-network">PlanetScale Global Network</a></h3><p><picture class="block"><img alt="PlanetScale global network diagram" src="https://planetscale.com/assets-2024/blog/content/vitess-vs-planetscale/planetscale-global-network.png" loading="lazy"></picture></p><p>The PlanetScale Global Network is our edge infrastructure that is responsible for automatically routing reads to the closest replica. This is an additional layer on top of Vitess that you only get with PlanetScale. It also supports the following features:</p><ul><li>Latency-based routing</li><li>Near-infinite connection pooling</li><li>In-app private data access</li><li>First-class serverless support</li><li><a href="https://planetscale.com/docs/vitess/etl">PlanetScale Connect</a></li></ul><h3 id="compliance-and-security"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#compliance-and-security">Compliance and security</a></h3><p>PlanetScale is committed to delivering a powerful and easy-to-use database platform while keeping your data secure. We have a number of crucial certifications and compliance measures in place as well, which are essential for customer audits. Some of these security measures that are baked into the PlanetScale product include:</p><ul><li>SOC 1 Type 2</li><li>SOC 2 Type 2+ HIPAA</li><li>BAAs available</li><li>PCI compliant (on <a href="https://planetscale.com/blog/planetscale-managed-is-now-pci-compliant">PlanetScale Managed</a>)</li><li>Compliance with GDPR and other global privacy regulations</li><li>Data locality</li><li>Private database connectivity through PrivateLink or GCP Private Service Connect</li><li>Audit and security logs</li><li>IP restrictions</li><li>And more</li></ul><p>Learn more in our <a href="https://planetscale.com/docs/security">Security documentation</a>.</p><h3 id="query-monitoring-and-insights"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#query-monitoring-and-insights">Query monitoring and insights</a></h3><p><a href="https://planetscale.com/docs/vitess/monitoring/query-insights">PlanetScale Insights</a> is our in-dashboard query performance analytics tool that allow you to track performance down to the individual query level. This gives you a great view of metrics such as query latency, errors, and anomalies — any query activity that we detect as out of the ordinary. You&#x27;ll also receive <a href="https://planetscale.com/docs/vitess/monitoring/schema-recommendations">schema recommendations</a> to improve database performance, reduce memory and storage, and improve your schema based on production database traffic.</p><p><picture class="block"><img alt="PlanetScale Insights" src="https://planetscale.com/assets-2024/blog/content/vitess-vs-planetscale/planetscale-insights.png" loading="lazy"></picture></p><h2 id="planetscale-can-help"><a href="https://planetscale.com/blog/self-run-vs-managed-vitess-with-planetscale#planetscale-can-help">PlanetScale can help</a></h2><p>Adding a massive piece of technology to your infrastructure can certainly be daunting. The path to production sometimes doesn&#x27;t go according to plan and can often require much more time and money than originally budgeted.</p><p>At PlanetScale, we see time and time again companies who know Vitess is the right solution for them, but aren&#x27;t sure if they want to spend the time implementing and managing it on their own. If you&#x27;re in that boat, it&#x27;s easy to set up a call with our Technical Solutions team to see if we&#x27;re the right fit for you. Our <a href="https://planetscale.com/docs/enterprise/sales-process">sales process</a> aims to make it as easy as possible to get all of your questions answered as fast as possible. You&#x27;re able to jump straight into a technical evaluation right away — either via email or call.</p><p>There are also instances where your desired configuration isn&#x27;t something that would work well with PlanetScale, and we don&#x27;t shy away from telling you if so. At the very least, a quick chat with us will hopefully at least bring clarity that you&#x27;re definitely choosing the correct path.</p><p>Again, we employ the majority of the Vitess maintainers, and our entire team at PlanetScale has extensive experience implementing and maintaining huge Vitess clusters. If you are thinking about implementing Vitess on your own and are curious how we can help, we&#x27;d love to hear from you. Simply <a href="https://planetscale.com/contact">fill out our contact form</a>, and we will be in touch.</p>]]></content>
        <summary><![CDATA[PlanetScale and Vitess have a close relationship. Learn what it looks like to run Vitess on your own vs using PlanetScale. We cover cost, time to implement, management, and more.]]></summary>
      </entry>
    
      <entry>
        <title>Achieving data consistency with the consistent lookup Vindex</title>
        <link href="https://planetscale.com/blog/vitess-consistent-lookup-vindex" />
        <id>https://planetscale.com/blog/vitess-consistent-lookup-vindex</id>
        <published>2024-04-29T00:00:00.000Z</published>
        <updated>2024-04-29T00:00:00.000Z</updated>
        
        <author>
          <name>Harshit Gangal</name>
        </author>
        
        <author>
          <name>Deepthi Sigireddi</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<h2 id="what-are-vindexes"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#what-are-vindexes">What are Vindexes</a></h2><p>Vitess uses <a href="https://vitess.io/docs/reference/features/vindexes/">Vindexes</a> (short for Vitess Index) to associate rows in a table with a designated address known as <code>Keyspace ID</code>. This allows Vitess to direct a row to its intended destination, typically a shard within the cluster.</p><p>Vindexes play a dual role: enabling data sharding through Primary Vindexes and facilitating global indexing via Secondary Vindexes. Through this mechanism, Vindexes serve as an indispensable tool for routing queries in a sharded database, ensuring optimal performance and scalability.</p><h2 id="lookup-vindex"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#lookup-vindex">Lookup Vindex</a></h2><p><a href="https://vitess.io/docs/reference/features/vindexes/#functional-and-lookup-vindex">Lookup Vindex</a> as a Secondary Vindex is used to direct Select/Update/Delete queries to the appropriate shard without incurring the performance penalty associated with scatter-gather operations—wherein the query is sent to all shards for processing.</p><p>When data is inserted into a table, a separate global index table maintains the mapping of a secondary index column to the corresponding <code>Keyspace ID</code>. This mapping information is later used to efficiently route queries to the destination shard.</p><p>Secondary Vindexes can be unique or non-unique, and we’ll illustrate both types. Let us look at an example to see how this works.</p><h4 id="data-table-definition"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#data-table-definition">Data table definition</a></h4><p><strong>USER Table</strong></p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------+--------------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Field</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">         |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Null</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Key</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------+--------------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> bigint</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRI</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar(255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> YES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> bigint</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> YES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> UNI</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> email</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar(255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> YES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------+--------------+------+-----+</span></span>
<span class="line"></span></code></pre></div></div><h4 id="non-unique-vindex-table-definition"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#non-unique-vindex-table-definition">Non unique Vindex table definition</a></h4><p><strong>NAME_USER_VDX Table</strong></p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+--------------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Field</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">         |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Null</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Key</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+--------------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar(255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRI</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">          |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> bigint</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRI</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> binary(8</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)    </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> YES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+--------------+------+-----+</span></span>
<span class="line"></span></code></pre></div></div><h4 id="unique-vindex-table-definition"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#unique-vindex-table-definition">Unique Vindex table definition</a></h4><p><strong>PHONE_USER_VDX Table</strong></p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+-----------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Field</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Null</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Key</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+-----------+------+-----+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">       |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> bigint</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRI</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> binary(8</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> YES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-------------+-----------+------+-----+</span></span>
<span class="line"></span></code></pre></div></div><p>When executing a query like <code>select id, phone, email from user where name = &#x27;Alex&#x27;</code>, the query planner uses the lookup Vindex table, <code>name_user_vdx</code>, to map the value <code>Alex</code> to its corresponding <code>Keyspace ID</code>. This lets the planner direct the query to a single destination shard rather than to all shards, thus avoiding a costly <code>scatter-gather</code> operation.</p><p>Of particular interest is the <code>Consistent Lookup Vindex</code>, a type of Secondary Vindex, which further enhances the efficiency and reliability of this routing mechanism.</p><h2 id="consistent-lookup-vindex"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#consistent-lookup-vindex">Consistent lookup Vindex</a></h2><p>The <code>user</code> data table and lookup Vindex tables are both sharded in most cases to enable optimal performance and storage. The sharding column for the <code>user</code> table and the Vindex tables are likely to be different. In the scenario above, let&#x27;s consider the sharding columns to be:</p><div class="table-overflow"><table><thead><tr><th>Table</th><th>Primary Vindex Column</th></tr></thead><tbody><tr><td>User</td><td>id</td></tr><tr><td>Name_User_Vdx</td><td>name</td></tr><tr><td>Phone_User_Vdx</td><td>phone</td></tr></tbody></table></div><p>Changing data in the <code>user</code> table through DML statements (Insert/Update/Delete) leads to changes to rows in the Vindex tables as well. To maintain consistency between the <code>user</code> data table and the Vindex tables, all these operations will need to occur in a transaction that spans multiple shards. This means we need to implement a costly protocol like 2PC (Two Phase Commit) to guarantee Atomicity and Isolation (A and I from ACID). Not using a proper multi-shard transaction for these operations can lead to partial commit and inconsistent data.</p><p>Consistent Lookup Vindex uses an alternate approach that makes use of careful locking and transaction sequences to guarantee consistency without using 2PC for all DML operations. This allows Vitess to provide a consistent view of the <code>user</code> data table even when record in the Vindex tables may be inconsistent.</p><p>When data is being modified, Vitess uses 3 connections to perform DML operations. Let’s call them Pre, Main and Post. Any transaction open on these connections will follow a well-defined sequence of operations. Committing a transaction will result in the following:</p><ul><li>Commit on Pre</li><li>Commit on Main</li><li>Commit on Post</li></ul><p>A failure in any of these steps rolls back the remaining open transactions in the same order.</p><p>Let’s look at an example to see how this works.</p><h4 id="sample-rows"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#sample-rows">Sample rows</a></h4><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USER:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> email</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">           |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 100</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Alex</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8877991122</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> alex@mail.com</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> emma@mail.com</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NAME_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Alex</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 100</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x313030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">PHONE_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8877991122</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x313030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"></span></code></pre></div></div><h3 id="delete-operation"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#delete-operation">Delete operation</a></h3><p>Deletion of Lookup Vindex table data happens through the <strong>Post</strong> connection.</p><p><strong>Example:</strong> <code>delete from user where id = 100</code></p><ol><li><p>First select all the lookup columns from the <code>User</code> Table</p><p><strong>Main:</strong> <code>select id, name, phone from user where id = 100 for update</code></p></li><li><p>Delete the Lookup Vindex Rows</p><p><strong>Post-Transaction:</strong></p><ol><li><code>delete from name_user_vdx where name = &#x27;Alex&#x27; and id = 100</code></li><li><code>delete from phone_user_vdx where phone = 8877991122</code></li></ol></li><li><p>Delete the User Table Row</p><p><strong>Main:</strong> <code>delete from user where id = 100</code></p></li></ol><p>On Commit, suppose the Main transaction succeeds but the Post transaction fails. Let’s see how we are still able to maintain consistency.</p><h4 id="updated-rows"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#updated-rows">Updated rows</a></h4><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USER:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> email</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">           |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> emma@mail.com</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NAME_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Alex</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 100</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x313030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">PHONE_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8877991122</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x313030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"></span></code></pre></div></div><p>If a select query is received <code>select count(*) from user where name = &#x27;Alex&#x27;</code></p><p>A lookup call will happen with <code>name = &#x27;Alex&#x27;</code> to the <code>name_user_vdx</code> Vindex which will return the shard destination with keyspace_id of <code>0x313030</code>. When the query is sent down to the specific shard a matching row does not exist in the <code>User</code> table any longer and hence will return no results.</p><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+----------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> count(*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+----------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        0</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+----------+</span></span>
<span class="line"></span></code></pre></div></div><p>The lookup Vindex table may be inconsistent with the <code>User</code> table but the results returned for the query remained consistent with the <code>User</code> table.</p><h3 id="insert-operation"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#insert-operation">Insert operation</a></h3><p>Insertion of Lookup Vindex table data happens through the <strong>Pre</strong> connection.</p><p><strong>Example:</strong> <code>insert into user(id, name, phone, email) values (300, &#x27;Emma&#x27;, 8877991122, &#x27;xyz@mail.com&#x27;)</code></p><ol><li>Insert into Lookup Vindex table <strong>Pre-Transaction:</strong><ol><li><code>insert into name_user_vdx(name, id, keyspace_id) values (&#x27;Emma&#x27;, 300, &#x27;0x333030&#x27;)</code> No error as <code>name</code> is a non-unique column.</li><li><code>insert into phone_user_vdx(phone, keyspace_id) values (8877991122, &#x27;0x333030&#x27;)</code> This results in a duplicate key error as it is a unique column. Note that this row is left over from the error we got during the previous delete operation. We’ll get into the details of how this is handled in a minute.</li></ol></li><li>Insert the User table Row <strong>Main:</strong> <code>insert into user(id, name, phone, email) values (300, &#x27;Emma&#x27;, 8877991122, &#x27;xyz@mail.com&#x27;)</code></li></ol><p><strong>Handling of Duplicate Key Error in Lookup Vindex:</strong></p><ol><li>Lock the lookup row so that no other transaction can race with the current operation. <strong>Pre-Transaction:</strong> <code>select phone, keyspace_id from phone_user_vdx where phone = 8877991122 for update</code></li><li>Lock the main table row to ensure that the row we want to insert does not exist yet and no other transaction can race with the current operation.<ol><li><strong>Main:</strong> <code>select phone from user where phone = 8877991122 for update</code> Because we previously deleted the corresponding row for this select, it will return no results. This tells us that the lookup Vindex table has an orphan row which can be updated with the new value from the insert statement.</li><li><strong>Pre-Transaction:</strong> <code>update phone_user_vdx set keyspace_id = ‘0x333030’ where phone = 8877991122</code></li></ol></li></ol><h4 id="updated-rows"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#updated-rows">Updated rows</a></h4><div class="code-block" data-language="shell"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USER:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> email</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">           |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> emma@mail.com</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 300</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8877991122</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> xyz@mail.com</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+-----+------+------------+-----------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NAME_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Alex</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 100</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x313030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 200</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Emma</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 300</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x333030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------+-----+--------------------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">PHONE_USER_VDX:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> phone</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">      |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> keyspace_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8811229988</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x323030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">|</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 8877991122</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> 0x333030</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">                 |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+------------+--------------------------+</span></span>
<span class="line"></span></code></pre></div></div><h3 id="update-operation"><a href="https://planetscale.com/blog/vitess-consistent-lookup-vindex#update-operation">Update operation</a></h3><p>Update of Lookup Vindex table data happens through a Delete operation followed by an Insert operation. We already know that Delete operation is handled through <strong>Post</strong> connection and Insert operation through <strong>Pre</strong> connection.</p><p>In the special case of an update where the Vindex column value is unchanged, it will cause <code>lock wait timeout</code> on the Insert operation (on the <strong>Pre</strong> connection) as the row lock will be held by the Delete operation (on the <strong>Post</strong> connection). To mitigate this, updating Vindex column data with the same value as before is turned into a no-op for lookup Vindex tables.</p><p>However, it is still possible to run into this limitation if the same lookup Vindex value is deleted and inserted as two different statements inside the same transaction.</p><p>If you want to learn more about this feature in Vitess, check out the Vitess documentation. They have docs on <a href="https://vitess.io/docs/reference/features/vindexes/">Vindexes</a>, <a href="https://vitess.io/docs/user-guides/vschema-guide/unique-lookup/">Unique</a> and <a href="https://vitess.io/docs/user-guides/vschema-guide/non-unique-lookup/">Non-Unique</a> lookup vindexes. You are also welcome to join the vitess <a href="https://vitess.io/slack">community</a>.</p>]]></content>
        <summary><![CDATA[How we implemented a consistent lookup Vindex in Vitess to ensure data consistency without using 2PC]]></summary>
      </entry>
    
      <entry>
        <title>The MySQL adaptive hash index</title>
        <link href="https://planetscale.com/blog/the-mysql-adaptive-hash-index" />
        <id>https://planetscale.com/blog/the-mysql-adaptive-hash-index</id>
        <published>2024-04-24T00:00:00.000Z</published>
        <updated>2024-04-24T00:00:00.000Z</updated>
        
        <author>
          <name>Ben Dicken</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>If you&#x27;re using MySQL, you likely have indexes that are powered by B-trees. The B-tree is a powerful data structure, and is frequently used to construct indexes in relational databases. If you are using the InnoDB storage engine, it is the <em>only</em> choice for your index, save for spatial indexes. However, MySQL has a secret weapon for making lookups with these types of indexes even faster: the Adaptive Hash Index, or AHI. Before jumping into how this works, let&#x27;s take a few moments to review B-trees, the InnoDB buffer pool, and how these work together during index lookups.</p><h2 id="b-tree-indexes"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#b-tree-indexes">B-Tree indexes</a></h2><p>The B-tree data structure has been used by computer systems for decades. It is particularly useful in the context of file system and data storage applications, due to the fact that each node can store many values. This is useful in algorithms that interface with storage systems, as the size of each node can be set to work well with the unit(s) of storage, for example aligning with the page size of a hard-drive disk.</p><p>As its name suggests, a B-tree is a specific type of <a href="https://en.wikipedia.org/wiki/Tree_(data_structure)">tree structure</a>, but differs in several ways, including:</p><ul><li>Each node contains up to N values, rather than just one</li><li>Each node can have up to N+1 children</li><li>All elements are stored in-order for a left-to-right traversal</li><li>All leaf nodes are on the same level of the tree</li></ul><p>Let&#x27;s look at an example B-tree build using first names from a database:</p><p><picture class="block"><img alt="A diagram of a B tree" src="https://planetscale.com/assets-2024/blog/content/the-mysql-adaptive-hash-index/btree.jpg" loading="lazy"></picture></p><p>In this example, N = 2, and all of the value slots in all of the nodes are full. For InnoDB B-tree indexes, N is usually much larger, as it is determined by how many values can fit within an InnoDB page (this defaults to 16k). In addition to this, nodes are often left with some buffer of extra space to allow for faster future inserts.</p><p>When you create an index on an InnoDB table, MySQL constructs a B-tree containing the values from the indexed column. The child pointer <em>before</em> any given value points to another node containing values that are less than it, and the pointer after contains ones that are greater. Thus, a left-to-right traversal of this structure should visit all of the data elements in order.</p><p>In the general case, the size of a B-tree node can be set to whatever the programmer desires. In InnoDB, index nodes are set to match the InnoDB page size. This is pretty large, so each node can potentially store hundreds of values.</p><p>Say we want to search for the name <code>paul</code> in this B-tree.</p><p><picture class="block"><img alt="A diagram of a B tree search" src="https://planetscale.com/assets-2024/blog/content/the-mysql-adaptive-hash-index/btree-search.jpg" loading="lazy"></picture></p><p>The steps to accomplish this are:</p><ul><li>Compare <code>paul</code> and <code>ian</code>, determine <code>ian</code> &lt; <code>paul</code>.</li><li>Check and see that <code>paul</code> &lt; <code>remy</code>, so traverse down middle pointer.</li><li>Compare <code>paul</code> to first <code>liam</code> and then <code>omar</code>. It is greater than both, so traverse down third pointer.</li><li>Discover <code>paul</code> as the first element in the leaf node.</li></ul><p>Doing this is much faster than a linear scan of all elements would have been. However, a search operation is not quite O(1), as multiple comparisons and page loads are typically needed for a single lookup. With only two values per node and a small data set, the benefit of a B-tree compared to a linear search is less apparent. But the benefits are huge for large data sets. InnoDB B-tree indexes rarely need to go beyond, say, 5 levels, even for tables with millions of rows.</p><p>There&#x27;s lots more that could be said about B-trees. However, the important take-away is that B-trees provide ultra-fast lookups for queries that utilize the index. Yet they are not quite a constant-time operation, and require multiple page reads and comparisons.</p><h2 id="the-innodb-buffer-pool"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#the-innodb-buffer-pool">The InnoDB buffer pool</a></h2><p>When using InnoDB, all of your data, including any and all indexes you have on your columns, are stored in pages on disk. As MySQL executes and fulfills queries, it needs to be able to load subsets of this data into RAM for faster access. In MySQL, the region of memory MySQL use for storing InnoDB data is known as the buffer pool, and its size is configurable using the <code>innodb_buffer_pool_size</code> configuration option. As MySQL runs and reads rows and indexes, it grabs pages (16k by default in InnoDB) from disk and puts them into this region of RAM.</p><p>Generally, the larger this pool is, the better the performance of your database will be, up to the point where the pool size = the size of your data set. If the size of the data is larger than the space available in the pool, MySQL will eventually have to evict pages from the buffer pool to make room for new ones to be loaded. MySQL uses a modified Least Recently Used (LRU) algorithm for this.</p><p>Due to this structure, when MySQL starts performing B-tree index lookups, initially many pages will have to be loaded from disk, a more costly I/O operation. However, as it processes more and more lookups, many of the these pages may end up residing in the buffer pool, meaning each B-tree node visit and comparison executes faster because it&#x27;s primarily hitting memory rather than disk. This helps performance, but it still requires looking at multiple pages as the B-tree is used to complete a search.</p><h2 id="hash-lookups-and-performance"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#hash-lookups-and-performance">Hash lookups and performance</a></h2><p>Another approach to performing fast lookups on large sets of data is to use hashing. With a hashing approach, we store all of the data (in this case, the first names of our users) in a large table. When performing a lookup for the name <code>paul</code>, we run this through a hash function that generates a repeatable output index. This index can be used to do a direct lookup into the hash table.</p><p>In the absence of collisions, a hash table lookup is a O(1) operation. Assuming your hash table data is in memory, this is even faster than a lookup into a B-tree index.</p><p><picture class="block"><img alt="Example of looking something up in a hash table" src="https://planetscale.com/assets-2024/blog/content/the-mysql-adaptive-hash-index/hash-lookup.jpg" loading="lazy"></picture></p><p>Though MySQL broadly supports <a href="https://dev.mysql.com/doc/refman/8.3/en/index-btree-hash.html">both <code>BTREE</code> and <code>HASH</code> indexes</a>, the InnoDB engine actually <a href="https://dev.mysql.com/doc/refman/8.3/en/create-index.html">does not support <code>HASH</code></a>. If you try to create an index with <code>USING HASH</code> on an InnoDB-powered table, MySQL will instead create a B-tree index.</p><p>For instance, doing the following works without error:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">mysql</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">></span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INDEX</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> alias_index </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user(username) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USING</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> HASH</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Query OK, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> affected, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> warning (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">79</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> sec)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Records: </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  Duplicates: </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  Warnings: </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span></span>
<span class="line"></span></code></pre></div></div><p>However, the <code>information_schema</code> tells us that it really is a B-tree index that was created:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_name, index_name, index_type</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> information_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">statistics</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_schema </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'quiz'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_name </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'user'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------+-------------+------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| TABLE_NAME | INDEX_NAME  | INDEX_TYPE |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------+-------------+------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| user       | alias_index | BTREE      |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| user       | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">PRIMARY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | BTREE      |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------+-------------+------------+</span></span>
<span class="line"><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> in</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">00</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> sec)</span></span>
<span class="line"></span></code></pre></div></div><p>It is a bit unfortunate that InnoDB does not support building on-disk, hash based indexes, as this type could be useful and more performant than a B-tree in some instances. However, this lack of support does not mean that hashing is not used at all for index lookups.</p><h2 id="the-adaptive-hash-index"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#the-adaptive-hash-index">The adaptive hash index</a></h2><p>Though InnoDB does not support on-disk hash indexes, MySQL has a feature to do in-memory hash lookups for indexing. This is known as the <a href="https://dev.mysql.com/doc/refman/8.3/en/innodb-adaptive-hash.html">adaptive hash index</a>. This feature can be used to speed up already-fast B-tree lookups, accelerating the performance of the queries that utilize these indexes as a part of their query plan. This acts as a layer that sits between the execution of MySQL and the in-memory buffer pool.</p><p><picture class="block"><img alt="Architecture diagram of MySQL, AHI, the buffer pool, and disk" src="https://planetscale.com/assets-2024/blog/content/the-mysql-adaptive-hash-index/mysql-ahi-buffer-pool-disk.jpg" loading="lazy"></picture></p><p>As its name suggests, the adaptive hash index (AHI) is constructed at runtime, and its usage adapts to the characteristics of your workload. If MySQL observes that a particular value is getting repeatedly looked up in a B-tree index, an entry in the AHI can be created either with the full value, or a prefix of the value. For future lookups of this same value (which are likely, since MySQL observed its repeated use) it will use the AHI instead of the B-tree. The keys of the AHI are the values (or value prefixes) of the underlying index. The values are pointers, referring to where the data for this value lives within the InnoDB buffer pool.</p><p><picture class="block"><img alt="How the adaptive hash index works with the InnoDB buffer pool" src="https://planetscale.com/assets-2024/blog/content/the-mysql-adaptive-hash-index/ahi-pool-diagram.jpg" loading="lazy"></picture></p><p>The pointers in the adaptive hash index only point to data within the buffer pool. Thus, the buffer pool needs to be sufficiently large for the AHI to kick in. If it is small and there are a lot of evictions taking place, it is not worth using it. Thankfully, MySQL is able to automatically adjust its use of the AHI based on the behavior it observes in the buffer pool. If conditions are not right for its use (few repeated lookups, small buffer pool, etc), MySQL will reduce or eliminate its use.</p><p>Though it can speed up queries, there is a bit of overhead to maintaining this special hash index. The feature can be enabled or disabled via the <code>innodb_adaptive_hash_index</code> configuration option. It is typically enabled by default, but if you have a workload that you know will not benefit from it, it can be disabled using <code>innodb_adaptive_hash_index=0</code> in your configuration file.</p><h2 id="testing-performance-of-the-adaptive-hash-index"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#testing-performance-of-the-adaptive-hash-index">Testing performance of the adaptive hash index</a></h2><p>Let&#x27;s run a few tests to see how the adaptive hash index can help a workload. We&#x27;ll start with something very simple. I&#x27;ll execute the following query 500k times in two different scenarios: with and without the AHI enabled.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user_id, username, bio </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> user </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> username </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'willpeace1'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>This is executing against a <code>users</code> table with a little over 390 million rows in it. With it disabled, we get the following timing:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">$</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> python3</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> same_query.py</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 500000</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">starting</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> query</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> load</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">completed</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> in</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 35.6</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> seconds</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">QPS</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 14043.57</span></span>
<span class="line"></span></code></pre></div></div><p>This goes fast, because I have a B-tree index already set on the <code>username</code> column. While this workload was running, I executed <code>SHOW ENGINE INNODB STATUS \G;</code>. This command provides information about what is going on with the InnoDB storage engine. You can inspect the <code>INSERT BUFFER AND ADAPTIVE HASH INDEX</code> section to see if and how much the adaptive hash index is being used.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">INSERT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> BUFFER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ADAPTIVE </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">HASH</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INDEX</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Ibuf: </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 11826</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, free list len </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">13206</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, seg </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 25033</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">8599</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> merges</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">merged operations:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> insert</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 52823</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mark </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">discarded operations:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> insert</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mark </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">Hash</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 276707</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, node heap has </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> buffer</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(s)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">...</span></span>
<span class="line"><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">00</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> hash</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> searches</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">s, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">418334</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">67</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> non</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-hash</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> searches</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">s</span></span>
<span class="line"></span></code></pre></div></div><p>The key line to observe here is the last one, which indicates that no hash lookups are occurring. This is expected, since we have disabled the feature.</p><p>Now after enabling the adaptive hash index and restarting the server, let&#x27;s try again:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">$</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> python3</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> same_query.py</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 500000</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">starting</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> query</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> load</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">completed</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> in</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 29.94</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> seconds</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">QPS</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 16701.1</span></span>
<span class="line"></span></code></pre></div></div><p>We achieved about a 16% speed up. This is a small, but still quite useful performance boost. B-tree index lookups are already very fast, but we have layered an additional optimization on top to make it even faster. I grabbed information about the buffer pool while this ran, and this shows that the AHI was being used:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">INSERT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> BUFFER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ADAPTIVE </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">HASH</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INDEX</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Ibuf: </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 11445</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, free list len </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">13587</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, seg </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 25033</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1881</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> merges</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">merged operations:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> insert</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 11507</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mark </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">discarded operations:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> insert</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mark </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">Hash</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> size</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 276707</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, node heap has </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> buffer</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(s)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">...</span></span>
<span class="line"><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">350953</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">05</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> hash</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> searches</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">s, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">50985</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">01</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> non</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-hash</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> searches</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">s</span></span>
<span class="line"></span></code></pre></div></div><p>Now, many hash searches are occurring. There are still some non-hash searches happening as well, which makes sense since the query still needs to access the actual data in the row, not just the index value.</p><p>Let&#x27;s try another workload. This time, we&#x27;ll re-use the same query, except instead of always searching for the same username, on each execution we&#x27;ll randomly select one username from a pool of one thousand. Though this specific workload is unrealistic, this can be thought of as a database and workload with a large amount of cold data, and a small amount of hot data.</p><p>Without the adaptive hash index disabled, we get:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">$ python3 </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">load</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">py</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 500000</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">starting query </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">workload</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">completed </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 54</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">16</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> seconds</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">QPS </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 9231</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">62</span></span>
<span class="line"></span></code></pre></div></div><p>With it enabled, we instead see:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">$ python3 </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">load</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">py</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 500000</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">starting query </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">workload</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">completed </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 43</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">24</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> seconds</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">QPS </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 11562</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">05</span></span>
<span class="line"></span></code></pre></div></div><p>In this workload, we got a 20% performance improvement.</p><p>These tests were executed on on a table with over 390 million rows:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>mysql> SELECT count(*) FROM user;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>+-----------+</span></span>
<span class="line"><span>| count(*)  |</span></span>
<span class="line"><span>+-----------+</span></span>
<span class="line"><span>| 398748007 |</span></span>
<span class="line"><span>+-----------+</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Even with such a large data set, the B-tree index for the <code>username</code> column was only 4 levels deep (this was checked with the help of <a href="https://github.com/jeremycole/innodb_ruby">innodb_ruby</a>). Running these types of workloads on tables with smaller indexes, and therefore shorter B-tree indexes, may result in less noticeable speedup. On the other hand, workloads using deeper B-tree indexes may see even more performance improvement.</p><h2 id="conclusion"><a href="https://planetscale.com/blog/the-mysql-adaptive-hash-index#conclusion">Conclusion</a></h2><p>When dealing with large, multi-terabytes databases and workloads with hundreds of thousands of queries per second, even small improvements like this can have a big impact on query latency and database server infrastructure needs. The adaptive hash index provides such improvements for already-fast B-tree index lookups, helping certain types of workloads get a boost in performance. Whether or not the AHI will help your workload depends heavily on data access patterns and the size of your InnoDB buffer pool.</p>]]></content>
        <summary><![CDATA[The adaptive hash index helps to improve performance of the already-fast B-tree lookups]]></summary>
      </entry>
    
      <entry>
        <title>Profiling memory usage in MySQL</title>
        <link href="https://planetscale.com/blog/profiling-memory-usage-in-mysql" />
        <id>https://planetscale.com/blog/profiling-memory-usage-in-mysql</id>
        <published>2024-04-11T00:00:00.000Z</published>
        <updated>2024-04-11T00:00:00.000Z</updated>
        
        <author>
          <name>Ben Dicken</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>When considering the performance of any software, there&#x27;s a classic trade-off between time and space. In the process of assessing the performance of a MySQL query, we often focus on execution time (or query latency) and use it as the primary metric for query performance. This is a good metric to use, as ultimately, we want to get query results as quickly as possible.</p><p>I recently released a blog post about how to <a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries">identify and profile problematic MySQL queries</a>, with a discussion centered around measuring poor performance in terms of execution time and row reads. However, in this discussion, memory consumption was largely ignored.</p><p>Though it may not be needed as often, MySQL also has built-in mechanisms for gaining a deep understanding of both how much memory a query is using and also what that memory is being used for. Let&#x27;s take a deep dive through this functionality and see how we can perform live monitoring of the memory usage of a MySQL connection.</p><h2 id="memory-statistics"><a href="https://planetscale.com/blog/profiling-memory-usage-in-mysql#memory-statistics">Memory statistics</a></h2><p>In MySQL, there are many components of the system that can be individually instrumented. The <code>performance_schema.setup_instruments</code> table lists each of these components, and there are quite a few:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> count</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">count</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"></span></code></pre></div></div><p>Included in this table are a number of instruments that can be used for memory profiling. To see what is available, try selecting from the table and filtering by <code>memory/</code>.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, documentation</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  WHERE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'memory/%'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>You should see several-hundred results. Each of these represent a different category of memory that can be individually instrumented in MySQL. Some of these categories contain a short bit of <code>documentation</code> describing what this memory category represents or is used for. If you&#x27;d like to see only memory types that have a non-null <code>documentation</code> value, you can run:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, documentation</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  WHERE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'memory/%'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> documentation </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">IS NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Each of these memory categories can be sampled at several different granularities. The various levels of granularity are stored across several tables:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_name</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> information_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">tables</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_name </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '%memory_summary%'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_schema </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'performance_schema'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-----------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| TABLE_NAME                              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-----------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory_summary_by_account_by_event_name |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory_summary_by_host_by_event_name    |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory_summary_by_thread_by_event_name  |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory_summary_by_user_by_event_name    |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory_summary_global_by_event_name     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-----------------------------------------+</span></span>
<span class="line"></span></code></pre></div></div><ul><li><code>memory_summary_by_account_by_event_name</code> Summarizes memory events based on accounts (An account is a combination of a user and host)</li><li><code>memory_summary_by_host_by_event_name</code> Summarizes memory events at a host granularity</li><li><code>memory_summary_by_thread_by_event_name</code> Summarizes memory events at a MySQL thread granularity</li><li><code>memory_summary_by_user_by_event_name</code> Summarizes memory events at a user granularity</li><li><code>memory_summary_global_by_event_name</code> A global summary of memory statistics</li></ul><p>Notice that there is no specific tracking for memory usage at a per-query level. However, this does not mean we cannot profile the memory usage of a query! To accomplish this, we can monitor the usage of memory on whatever connection the query of interest is being executed on. Because of this, we&#x27;ll focus our use on the <code>memory_summary_by_thread_by_event_name</code> table, as there is a convenient mapping between a MySQL connection and a thread.</p><h2 id="finding-usage-for-a-connection"><a href="https://planetscale.com/blog/profiling-memory-usage-in-mysql#finding-usage-for-a-connection">Finding usage for a connection</a></h2><p>At this point, you should set up two separate connections to your MySQL server on the command line. The first is the one that will execute the query you want to monitor memory usage for. The second will be used for monitoring purposes.</p><p>On the first connection, run these queries to get your connection ID and thread ID.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SET</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> @cid </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> CONNECTION_ID</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SET</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> @tid </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> thread_id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">threads</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> PROCESSLIST_ID</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">@cid);</span></span>
<span class="line"></span></code></pre></div></div><p>Then grab these values. Of course, yours will likely look different than what you see here.</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SELECT @cid, @tid;</span></span>
<span class="line"><span></span></span>
<span class="line"><span>+------+------+</span></span>
<span class="line"><span>| @cid | @tid |</span></span>
<span class="line"><span>+------+------+</span></span>
<span class="line"><span>|   49 |   89 |</span></span>
<span class="line"><span>+------+------+</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Next up, execute some long-running query you&#x27;d like to profile the memory usage for. For this example, I&#x27;ll do a large <code>SELECT</code> from a table that has 100 million rows in it, which should take awhile since there is no index on the <code>alias</code> column:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> alias </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> chat</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">message</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ORDER BY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> alias </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DESC</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 100000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Now, while this is executing, switch over to your other console connection and run the following, replacing the thread ID with the one from your connection:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    event_name,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    current_number_of_bytes_used</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">memory_summary_by_thread_by_event_name</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> thread_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> YOUR_THREAD_ID</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ORDER BY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> current_number_of_bytes_used </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DESC</span></span>
<span class="line"></span></code></pre></div></div><p>You should see results along the lines of this, though the details will depend highly on your query and data:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>+---------------------------------------+------------------------------+</span></span>
<span class="line"><span>| event_name                            | current_number_of_bytes_used |</span></span>
<span class="line"><span>+---------------------------------------+------------------------------+</span></span>
<span class="line"><span>| memory/sql/Filesort_buffer::sort_keys | 203488                       |</span></span>
<span class="line"><span>| memory/innodb/memory                  | 169800                       |</span></span>
<span class="line"><span>| memory/sql/THD::main_mem_root         | 46176                        |</span></span>
<span class="line"><span>| memory/innodb/ha_innodb               | 35936                        |</span></span>
<span class="line"><span>...</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>This indicates the amount of memory for each category being used at the exact moment this query was executed. If you run this query several times while the other <code>SELECT alias...</code> query is executing, you may see differences in the results, as memory usage for a query is not necessarily constant over its whole execution. Each execution of this query represents a sample at a moment of time. Thus, if we want to see how the usage changes over time, we&#x27;ll need to take many samples.</p><p>The documentation for <code>memory/sql/Filesort_buffer::sort_keys</code> is missing from the <code>performance_schema.setup_instruments</code> table.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, documentation</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> name</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'memory%sort_keys'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------------------------------+---------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">name</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                                  | documentation |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------------------------------+---------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| memory</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Filesort_buffer::sort_keys | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;null></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------------------------------+---------------+</span></span>
<span class="line"></span></code></pre></div></div><p>However, the name indicates that it is memory being used for sorting data from a file. This makes sense as a large part of the expense of this query would be sorting the data so that is can be displayed in descending order.</p><h2 id="collecting-usage-over-time"><a href="https://planetscale.com/blog/profiling-memory-usage-in-mysql#collecting-usage-over-time">Collecting usage over time</a></h2><p>As a next step, we need to be able to sample this memory usage over time. For short queries this will not be as useful, as we&#x27;ll only be able to execute this query once, or a small number of times while the profiled query is executing. This will be more useful for longer-running queries, ones that take multiple seconds or minutes. These, would be the types of queries we&#x27;d want to profile anyways, as these are the ones likely to use a large portion of memory.</p><p>This could be implemented fully in SQL and invoked via a stored procedure. However, in this case, let&#x27;s use a separate script in Python to provide monitoring.</p><div class="code-block" data-language="python"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">#!/usr/bin/env python3</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> time</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> MySQLdb</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> argparse</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">MEM_QUERY</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'''</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">SELECT event_name, current_number_of_bytes_used</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  FROM performance_schema.memory_summary_by_thread_by_event_name</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  WHERE thread_id = </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">%s</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  ORDER BY current_number_of_bytes_used DESC LIMIT 4</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'''</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">parser </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> argparse</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ArgumentParser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">parser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">add_argument</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'--thread-id'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> required</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">True</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">args </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">parse_args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">dbc </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> MySQLdb</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">connect</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">host</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'127.0.0.1'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> user</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'root'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> password</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'password'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> dbc</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cursor</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ms </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">while</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">True</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">execute</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">MEM_QUERY</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">thread_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,))</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    results </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">fetchall</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">    print</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">f</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">\n</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">## Memory usage at time </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">{</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ms</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">}</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> ##'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> r </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">        print</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">f</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">{</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">r</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">][</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">7</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:]</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">}</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> -> </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">{</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">round</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">r</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1024</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">}</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">Kb'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    ms</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">250</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    time</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">sleep</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0.25</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"></span></code></pre></div></div><p>This is a simple, first stab at such a monitoring script. In summary, this code does the following:</p><ul><li>Get the provided thread ID to monitor via command line</li><li>Set up a connection to a MySQL database</li><li>Every 250 milliseconds, execute a query to get the top 4 used memory categories and print a readout</li></ul><p>This could be adjusted in many ways depending on your profiling needs. For example, tweaking the frequency of the ping to the server or changing how many memory categories are listed per iteration. Running this while a query is executing provides results like this:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>...</span></span>
<span class="line"><span>## Memory usage at time 4250 ##</span></span>
<span class="line"><span>innodb/row0sel -> 25.22Kb</span></span>
<span class="line"><span>sql/String::value -> 16.07Kb</span></span>
<span class="line"><span>sql/user_var_entry -> 0.41Kb</span></span>
<span class="line"><span>innodb/memory -> 0.23Kb</span></span>
<span class="line"><span></span></span>
<span class="line"><span>## Memory usage at time 4500 ##</span></span>
<span class="line"><span>innodb/row0sel -> 25.22Kb</span></span>
<span class="line"><span>sql/String::value -> 16.07Kb</span></span>
<span class="line"><span>sql/user_var_entry -> 0.41Kb</span></span>
<span class="line"><span>innodb/memory -> 0.23Kb</span></span>
<span class="line"><span></span></span>
<span class="line"><span>## Memory usage at time 4750 ##</span></span>
<span class="line"><span>innodb/row0sel -> 25.22Kb</span></span>
<span class="line"><span>sql/String::value -> 16.07Kb</span></span>
<span class="line"><span>sql/user_var_entry -> 0.41Kb</span></span>
<span class="line"><span>innodb/memory -> 0.23Kb</span></span>
<span class="line"><span></span></span>
<span class="line"><span>## Memory usage at time 5000 ##</span></span>
<span class="line"><span>innodb/row0sel -> 25.22Kb</span></span>
<span class="line"><span>sql/String::value -> 16.07Kb</span></span>
<span class="line"><span>sql/user_var_entry -> 0.41Kb</span></span>
<span class="line"><span>innodb/memory -> 0.23Kb</span></span>
<span class="line"><span>...</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>This is great, but there&#x27;s a few weaknesses. It would be nice to see more than the top 4 memory usage categories, but increasing that numbers increases the size of this already-large output dump. It would also be nice to have an easier way to get a picture of the memory usage at-a-glance via some visualizations. This could be done by having the script dump the results to a CSV or JSON, and then loading them up later in a visualization tool. Even better, we could plot the results we are getting live, as the data is streaming in. This provides a more up-to-date view, and allows us to observe the memory usage live as it is happening, all in one tool.</p><h2 id="plotting-memory-usage"><a href="https://planetscale.com/blog/profiling-memory-usage-in-mysql#plotting-memory-usage">Plotting memory usage</a></h2><p>In order make this tool even more useful and provide visualizations, a few changes are going to be made.</p><ul><li>The user will provide connection ID on the command line, and the script will be responsible for finding the underlying thread.</li><li>The frequency at which the script requests memory data will be configurable, also via the command line.</li><li>The <code>matplotlib</code> library will be used to generate a visualization of the memory usage. This will consist of a stack plot with a legend showing the top memory usage categories, and will retain the past 50 samples.</li></ul><p>It&#x27;s quite a bit of code, but is included here for the sake of completeness.</p><div class="code-block" data-language="python"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">#!/usr/bin/env python3</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> matplotlib</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">pyplot </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">as</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> plt</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> numpy </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">as</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> np</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> MySQLdb</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">import</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> argparse</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">MEM_QUERY</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'''</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">SELECT event_name, current_number_of_bytes_used</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  FROM performance_schema.memory_summary_by_thread_by_event_name</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  WHERE thread_id = </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">%s</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  ORDER BY event_name DESC'''</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">TID_QUERY</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'''</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">SELECT  thread_id</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  FROM performance_schema.threads</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">  WHERE PROCESSLIST_ID=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">%s</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'''</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">class</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> MemoryProfiler</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> __init__</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">x </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> []</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">y </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> []</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">mem_labels </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> [</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'XXXXXXXXXXXXXXXXXXXXXXX'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ms </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">color_sequence </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> [</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'#ffc59b'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#d4c9fe'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#a9dffe'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#a9ecb8'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">                               '#fff1a8'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#fbbfc7'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#fd812d'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#a18bf5'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">                               '#47b7f8'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#40d763'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#f2b600'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '#ff7082'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">rcParams</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'axes.xmargin'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">rcParams</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'axes.ymargin'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">rcParams</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"font.family"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "inter"</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> update_xy_axis</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> frequency</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ms </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> frequency</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">x</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">append</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ms</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        if</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ==</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">y </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> [[]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> x </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> range</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">))]</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> i </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> range</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> -</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> -</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            usage </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> float</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">][</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">])</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> /</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1024</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">].</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">append</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">usage</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        if</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">x</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 50</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">x</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">pop</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> i </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> range</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)):</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">                self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">].</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">pop</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> update_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        total_mem </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> sum</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">map</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">lambda</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> e</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> e</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">],</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">))</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">        self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">mem_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">clear</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> i </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> range</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">len</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> -</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> -</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            usage </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> float</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">][</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">])</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> /</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1024</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            mem_type </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">i</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">][</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">            # Remove 'memory/' from beginning of name for brevity</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            mem_type </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mem_type</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">7</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:]</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">            # Only show top memory users in legend</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            if</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">usage </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> total_mem </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1024</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> /</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 50</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                mem_type </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '_'</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> +</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> mem_type</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">mem_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">insert</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> mem_type</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> draw_plot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">clf</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">stackplot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">x</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> colors</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">color_sequence</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">legend</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">labels</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">mem_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> bbox_to_anchor</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1.04</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">),</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> loc</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"upper left"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> borderaxespad</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">xlabel</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"milliseconds since monitor began"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ylabel</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"Kilobytes of memory"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> configure_plot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ion</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        fig </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">figure</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">figsize</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">12</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">))</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">stackplot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">x</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">y</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> colors</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">color_sequence</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">legend</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">labels</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">mem_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> bbox_to_anchor</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1.04</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">),</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> loc</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">"upper left"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> borderaxespad</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">tight_layout</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">pad</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        return</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> fig</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> start_visualization</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> frequency</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cursor</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span><span style="--shiki-light:#C11027;--shiki-dark:#FF909F">;</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        fig </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">configure_plot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        while</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">True</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">execute</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">MEM_QUERY</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,))</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            results </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">fetchall</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">update_xy_axis</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> frequency</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">update_labels</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">results</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">            self</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">draw_plot</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">plt</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            fig</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">canvas</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">draw_idle</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            fig</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">canvas</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">start_event_loop</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">frequency </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1000</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> get_command_line_args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">():</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    '''</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    Process arguments and return argparse object to caller.</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    '''</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    parser </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> argparse</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">ArgumentParser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">description</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'Monitor MySQL query memory for a particular connection.'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    parser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">add_argument</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'--connection-id'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> required</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">True</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">                        help</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'The MySQL connection to monitor memory usage of'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    parser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">add_argument</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'--frequency'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> type</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=float</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> default</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">500</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span></span>
<span class="line"><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">                        help</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'The frequency at which to ping for memory usage update in milliseconds'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    return</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parser</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">parse_args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> get_thread_for_connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> cid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">):</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    '''</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    Get a thread ID corresponding to the connection ID</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    PARAMS</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">      database_connection - Database connection object</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">      cid - The connection ID to find the thread for</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    '''</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    c </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cursor</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">execute</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">TID_QUERY</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> (</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">cid</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,))</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    result </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> c</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">fetchone</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    return</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">result</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">])</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">def</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> main</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">():</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    args </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> get_command_line_args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    database_connection </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> MySQLdb</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">connect</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600">host</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'127.0.0.1'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> user</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'root'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#A78103;--shiki-dark:#F2B600"> password</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'password'</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    connection_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> get_thread_for_connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    m </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> MemoryProfiler</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    m</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">start_visualization</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">(</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">database_connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> connection_id</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">,</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> args</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">frequency</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    connection</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">.</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">close</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">if</span><span style="--shiki-light:#144EB6;--shiki-dark:#47B7F8"> __name__</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ==</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "__main__"</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">:</span></span>
<span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">    main</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">()</span></span>
<span class="line"></span>
<span class="line"></span></code></pre></div></div><p>With this, we can do detailed monitoring of executing MySQL queries. To use it, first get the connection ID for the connection you want to profile:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SELECT CONNECTION_ID();</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Then, executing the following will begin a monitoring session:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">./monitor.py</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-connection-id</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> YOUR_CONNECTION_ID</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-frequency</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 250</span></span>
<span class="line"></span></code></pre></div></div><p>When executing a query on the database, we can observe the increase in memory usage, and see what categories of memory are the largest contributors.</p><p><picture class="block"><img alt="Memory profile visualization example" src="https://planetscale.com/assets-2024/blog/content/profiling-memory-usage-in-mysql/visualization1.apng" loading="lazy"></picture></p><p>This visualization can also help us to clearly see what kinds of operations are memory hogs. For example, here is a snippet of a memory profile for creating a <code>FULLTEXT</code> index on a large table:</p><p><picture class="block"><img alt="Memory profile visualization example" src="https://planetscale.com/assets-2024/blog/content/profiling-memory-usage-in-mysql/visualization2.apng" loading="lazy"></picture></p><p>The memory usage is significant, and continues to grow into using hundreds of megabytes as it executes.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>For another example of how you can use MySQL to profile memory usage, see check out <a href="https://www.youtube.com/watch?v=hKGWdqlEt98">this DBAMA presentation</a> and the corresponding <a href="https://github.com/nryeng/dbama-query-profiling/tree/master">GitHub repository</a>.</p></div><h2 id="conclusion"><a href="https://planetscale.com/blog/profiling-memory-usage-in-mysql#conclusion">Conclusion</a></h2><p>Though it may not be needed as often, having the ability to get detailed memory usage information can be extremely valuable when the need for detailed query optimization arises. Doing this can reveal when and why MySQL may be cause memory pressure on the system, or if a memory upgrade for your database server may be needed. MySQL provides a number of primitives that you can build upon to develop profiling tooling for your queries and workload.</p>]]></content>
        <summary><![CDATA[Learn how to visualize the memory usage of a MySQL connection]]></summary>
      </entry>
    
      <entry>
        <title>Summer 2023: Fuzzing Vitess at PlanetScale</title>
        <link href="https://planetscale.com/blog/summer-2023-fuzzing-vitess-at-planetscale" />
        <id>https://planetscale.com/blog/summer-2023-fuzzing-vitess-at-planetscale</id>
        <published>2024-04-09T00:00:00.000Z</published>
        <updated>2024-04-09T00:00:00.000Z</updated>
        
        <author>
          <name>Arvind Murty</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>My name is Arvind Murty, and from May to July of 2023, I worked on <a href="https://vitess.io/">Vitess</a> via an internship with PlanetScale.</p><p>I was first introduced to Vitess when I was in high school as a potential open-source project for me to work on. I had been interested in working on one because they’re a relatively easy way to get some real-world experience in large-scale software development. Vitess seemed like an good place to start, so I started contributing, mostly on internal cleanup. I had been doing this on and off until the spring of 2023, when Andrés Taylor approached me about doing an internship under his guidance. Needless to say, I agreed.</p><h2 id="my-focus-at-planetscale"><a href="https://planetscale.com/blog/summer-2023-fuzzing-vitess-at-planetscale#my-focus-at-planetscale">My focus at PlanetScale</a></h2><p>When I started in mid-May, Andrés gave me my instructions: find as many bugs in the Vitess planner as possible.</p><p>We first looked into a tool called <a href="https://github.com/sqlancer/sqlancer">SQLancer</a>. From its README:</p><blockquote><p>SQLancer (Synthesized Query Lancer) is a tool to automatically test Database Management Systems (DBMSs) in order to find logic bugs in their implementation. We refer to logic bugs as those bugs that cause the DBMS to fetch an incorrect result set (e.g., by omitting a record).</p></blockquote><p>SQLancer had been very successful at finding bugs in well-established DBMSs, such as SQLite and MySQL, so we thought it might work well for Vitess. But there were three main problems:</p><ul><li>Vitess ideally should perfectly mimic MySQL, quirks included. SQLancer on the other hand compares queries to an oracle, which determines if queries are logically correct.</li><li>Vitess has the added layer of the <a href="https://vitess.io/docs/reference/features/vschema/">VSchema</a>. The VSchema has many added considerations, such as sharding keys, which changes how Vitess plans the query.</li><li>It would take a lot of work to properly integrate Vitess with SQLancer, due to each DBMS tester in SQLancer essentially being written completely separately with similar logic.</li></ul><h2 id="vitess-planner-bug-hunting-strategy"><a href="https://planetscale.com/blog/summer-2023-fuzzing-vitess-at-planetscale#vitess-planner-bug-hunting-strategy">Vitess planner bug hunting strategy</a></h2><p>We decided to go for the low-hanging fruit and build our own random query generator. Which turned out to not be that low-hanging since it yielded a bunch of failing queries. Andrés had already made a quick random query fuzzer that tested queries with aggregation, <code>GROUP BY</code>, <code>ORDER BY</code>, and <code>LIMIT</code>, so I started to build off of it in <a href="https://github.com/vitessio/vitess/pull/13260">this PR</a>. From a given set of tables, the fuzzer randomly selects a multiset of the tables, then chooses a random multiset of columns to provide to the clauses (<code>SELECT</code>, <code>GROUP BY</code>, <code>WHERE</code>, etc.) and the random expression generator. Once the query is generated, it’s run on both Vitess and MySQL, and the results and errors are compared. If there is a mismatch, it is reported.</p><p>Adding most types of queries was pretty straightforward (for example, for derived tables, generate a query q, then generate another query with q as a table), but there were two functionalities that were more complicated: random expressions and query simplification. Andrés had already built both of these, but for our purposes, they needed to be modified.</p><p>The <a href="https://systay.github.io/2022/01/06/automatically-simplifying-queries.html">query simplifier</a> is a tool used to automatically simplify queries that produce errors. It uses a brute-force approach, removing or modifying nodes in the AST and checking if the new, simpler query still exhibits the same error. If it does, the simplifier is called on the new query. However, it was not originally intended to be used for end-to-end tests, so we had to figure out how to make it work — specifically, how to supply the VSchema information. After that, I made some minor improvements to the simplifier and refactored it in <a href="https://github.com/vitessio/vitess/pull/13636">this PR</a>.</p><p>The original random expression generator only generated random literal expressions, so the first step was to add columns. This was fairly simple for tables I knew the schema for, but became more complicated once I added derived tables and wanted to randomly choose columns from them.</p><p>The other improvement I made was to add aggregation to the expressions. Because aggregation can only exist in the <code>SELECT</code> statement or the <code>GROUP BY</code>, <code>ORDER BY</code>, and <code>HAVING</code> clauses, I had to make sure the generator only produced aggregations for the statements and clauses in which they are allowed.</p><h2 id="conclusion"><a href="https://planetscale.com/blog/summer-2023-fuzzing-vitess-at-planetscale#conclusion">Conclusion</a></h2><p>The fuzzer can always be improved, and I think the first step that should be taken is complicating or randomizing the schema and VSchema. All of the queries currently run on the widely-used <code>EMP</code> (employee) and <code>DEPT</code> (department) tables using a standard sharding based on <code>EMPNO</code> (employee number) and <code>DEPTNO</code> (department number), respectively. The other main improvement would be to clean up the code; currently, there is a flag <code>testFailingQueries</code> that prevents certain types of queries that were known to fail from being generated. With the query planner being improved since I completed my work on the fuzzer, this flag can either be deleted altogether, or at the very least be removed from many spots.</p><p>My experience on Vitess at PlanetScale, while short, was instructive in more ways than one. Not only did I get to make some meaningful contributions, but I also learned how software development as a team works. For those two and a half months I was essentially a temporary member of the query serving team. And while I mainly worked with Andrés, I participated in the daily stand-ups and occasionally worked with the other members, for which I’d like to thank Harshit, Florent, and Manan. And of course thank you to Andrés for spearheading this project and mentoring me along the way.</p>]]></content>
        <summary><![CDATA[My experience working as an intern in the Vitess query serving team for PlanetScale.]]></summary>
      </entry>
    
      <entry>
        <title>Identifying and profiling problematic MySQL queries</title>
        <link href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries" />
        <id>https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries</id>
        <published>2024-03-29T00:00:00.000Z</published>
        <updated>2024-03-29T00:00:00.000Z</updated>
        
        <author>
          <name>Ben Dicken</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Though we try our best to avoid it, it&#x27;s easy to let underperforming queries slip through the cracks in our workloads, negatively impacting the performance of a database system. This is especially true in large-scale database environments, with many gigabytes or terabytes of data, hundreds of tables, and thousands of query patterns being executed on a daily basis.</p><p>Thankfully, MySQL has the ability to collect data that can be leveraged for identifying problematic queries, and can also do profiling on them in order to drill into their poor performance. In this article, I&#x27;ll go over several built-in techniques for how to do this in native MySQL. If you use PlanetScale, this type of information can be gathered more easily and intuitively using the <a href="https://planetscale.com/docs/vitess/monitoring/query-insights">PlanetScale Insights</a> dashboard. I&#x27;ll include a brief discussion of this feature later on.</p><p>For this article, I&#x27;ll be using the following schema as an example.</p><p><picture class="block"><img alt="The schema that will be used for the sample workload" src="https://planetscale.com/assets-2024/blog/content/identifying-and-profiling-problematic-mysql-queries/schema.png" loading="lazy"></picture></p><p>A fake workload has been run on this database, and later on you&#x27;ll see some queries that operate on these tables.</p><h2 id="enable-performance_schema"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#enable-performance_schema">Enable performance_schema</a></h2><p>If you have a large application that executes numerous queries on the database, it can be overwhelming to know where to start looking for problems. How do you know which queries are under-performing? One way to determine this would be to execute your queries manually in a shell session and examine the resulting timing. You could also use your web application in a browser to see which page loads and requests appear to be running slowly. However, there are more principled ways to identify these queries.</p><p>You can use information from the tables in <code>performance_schema</code> to help us identify these. To use this, first switch over to this database.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> performance_schema;</span></span>
<span class="line"></span></code></pre></div></div><p>You also should check to ensure that <code>performance_schema</code> is enabled. It should be enabled by default, but it can be manually turned off or disabled if the host MySQL is running on has insufficient memory, as all of the information it tracks is stored in an in-memory <code>PERFORMANCE_SCHEMA</code> storage engine. If it is enabled, you should see the following:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">SHOW VARIABLES </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'performance_schema'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------+-------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| Variable_name      | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">Value</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------+-------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| performance_schema | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------+-------+</span></span>
<span class="line"></span></code></pre></div></div><h2 id="identifying-slow-queries"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#identifying-slow-queries">Identifying slow queries</a></h2><p>The next step is to figure out what queries to focus our efforts on fixing. There are a few techniques you can use to identify these using the tables in <code>performance_schema</code> and <code>sys</code>. There are a LOT of tables in the <code>performance_schema</code> database:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> COUNT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> information_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">tables</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> table_schema </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'performance_schema'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">COUNT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">113</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">      |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------+</span></span>
<span class="line"></span></code></pre></div></div><p><em>(Your number may be different depending on your version of MySQL).</em></p><p>A good place to start is looking at the information provided by the <code>events_statements_summary_by_digest</code> table. Let&#x27;s take a look at one row for starters, the one with the highest average execution count:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">events_statements_summary_by_digest</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> schema_name </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'game'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    ORDER BY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> avg_timer_wait</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    DESC</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> \G;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">***************************</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">[ 1. row ]</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">***************************</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">SCHEMA_NAME                 | game</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DIGEST</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                      | 8ee55a42fea07da3d5e786413a6c1bd...</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">DIGEST_TEXT                 | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `p1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> . </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`username`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> , </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`m`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ...</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">COUNT_STAR                  | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2486</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">SUM_TIMER_WAIT              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">425862156000000</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">MIN_TIMER_WAIT              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">149411000000</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">AVG_TIMER_WAIT              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">171304165000</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">MAX_TIMER_WAIT              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">380467000000</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">SUM_LOCK_TIME               | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1306000000</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">...</span></span>
<span class="line"></span></code></pre></div></div><p>There are quite a few columns in each row. However, the columns such as <code>SUM_TIMER_WAIT</code> and <code>AVG_TIMER_WAIT</code> are good to look at if you want to see your most expensive queries. The times shown here are given in picoseconds, and thus need to be divided by 1 trillion to convert to seconds. In this case, the <code>AVG_TIMER_WAIT</code> (the average time for this query) is .17 seconds, and this query was only executed <code>7580</code> times (see the <code>COUNT_STAR</code> value in the result set above).</p><p>There are also a bunch of tables in the <code>sys</code> database that keep useful stats for identifying slow queries. You can check out the info in tables like <code>statements_with_sorting</code>, <code>statements_with_runtimes_in_95th_percentile</code>, and <code>statements_with_full_table_scans</code>. As an example of this, let&#x27;s examine the queries with the highest average number of rows examined from the <code>statements_with_runtimes_in_95th_percentile</code>:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> substring</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(query,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">50</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), avg_latency, rows_examined_avg</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> sys</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">statements_with_runtimes_in_95th_percentile</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  ORDER BY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> rows_examined_avg</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  DESC</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------------------------------------------------+-------------+-------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">substring</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(query,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">50</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)                              | avg_latency | rows_examined_avg |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------------------------------------------------+-------------+-------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `alias`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `chat`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> . </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`message`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ?     | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">13</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> s      | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10000000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">          |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `alias`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `message`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ?              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">881</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">20</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms   | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2500004</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">           |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| EXPLAIN ANALYZE </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `p1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> .  ... </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` WHERE `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">m</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` . `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">223</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">97</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms   | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1081990</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">           |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `p1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> . </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`username`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> , </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`m`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ... </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` WHERE `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">m</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` . `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">57</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">75</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">421446</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `message`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIMIT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ?                    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">70</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">52</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">125125</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| EXPLAIN ANALYZE </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> FR ... E </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`created_at`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">56</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">55</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">100000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `message`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> WHERE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `created_at`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> BETW | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">58</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">28</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">100000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">            |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> SUBSTRING</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ( QUERY , ?,  ... s_examined_avg</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` | 76.72 ms    | 1498              |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">| SELECT SUBSTRING ( QUERY , ?,  ... `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">rows_examined</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` | 75.59 ms    | 1495              |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">| SELECT SUBSTRING ( QUERY , ?,  ... ned`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AS</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> DECIMAL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">78</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">97</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ms    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1494</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">              |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----------------------------------------------------+-------------+-------------------+</span></span>
<span class="line"></span></code></pre></div></div><p>For example, you may also be interested in taking a look at things from the perspective of a table instead of a query. You can look at stats on a per-table basis for how many row reads are being served via indexes versus without. Say you have a <code>chat</code> database with a <code>message</code> table that you know is heavily used. You can find these stats using the <code>table_io_waits_summary_by_index_usage</code> table.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `OBJECT_SCHEMA`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`OBJECT_NAME`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`INDEX_NAME`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`COUNT_STAR`</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">table_io_waits_summary_by_index_usage</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> object_schema </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'game'</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> object_name </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'message'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------+-------------+------------+------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | COUNT_STAR |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------+-------------+------------+------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| game          | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">message</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">PRIMARY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">          |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| game          | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">message</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | to_id      | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">          |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| game          | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">message</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | from_id    | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">164500</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| game          | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">message</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;null></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2574002473</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">---------------+-------------+------------+------------+</span></span>
<span class="line"></span></code></pre></div></div><p>This indicated that there have been over 2 billion row reads that were not fulfilled by an index (the row where <code>INDEX_NAME</code> is <code>&lt;null&gt;</code>). Either this table needs one or more indexes added to it, or the queries using this table need to be updated, or both!</p><p>There&#x27;s also a bunch of cool stats you can look at over in the <code>sys</code> table. Here&#x27;s one quick example. You can grab data on how many full table scans are being executed by each query using the <code>sys.statements_with_full_table_scans</code> table.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">USE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> sys;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> query, db, exec_count, total_latency</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> sys</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">statements_with_full_table_scans</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    ORDER BY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> exec_count </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DESC</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    LIMIT</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------------------------------------+------+------------+---------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| query                                                             | db   | exec_count | total_latency |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-------------------------------------------------------------------+------+------------+---------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `class`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> , </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`size`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `spaceship`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> WHERE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `size`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ?         | game | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">8422</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">26</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">65</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> s       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `p1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> . </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`username`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> , </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`m`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ... </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` WHERE `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">m</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` . `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">created_at</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` > ? | game | 6742       | 6.45 min      |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">| SELECT * FROM `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">earned_achievem ... id</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` AND `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ea</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` . `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">player_id</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` > ? | game | 6718       | 969.17 ms     |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">| SELECT * FROM `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">item</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` WHERE NAME LIKE ? LIMIT ?                    | game | 5676       | 969.43 ms     |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">| SELECT NAME , `</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">size</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` FROM `</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">planet</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` WHERE `</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">population</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">` > ?         | game | 5625       | 173.02 ms     |</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">+-------------------------------------------------------------------+------+------------+---------------+</span></span>
<span class="line"></span></code></pre></div></div><p>This shows which queries are triggering full table scans. In this case, there&#x27;s a ton, but this would be expected since there are no indexes on this example database other than the default ones on the primary keys.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>For more information about how to work with the <code>performance_schema</code> and <code>sys</code> tables, check out <a href="https://www.youtube.com/watch?v=hUHQ3q9TW3o&list=PLBrWqg4Ny6vXQZqsJ8qRLGRH9osEa45sw&index=10">this video</a>.</p></div><h2 id="inspecting-with-explain"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#inspecting-with-explain">Inspecting with EXPLAIN</a></h2><p>By this point, you&#x27;ve hopefully gathered a collection of queries that need to be further inspected. The next step is to do some root-cause analysis into why these queries are taking a long time and reading too many of rows. Of course, one great way to drill into the behavior of a query is with <code>EXPLAIN</code> or <code>EXPLAIN ANALYZE</code>. In this example, I&#x27;m going to use <code>EXPLAIN ANALYZE</code>.</p><p>For example, running the following query:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">EXPLAIN ANALYZE </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> p1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">username</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">to_id</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">p2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">username</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">from_id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    FROM</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> message</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> m</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    LEFT JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> player p1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">to_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> p1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    LEFT JOIN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> player p2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">from_id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> p2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">id</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    WHERE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">created_at</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '2020-10-10 00:00:00'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Gives detailed information regarding the query plan and costs of the various steps.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------------------------------------------------------------------------------------------------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| EXPLAIN                                                                                                                                    |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------------------------------------------------------------------------------------------------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> Nested </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">loop</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> left join</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">333272</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">332036</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">515</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">320</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">345454</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)                                           |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|     </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> Nested </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">loop</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> left join</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">217059</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">332036</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">475</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">240</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">345454</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)                                       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|         </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Filter</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">: (</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">created_at</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TIMESTAMP</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'2020-10-10 00:00:00'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">100846</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">332036</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">153</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">188</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">345454</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|             </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> Table</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> scan </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> m  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">100846</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">996208</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">148</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">159</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">1e</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)                                       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|         </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> Single</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-row</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> lookup </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> p1 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">using</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRIMARY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">to_id</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">25</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">54</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.5e</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">70</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.9e</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">345454</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|     </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-></span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> Single</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-row</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> lookup </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> p2 </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">using</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> PRIMARY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">m</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">from_id</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)  (cost</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">25</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) (actual </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">time=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">137e</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">..153e</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">6</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> rows=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> loops</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">345454</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">--------------------------------------------------------------------------------------------------------------------------------------------+</span></span>
<span class="line"></span></code></pre></div></div><p>There are a number of things to look at when inspecting this output. Generally, if you see table scans over large tables, this is something you should try to mitigate with an index. More broadly, any time you see a node with a high <code>cost</code> or <code>rows</code> value, this probably deserves further tuning. Perhaps the query can be re-written, or perhaps one or more indexes can help improve performance.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>For more information about <code>EXPLAIN</code>, see our <a href="https://planetscale.com/learn/courses/mysql-for-developers/queries/explain-overview">MySQL for Developers course</a> or our <a href="https://planetscale.com/blog/how-read-mysql-explains">How to read MySQL EXPLAINS</a> blog post.</p></div><h2 id="preparing-to-instrument-a-query"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#preparing-to-instrument-a-query">Preparing to instrument a query</a></h2><p>Explain is a great tool, but you can also profile a query to determine how much time it spends in each stage of execution. To do this, first ensure that the proper instruments and consumers are enabled so that information can be gathered appropriately. In order to do this, run the following:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    SET</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ENABLED</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, TIMED </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_consumers</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    SET</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ENABLED</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">TIMES</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>If you&#x27;d like, you can be more selective at this step. Rather than enabling this setting for all of instruments and consumers, you can enable subsets of the rows in these two tables. To enable it for the stages of query execution, you could run the following.</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  SET</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ENABLED</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'NO'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, TIMED </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'NO'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_instruments</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  SET</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ENABLED</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, TIMED </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'YES'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  WHERE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NAME</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> LIKE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '%stage/%'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Ultimately, it&#x27;s up to you based on what level of comfort you have with possible performance hits of profiling. If you&#x27;re not very concerned with the profiling overhead, just turn everything on and then disable later when you&#x27;re finished.</p><p>Next, ensure that history tracking is enabled. If you have not configured it before, you&#x27;ll probably see the following when you look at the <code>setup_actors</code> configuration:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_actors</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------+------+------+---------+---------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| HOST | USER | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ROLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ENABLED</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | HISTORY |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------+------+------+---------+---------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| %    | %    | %    | YES     | YES     |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------+------+------+---------+---------+</span></span>
<span class="line"></span></code></pre></div></div><p>If you want you can leave these settings as-is. However, this means that performance schema and history tracking will be enabled for all users. This could have a (small) adverse effect on the overall performance of your system. You can optionally enable it only for one specific user (this would be the user you need to run your test queries on). If you choose to go this route, turn it off globally:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">UPDATE</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_actors</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  SET</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ENABLED</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'NO'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, HISTORY </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'NO'</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> HOST </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '%'</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> AND</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> USER </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> '%'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>And then enable it only for the user(s) that you want to track for:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">INSERT INTO</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> performance_schema</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">setup_actors</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  (HOST, USER, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ROLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ENABLED</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, HISTORY)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  VALUES</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'your_host'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'your_user'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'%'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'YES'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><h2 id="profiling-the-query"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#profiling-the-query">Profiling the query</a></h2><p>Now, let&#x27;s profile one of our problematic query. First, get the ID of the connection you are going to run the query on.</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SET @connection_thread = (</span></span>
<span class="line"><span>  SELECT thread_id</span></span>
<span class="line"><span>    FROM performance_schema.threads</span></span>
<span class="line"><span>    WHERE PROCESSLIST_ID = CONNECTION_ID() );</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Next, execute the query you want to profile. Immediately after doing this, run the following query to determine the starting event ID of that execution:</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SELECT thread_id, statement_id, SUBSTRING(sql_text,1,50)</span></span>
<span class="line"><span>  FROM performance_schema.events_statements_history_long</span></span>
<span class="line"><span>  WHERE thread_id = @connection_thread</span></span>
<span class="line"><span>  ORDER BY event_id DESC LIMIT 20;</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Find the statement you want to profile and put its ID into a variable.</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SET @statement_id = ?;</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>Finally, run the following to see the profiling information for that query execution.</p><div class="code-block"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span>SET @eid = (SELECT event_id FROM performance_schema.events_statements_history_long WHERE statement_id = @statement_id);</span></span>
<span class="line"><span>SET @eeid = (SELECT end_event_id FROM performance_schema.events_statements_history_long WHERE statement_id = @statement_id);</span></span>
<span class="line"><span>SELECT event_name, source, (timer_end-timer_start)/1000000000 as 'milliseconds'</span></span>
<span class="line"><span>  FROM performance_schema.events_stages_history_long</span></span>
<span class="line"><span>  WHERE event_id BETWEEN @eid AND @eeid;</span></span>
<span class="line"><span></span></span></code></pre></div></div><p>This should provide a timing breakdown that looks something like this:</p><div class="code-block" data-language="SQL"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------------------------------------------+----------------------------------+--------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| event_name                                     | source                           | milliseconds |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------------------------------------------+----------------------------------+--------------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">starting                             | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">init_net_server_extension</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">110</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2400</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Executing hook </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> transaction</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> begin</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">. | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">rpl_handler</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1481</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0010</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">starting                             | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">rpl_handler</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1483</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">              | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0110</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">checking permissions                 | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_authorization</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2169</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0020</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">checking permissions                 | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_authorization</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2169</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">checking permissions                 | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_authorization</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2169</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0030</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">Opening tables                       | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_base</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5859</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                 | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0950</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/init</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                                 | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">759</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0050</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/System</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> lock                          | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">lock</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">331</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                      | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0110</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">optimizing                           | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_optimizer</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">355</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">             | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0270</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/statistics</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                           | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_optimizer</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">699</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">             | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1040</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">preparing                            | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_optimizer</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">783</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">             | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0590</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">executing                            | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_union</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1676</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">735</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3020</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">     |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/end</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                                  | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">795</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0010</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">query </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">end</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                            | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_parse</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4805</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0020</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">waiting </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">for</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> handler </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">           | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">handler</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1636</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                  | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0060</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">closing tables                       | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_parse</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4869</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0080</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">freeing items                        | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_parse</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5343</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2810</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| stage</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">/sql/</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">cleaning up                          | </span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">sql_parse</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">cc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">:</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2387</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                | </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0010</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">       |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">------------------------------------------------+----------------------------------+--------------+</span></span>
<span class="line"></span></code></pre></div></div><p>In this case, you can see that the query spent the majority of the time in the execution stage. However, this would also reveal if the query had spent a lot of time waiting on a lock or on optimizing, in which case you could dig further into the problem.</p><h2 id="planetscale-insights"><a href="https://planetscale.com/blog/identifying-and-profiling-problematic-mysql-queries#planetscale-insights">PlanetScale Insights</a></h2><p>As we&#x27;ve seen, MySQL provides a lot of capability to drill into problematic queries in your workload, and what we&#x27;ve discussed here is really only scratching the surface. However, it should also be clear that gleaning this information can be tedious. Getting exactly what you want requires significant poking around and digging through tables in <code>performance_schema</code> and <code>sys</code>.</p><p>Many of these same observations can be gathered much easier using <a href="https://planetscale.com/docs/vitess/monitoring/query-insights">PlanetScale Insights</a>. Insights provides a plethora of useful visualizations to help you get an overview of the performance of your database and automatic detection of anomalous behavior.</p><p><picture class="block"><img alt="PlanetScale Insights query latency graph" src="https://planetscale.com/assets-2024/blog/content/identifying-and-profiling-problematic-mysql-queries/insights1.png" loading="lazy"></picture></p><p>You can also use it to drill in on specific queries. For example, you can look at all queries executed over some window of time, and sort by different statistics such as rows read. This can help you quickly identify slow queries and ones where adding an index might be worthwhile.</p><p><picture class="block"><img alt="PlanetScale Insights query details" src="https://planetscale.com/assets-2024/blog/content/identifying-and-profiling-problematic-mysql-queries/insights2.png" loading="lazy"></picture></p><p>Insights allows you to gain a deep understanding of your workload. This gives you more time to focus on improving your queries, developing software, and working efficiently.</p>]]></content>
        <summary><![CDATA[MySQL has built-in functionality for collecting statistics on and profiling your MySQL queries. Learn how to leverage these features to identify problems.]]></summary>
      </entry>
    
      <entry>
        <title>The Problem with Using a UUID Primary Key in MySQL</title>
        <link href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql" />
        <id>https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql</id>
        <published>2024-03-19T09:00:00.000Z</published>
        <updated>2024-03-19T09:00:00.000Z</updated>
        
        <author>
          <name>Brian Morrison II</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Universally Unique Identifiers, also known as UUIDs, are designed to allow developers to generate unique IDs in a way that guarantees uniqueness without knowledge of other systems. These are especially useful in a distributed architecture, where you have a number of systems and databases responsible for creating records. You might think that using UUIDs as a primary key in a database is a great idea, but when used incorrectly, they can drastically hurt database performance.</p><p>In this article, you&#x27;ll learn about the downsides of using UUIDs as a primary key in your MySQL database.</p><h2 id="the-many-versions-of-uuids"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#the-many-versions-of-uuids">The many versions of UUIDs</a></h2><p>At the time of this writing, there are five official versions of UUIDs and three proposed versions. Let&#x27;s take a look at each version to better understand how they work.</p><h3 id="uuidv1"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv1">UUIDv1</a></h3><p>A UUID version 1 is known as a time-based UUID and can be broken down as follows:</p><p><picture class="block"><img alt="UUIDv1 structure" src="https://planetscale.com/assets-2024/blog/content/the-problem-with-using-a-uuid-primary-key-in-mysql/uuid-v1-diagram.png" loading="lazy"></picture></p><p>While much of modern computing uses the UNIX epoch time (Jan 1, 1970) as its base, UUIDs actually use a different date of Oct 10, 1568, which is the date that the Gregorian calendar started to be more widely used. The embedded timestamp within a UUID grows in 100 nanoseconds increments from this date, which is then used to set the <code>time_low</code>, <code>time_mid</code>, and <code>time_hi</code> segments of the UUID.</p><p>The third segment of the UUID contains the <code>version</code> as well as <code>time_hi</code> and occupies the first character of that segment. This is true for all versions of UUIDs as shown in subsequent examples. The <code>reserved</code> portion is also known as the <a href="https://datatracker.ietf.org/doc/html/rfc4122#section-4.1.1">variant</a> of the UUID, which determines how the bits within the UUID are used. Finally, the last segment of the UUID is the <code>node</code>, which is the unique address of the system generating the UUID.</p><h3 id="uuidv2"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv2">UUIDv2</a></h3><p>Version 2 of the UUID implemented a change compared to version 1, where the <code>low_time</code> segment of the structure was replaced with a POSIX user ID. The theory was that these UUIDs could be traced back to the user account that generated them. Since the <code>low_time</code> segment is where much of the variability of UUIDs reside, replacing this segment increases the chance of collision. As a result, this version of the UUID is rarely used.</p><h3 id="uuidv3-and-v5"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv3-and-v5">UUIDv3 and v5</a></h3><p>Versions 3 and 5 of UUIDs are very similar. The goal of these versions is to allow UUIDs to be generated in a deterministic way so that, given the same information, the same UUID can be generated. These implementations use two pieces of information: a namespace (which itself is a UUID) and a name. These values are run through a hashing algorithm to generate a 128-bit value that can be represented as a UUID.</p><p>The key difference between these versions is that version 3 uses an MD5 hashing algorithm, and version 5 uses SHA1.</p><h3 id="uuidv4"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv4">UUIDv4</a></h3><p>Version 4 is known as the random variant because, as the name implies, the value of the UUID is almost entirely random. The exception to this is the first position in the third segment of the UUID, which will always be <code>4</code> to signify the version used.</p><p><picture class="block"><img alt="UUIDv4 structure" src="https://planetscale.com/assets-2024/blog/content/the-problem-with-using-a-uuid-primary-key-in-mysql/uuid-v4-diagram.png" loading="lazy"></picture></p><h3 id="uuidv6"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv6">UUIDv6</a></h3><p>Version 6 is nearly identical to Version 1. The only difference is that the bits used to capture the timestamp are flipped, meaning the most significant portions of the timestamp are stored first. The graphic below demonstrates these differences.</p><p><picture class="block"><img alt="UUIDv6 structure" src="https://planetscale.com/assets-2024/blog/content/the-problem-with-using-a-uuid-primary-key-in-mysql/uuid-v6-diagram.png" loading="lazy"></picture></p><p>The main reason for this is to create a value that is compatible with Version 1 while allowing these values to be more sortable since the most significant portion of the timestamp is upfront.</p><h3 id="uuidv7"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv7">UUIDv7</a></h3><p>Version 7 is also a time-based UUID variant, but it integrates the more commonly used Unix Epoch timestamp instead of the Gregorian calendar date used by Version 1. The other key difference is that the node (the value based on the system generating the UUID) is replaced with randomness, making these UUIDs less trackable back to their source.</p><h3 id="uuidv8"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuidv8">UUIDv8</a></h3><p>Version 8 is the latest version that permits vendor-specific implementations while adhering to RFC standards. The only requirement for UUIDv8 is that the version be specified in the first position of the third segment as all other versions.</p><h2 id="uuids-and-mysql"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#uuids-and-mysql">UUIDs and MySQL</a></h2><p>Using UUIDs (mostly) guarantees uniqueness across all systems in your architecture, so you might be inclined to use them as primary keys for your records. Be aware that there are several tradeoffs to doing so when compared to an auto-incrementing integer.</p><h3 id="insert-performance"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#insert-performance">Insert performance</a></h3><p>Whenever a new record is inserted into a table in MySQL, the index associated with the primary key needs to be updated so querying the table is performant. Indexes in MySQL take the form of a B+ Tree, which is a multi-layered data structure that allows queries to quickly find the data they need.</p><p>The following diagram demonstrates what a relatively simple version of this structure looks like with six entries with values from 1 to 6. If a query comes asking for <code>5</code>, MySQL will start at the root node and know from there that it has to traverse down the right side of the tree to find what it&#x27;s looking for.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>For simplicity, these diagrams display a B-Tree instead of a B+ Tree. The key difference is that in a B+Tree, the leaf nodes contain a reference to the actual data, while in a B-Tree, the leaf nodes do not.</p></div><p><picture class="block"><img alt="B-Tree Diagram" src="https://planetscale.com/assets-2024/blog/content/the-problem-with-using-a-uuid-primary-key-in-mysql/b-tree-diagram.png" loading="lazy"></picture></p><p>If values 7-9 are added, MySQL will split the right node and rebalance the tree.</p><p><picture class="block"><img alt="The B-Tree diagram with 7-9 added" src="https://planetscale.com/assets-2024/blog/content/the-problem-with-using-a-uuid-primary-key-in-mysql/b-tree-rebalanced-diagram.png" loading="lazy"></picture></p><p>This process is known as page splitting, and the goal is to keep the B+ Tree structure balanced so that MySQL can quickly find the data it&#x27;s looking for. With sequential values, this process is relatively straightforward; however, when randomness is introduced into the algorithm, it can take significantly longer for MySQL to rebalance the tree. On a high-volume database, this can hurt user experience as MySQL tries to keep the tree in balance.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>For more information about how B+ Trees work, we have a dedicated video in <a href="https://planetscale.com/learn/courses/mysql-for-developers/indexes/b-trees?autoplay=1">our MySQL for Developers course</a>.</p></div><h3 id="higher-storage-utilization"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#higher-storage-utilization">Higher storage utilization</a></h3><p>All primary keys in MySQL are indexed. By default, an auto-incrementing integer will consume 32 bits of storage per value. Compare this with UUIDs. If stored in a compact binary form, a single UUID would consume 128 bits on disk. Already, that is 4x the consumption of a 32-bit integer. If instead you choose to use a more human readable string-based representation, each UUID could be stored as a <code>CHAR(36)</code>, consuming a whopping 288 bits per UUID. This means that each record would store 9 times more data than the 32-bit integer.</p><p>In addition to the default index created on the primary key, secondary indexes will also consume more space. This is because secondary indexes use the primary key as a pointer to the actual row, meaning they need to be stored with the index. This can lead to a significant increase in storage requirements for your database depending on how many indexes are created on tables using UUIDs as the primary key.</p><p>Finally, page splitting (as described in the previous section) can also negatively impact storage utilization as well as performance. InnoDB assumes that the primary key will increment predictably, either numerically or lexicographically. If true, InnoDB will fill the pages to about 94% of the page size before creating a new page. When the primary key is random, the amount of space utilized from each page can be as low as 50%. Due to this, using UUIDs that incorporate randomness can lead to excessive use of pages to store the index.</p><h2 id="best-ways-to-use-a-uuid-primary-key-with-mysql"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#best-ways-to-use-a-uuid-primary-key-with-mysql">Best ways to use a UUID primary key with MySQL</a></h2><p>If you absolutely need to use UUIDs as the unique identifier for records in your table, there are a few best practices you can follow to minimize the negative side effects of doing so.</p><h3 id="use-the-binary-data-type"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#use-the-binary-data-type">Use the binary data type</a></h3><p>While UUIDs are often sometimes as 36-character strings, they can also be represented in their native binary format as well. If converted to a binary value, you can store it in a <code>BINARY(16)</code> column, which reduces the storage requirements per value down to 16 bytes. This is still quite a bit larger than a 32-bit integer, but is certainly better than storing the UUID as a <code>CHAR(36)</code>.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> uuids</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  UUIDAsChar </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">char</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">36</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">not null</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  UUIDAsBinary </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">binary</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">16</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">not null</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> uuids </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  UUIDAsChar </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'd211ca18-d389-11ee-a506-0242ac120002'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">  UUIDAsBinary </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> UUID_TO_BIN(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'d211ca18-d389-11ee-a506-0242ac120002'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> uuids;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +--------------------------------------+------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | UUIDAsChar                           | UUIDAsBinary                       |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +--------------------------------------+------------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | d211ca18-d389-11ee-a506-0242ac120002 | 0xD211CA18D38911EEA5060242AC120002 |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +--------------------------------------+------------------------------------+</span></span>
<span class="line"></span></code></pre></div></div><h3 id="use-an-ordered-uuid-variant"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#use-an-ordered-uuid-variant">Use an ordered UUID variant</a></h3><p>Using a UUID version that supports ordering can mitigate some of the performance and storage impacts of using UUIDs by making the generated values more sequential which avoids some of the page splitting issues described earlier. Even when they are being generated on multiple systems, time-based UUIDs such as version 6 or 7 can guarantee uniqueness while keeping values as close to sequential as possible. The exception to this is UUIDv1, which has the least significant portion of the timestamp first.</p><h3 id="use-the-built-in-mysql-uuid-functions"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#use-the-built-in-mysql-uuid-functions">Use the built-in MySQL UUID functions</a></h3><p>MySQL supports generating UUIDs directly within SQL; however, it only supports UUIDv1 values. While it is not a great practice to use them by themselves, there is a helper function in MySQL called <code>uuid_to_bin</code>. Not only does this function convert the string value to binary, but you can use the option &#x27;swap flag&#x27;, which will reorder the timestamp portion to make the resulting binary more sequential.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> @uuidvar </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'd211ca18-d389-11ee-a506-0242ac120002'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Without swap flag</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> HEX(UUID_TO_BIN(@uuidvar)) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">as</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> UUIDAsHex;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | UUIDAsHex                        |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | D211CA18D38911EEA5060242AC120002 |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- With swap flag</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> HEX(UUID_TO_BIN(@uuidvar,</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">as</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> UUIDAsHex;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | UUIDAsHex                        |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- | 11EED389D211CA18A5060242AC120002 |</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- +----------------------------------+</span></span>
<span class="line"></span></code></pre></div></div><h3 id="use-an-alternate-id-type"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#use-an-alternate-id-type">Use an alternate ID type</a></h3><p>UUIDs are not the only type of identifier that provides uniqueness within a distributed architecture. Considering they were first created in 1987, there has been plenty of time for other professionals to propose different formats such as Snowflake IDs, ULIDs, or even NanoIDs (<a href="https://planetscale.com/blog/why-we-chose-nanoids-for-planetscales-api">which we use at PlanetScale</a>).</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1"># Snowflake ID</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">7167350074945572864</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1"># ULID</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">01HQF2QXSW5EFKRC2YYCEXZK0N</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1"># NanoID</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">kw2c0khavhql</span></span>
<span class="line"></span></code></pre></div></div><h2 id="conclusion"><a href="https://planetscale.com/blog/the-problem-with-using-a-uuid-primary-key-in-mysql#conclusion">Conclusion</a></h2><p>Using a UUID primary key in MySQL can (nearly) guarantee uniqueness in a distributed system; however, it comes with several tradeoffs. Luckily, with the many versions available and several alternatives, you have options that can better address some of these tradeoffs. After reading this article, you should be in a better position to make an informed decision about the ID type you choose when architecting your next database.</p>]]></content>
        <summary><![CDATA[Understand the different versions of UUIDs and why using them as a primary key in MySQL can hurt database performance.]]></summary>
      </entry>
    
      <entry>
        <title>Announcing Vitess 19</title>
        <link href="https://planetscale.com/blog/announcing-vitess-19" />
        <id>https://planetscale.com/blog/announcing-vitess-19</id>
        <published>2024-03-08T09:01:00.000Z</published>
        <updated>2024-03-08T09:01:00.000Z</updated>
        
        <author>
          <name>Vitess Engineering Team</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>We&#x27;re thrilled to announce the release of Vitess 19, the latest version packed with enhancements aimed at improving the scalability, performance, and usability of your database systems. With this release, the Vitess team continues our commitment to providing a powerful, scalable, and reliable database clustering solution for MySQL.</p><h2 id="whats-new-in-vitess-19"><a href="https://planetscale.com/blog/announcing-vitess-19#whats-new-in-vitess-19">What&#x27;s new in Vitess 19</a></h2><ul><li><strong>Dropping Support for MySQL 5.7</strong>: As Oracle marked MySQL 5.7 end of life in October 2023, we&#x27;re also moving forward by dropping support for MySQL 5.7. We advise users to upgrade to MySQL 8.0 while on Vitess 18 before making the jump to Vitess 19. However, Vitess 19 will still support importing from MySQL 5.7.</li><li><strong>Deprecations</strong>: We&#x27;re cleaning house to streamline our offerings and improve maintainability. This includes deprecating several VTTablet flags, MySQL-specific tags of the Docker image <code>vitess/lite</code>, and changes to the <code>EXPLAIN</code> statement format.</li><li><strong>Breaking Changes</strong>: Notably, <code>ExecuteFetchAsDBA</code> now rejects multi-statement SQL, enforcing stricter security and stability practices.</li><li><strong>New Metrics</strong>: We&#x27;re introducing new metrics for stream consolidations and adding the build version to <code>/debug/vars</code> to provide deeper insights and traceability.</li><li><strong>Enhanced Query Compatibility</strong>: This release brings support for multi-table delete operations, a new <code>SHOW VSCHEMA KEYSPACES</code> query, and several other SQL syntax enhancements that broaden Vitess&#x27;s compatibility with MySQL.</li><li><strong>Apply VSchema Enhancements</strong>: We&#x27;ve added a <code>--strict</code> sub-flag and corresponding gRPC field to the <code>ApplyVSchema</code> command, ensuring that only known parameters are used in Vindexes, enhancing error checking and config validation.</li><li><strong>Tablet Throttler</strong>: Throttlers now communicate via gRPC only. HTTP communication is no longer used. This closes a possible vulnerability vector.</li><li><strong>Online DDL</strong>: Support for backoff for cut-over attempts in the face of locking. Support for forced cut-over.</li><li><strong>Incremental Backup</strong>: Support for backup names and empty backups.</li><li><strong>Table lifecycle</strong>: Quicker cleanup flow.</li><li><strong>Performance improvements</strong>: Including a new connection pool for the Tablets, faster hashing in sharded Vitess clusters, and faster aggregations in the Gates.</li></ul><h2 id="new-and-updated-features"><a href="https://planetscale.com/blog/announcing-vitess-19#new-and-updated-features">New and updated features</a></h2><p>Let&#x27;s take a closer look at some of the key features.</p><h3 id="query-compatibility-enhancements"><a href="https://planetscale.com/blog/announcing-vitess-19#query-compatibility-enhancements">Query compatibility enhancements</a></h3><p>Vitess 19 introduces several SQL syntax improvements and compatibility features, including:</p><ul><li>Support for <code>AVG()</code> aggregation function on sharded keyspaces, utilizing a combination of <code>SUM</code> and <code>COUNT</code>.</li><li>Non-recursive Common Table Expressions (CTEs) support, allowing for more complex query constructions.</li></ul><h3 id="tablet-throttler"><a href="https://planetscale.com/blog/announcing-vitess-19#tablet-throttler">Tablet throttler</a></h3><p>Inter-throttler communication is now solely based on gRPC. HTTP communication is no longer supported.</p><h3 id="online-ddl"><a href="https://planetscale.com/blog/announcing-vitess-19#online-ddl">Online DDL</a></h3><p>Vitess migration cut-over now uses back-off in the face of table locks. If unable to cut-over, subsequent attempts take place at increasing intervals. This reduces the impact on an already overloaded production system.</p><p>Online DDL also supports forced cut-over, at either predetermined timeout or on demand. Forced cut-over prioritizes the completion of cut-over operations over production traffic and terminates queries and transactions that conflict with the cut-over.</p><p>See <a href="https://github.com/vitessio/vitess/pull/14546">this PR</a> for more information.</p><h3 id="incremental-backup"><a href="https://planetscale.com/blog/announcing-vitess-19#incremental-backup">Incremental backup</a></h3><p>The flag <code>Backup|BackupShard –incremental-from-pos</code> now accepts a backup name as the backup starting point.</p><p>An empty incremental backup is now allowed, and the <code>Backup|BackupShard</code> command returns with a success error code, even though no backup manifest or other artifacts are created.</p><h3 id="table-lifecycle"><a href="https://planetscale.com/blog/announcing-vitess-19#table-lifecycle">Table lifecycle</a></h3><p>The table GC mechanism is now more responsive to tables that need to be garbage collected and can observe operations that generate GC tables. For example, it can capture the result of an <code>ALTER VITESS_MIGRATION … CLEANUP</code> command and move the table through the relevant stages within seconds rather than taking several minutes or hours.</p><h3 id="breaking-change-executefetchasdba"><a href="https://planetscale.com/blog/announcing-vitess-19#breaking-change-executefetchasdba">Breaking change: <code>ExecuteFetchAsDBA</code></a></h3><p>The command <code>ExecuteFetchAsDBA</code> now rejects multi-statement input. Previously, multi-statement input was implicitly allowed but resulted in undefined and undesired behavior: errors were only reported for the first statement and silently dropped for successive statements. The connection was left in an undefined state and could leak results to subsequent users of the connection pool. The schema tracker would not be notified of changes until the connection was closed. We will introduce formal multi-statement support in a future version.</p><h2 id="performance-improvements"><a href="https://planetscale.com/blog/announcing-vitess-19#performance-improvements">Performance improvements</a></h2><p>Following the trend over the past 3 years, this new Vitess release is faster than the previous one in <em>all</em> the benchmarks we track in <a href="https://benchmark.vitess.io/status">Arewefastyet</a>. We&#x27;ve fixed several performance regressions from Vitess 18 and introduced significant performance improvements.</p><h3 id="new-connection-pool"><a href="https://planetscale.com/blog/announcing-vitess-19#new-connection-pool">New connection pool</a></h3><p>The connection pool for MySQL connections in the Tablets has been rewritten from scratch. The new pool is architected over several lock-free stacks and provides significantly lower query latencies, lower and more fair wait times, and more efficient usage of idle connections. This is particularly noticeable in Vitess clusters with external tablets (i.e., clusters where the Tablet and the MySQL instance are deployed on different hosts) and busy Vitess clusters with many point queries.</p><h3 id="faster-hashing-in-sharded-vitess-clusters"><a href="https://planetscale.com/blog/announcing-vitess-19#faster-hashing-in-sharded-vitess-clusters">Faster hashing in sharded Vitess clusters</a></h3><p>The VIndex hasher for textual columns was previously implemented using the <code>x/text/collate</code> package, which allocates a linear amount of memory based on the length of the column being hashed. We&#x27;ve replaced it with a custom, backwards-compatible implementation that is both faster and uses a constant amount of memory. This is a very significant performance improvement for sharded tables that use large textual columns as sharding keys.</p><h3 id="faster-comparisons-in-cross-shard-aggregations"><a href="https://planetscale.com/blog/announcing-vitess-19#faster-comparisons-in-cross-shard-aggregations">Faster comparisons in cross-shard aggregations</a></h3><p>The performance of cross-shard aggregations that use <code>ORDER</code> or <code>GROUP BY</code> qualifiers has been greatly improved by introducing Tiny Weights. The query executor in the VTGates now tags all the SQL values from the upstream shards with a compressed form of their weight string, allowing constant-time comparisons while performing aggregations.</p><h2 id="a-call-to-the-community"><a href="https://planetscale.com/blog/announcing-vitess-19#a-call-to-the-community">A call to the community</a></h2><p>We&#x27;re excited to see how you&#x27;ll use Vitess 19 to scale your database systems. As always, we&#x27;re eager to hear your feedback and experiences. Join us on our <a href="https://github.com/vitessio/vitess">GitHub</a> or <a href="http://vitess.io/slack">Slack channel</a> to share your stories, ask questions, and connect with the Vitess community.</p><h2 id="getting-started"><a href="https://planetscale.com/blog/announcing-vitess-19#getting-started">Getting started</a></h2><p>Upgrading to Vitess 19 is straightforward, but we recommend reviewing the <a href="https://github.com/vitessio/vitess/blob/main/changelog/19.0/19.0.0/release_notes.md">detailed release notes</a> for a smooth transition. Check out our <a href="https://vitess.io/docs/">documentation</a> for comprehensive guides and tips.</p><p>Thank you for your continued support and contributions to the Vitess project. Here&#x27;s to making database scaling even easier and more efficient with Vitess 19!</p><hr><p><em>The Vitess Team</em></p>]]></content>
        <summary><![CDATA[Vitess 19 is now generally available.]]></summary>
      </entry>
    
      <entry>
        <title>Three common MySQL database design mistakes</title>
        <link href="https://planetscale.com/blog/three-common-mysql-database-design-mistakes" />
        <id>https://planetscale.com/blog/three-common-mysql-database-design-mistakes</id>
        <published>2024-02-13T09:00:00.000Z</published>
        <updated>2024-02-13T09:00:00.000Z</updated>
        
        <author>
          <name>Brian Morrison II</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Many years ago, I worked for a telematics company that ingested data from hundreds of thousands of devices worldwide. There was a point of incredible growth where we onboarded a customer that gave us a massive number of new devices and a huge bump in revenue. It was a great moment for the company&#x27;s trajectory, but the increased amount of data being processed highlighted a massive flaw in our system.</p><p>The ID column of the data history table (which logged every event that occurred across all devices) was created with the <code>INT</code> data type, and it was quickly running out of space.</p><p>It wasn&#x27;t an issue immediately, but if that column ran out of space, our entire system would come to a halt. Funnily enough, we built a quick tool called “the doomsday clock,” which would roughly calculate the date this would occur. Had we expected this, we would have designed the database with a different type that would have more easily accommodated growth like this, but of course, the results of our decisions are always more obvious in hindsight.</p><p>Let&#x27;s take a look at this issue and a few other common database design mistakes when setting up your database.</p><h2 id="suboptimal-data-type"><a href="https://planetscale.com/blog/three-common-mysql-database-design-mistakes#suboptimal-data-type">Suboptimal data type</a></h2><p>The scenario described in the intro of this article highlights the importance of selecting a data type that&#x27;s big enough to accommodate your existing data, as well as any potential growth you might experience.</p><p>This applies to more than just numerical types, though. For example, if you attempted to write a string with 300 characters into a <code>VARCHAR(255)</code> column, MySQL would return an error and reject the write if it is in strict mode, which is the default setting for MySQL. If MySQL is not in strict mode, attempting to insert string data into a column that exceeds its length causes the data to be truncated, losing potentially important data.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>If you want to learn more about the different string types in MySQL, check out our article <a href="https://planetscale.com/blog/mysql-data-types-varchar-and-char">breaking down the text options available to you</a>.</p></div><p>Conversely, you can also select columns that store too much data. While this won&#x27;t have as much of a negative impact as not having enough room, there are storage and performance implications with over-provisioning columns. Let&#x27;s assume you have a column storing the US zip code, which is typically five digits. You could default to using <code>INT</code> for the column type (which stores a 32-bit integer), but you&#x27;d allocate far more storage than necessary. Utilizing a <code>SMALLINT</code> would be a better choice, as it stores a 16-bit integer and would be more than enough to store a zip code.</p><p>These are only a few small examples of selecting an inappropriate data type for your columns.</p><h2 id="missing-or-redundant-indexes"><a href="https://planetscale.com/blog/three-common-mysql-database-design-mistakes#missing-or-redundant-indexes">Missing or redundant indexes</a></h2><p>Indexes in MySQL speed up data access by building a separate structure that&#x27;s optimized to return data if the query&#x27;s criteria match the configuration of that index.</p><p>Indexes are very important when designing a fast database. When indexes aren&#x27;t utilized, any SQL queries that do not use pagination or provide a <code>LIMIT</code> will perform a scan on that table. When scanning, MySQL will start reading from the first row until it has found every row that matches the criteria. If you have a heavily used query on a particularly large table, repeatedly scanning the table can have massive negative performance implications.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>To see the practical effects of missing indexes, our very own Aaron Francis saved the SaaS for one of our customers and <a href="https://www.youtube.com/watch?v=kkjAxSViOAA">documented his process on YouTube</a>!</p></div><p>On the other hand, you can have too many indexes as well.</p><p>Every index created will utilize additional storage, so having unused or duplicate indexes directly impacts how much you are paying for that storage. Whenever data is updated or inserted into a table with indexes, MySQL needs to update those indexes (along with their <a href="https://dev.mysql.com/doc/refman/8.0/en/information-schema-statistics-table.html">associated statistics</a>) to ensure they are accurate regardless of whether they are used. This can be a time-intensive operation that can create a bad user experience.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>If you want to learn more about how to effectively use indexes, we have a whole section covering them in our <a href="https://planetscale.com/learn/courses/mysql-for-developers">MySQL for Developers course</a>.</p></div><h2 id="improperly-storing-semi-structured-data"><a href="https://planetscale.com/blog/three-common-mysql-database-design-mistakes#improperly-storing-semi-structured-data">Improperly storing semi-structured data</a></h2><p>Over the past 20 years, utilizing NoSQL to store semi-structured data has gained favor with companies that need to process vast amounts of data very quickly.</p><p>Plenty of dedicated solutions are available on the market for storing this kind of data. However, MySQL is actually very capable in this area as well. Most semi-structured data stored in a database is represented as JSON. The most obvious way to store this would be to store the string in a <code>TEXT</code> column, but this is definitely not the most optimal way.</p><p>MySQL has a dedicated <code>JSON</code> column type that is designed to store JSON in an efficient binary storage format.</p><p>Using <code>JSON</code> over <code>TEXT</code> has several key benefits. The first is that <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-introduction.html">InnoDB</a>, the most commonly used MySQL database engine, natively supports querying and filtering based on data within the JSON object stored in the column, removing the need to manually filter after results have been returned to your application code. MySQL also supports building indexes based on data within JSON. This enables fast searches, allowing you to return rows based on your queries more quickly.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p>If you want to learn more about the JSON data type, our blog has several articles on the topic, including <a href="https://planetscale.com/blog/the-mysql-json-data-type">JSON type basics</a> and <a href="https://planetscale.com/blog/indexing-json-in-mysql">how to index JSON columns</a>.</p></div><h2 id="conclusion"><a href="https://planetscale.com/blog/three-common-mysql-database-design-mistakes#conclusion">Conclusion</a></h2><p>Whatever happened to that ID column that was running out of space? Well, luckily enough, the column type was a signed integer, meaning we were able to reseed it to -2,147,483,648. This effectively doubled our ID capacity by assigning negative numbers as IDs and incrementing towards 0. It&#x27;s not the prettiest solution, but it did help us avoid a rather large amount of downtime that would be required to update the schema for nearly all tables in our database.</p><p>Designing a database for growth is no simple task, and things can get out of hand in a hurry. We&#x27;ve touched on only a few potential database design mistakes here, but every MySQL use case is unique and has its own challenges.</p><p>If you&#x27;ve encountered design issues, tell us more on Twitter, and make sure to tag <a href="https://twitter.com/planetscale">@planetscale</a>!</p>]]></content>
        <summary><![CDATA[Learn about a few common mistakes when designing your MySQL database schema.]]></summary>
      </entry>
    
      <entry>
        <title>Working with Geospatial Features in MySQL</title>
        <link href="https://planetscale.com/blog/geospatial-features-mysql" />
        <id>https://planetscale.com/blog/geospatial-features-mysql</id>
        <published>2024-01-25T09:00:00.000Z</published>
        <updated>2024-01-25T09:00:00.000Z</updated>
        
        <author>
          <name>Savannah Longoria</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Data is abstract. Geospatial design management is how engineers across various disciplines make sense of complex data to make more informed decisions and better understand the spatial relationships in the world around us. In this blog post, I explore how complex data and geographic features can be represented in MySQL.</p><p>Geospatial data, often referred to in technical documentation as <strong><em>geodata</em></strong>, includes information related to locations on the Earth&#x27;s surface. In MySQL, geographic features represent anything in the real world with a location and are defined as either Entities or Space.</p><div class="table-overflow"><table><thead><tr><th>Type</th><th>Definition</th><th>Examples</th></tr></thead><tbody><tr><td><p><strong>Entities</strong></p></td><td><p>Specific objects with defined boundaries and individual properties</p></td><td><ul><li><strong>Landmarks</strong>: Mountains, rivers, forests, buildings</li><li><strong>Infrastructure</strong>: Roads, bridges, power lines</li><li><strong>Administrative areas</strong>: Countries, cities, states</li><li><strong>Points of interest</strong>: Restaurants, shops, ATMs</li></ul></td></tr><tr><td><p><strong>Spaces</strong></p></td><td><p>Continuous areas defined by their location and characteristics</p></td><td><ul><li><strong>Land cover</strong>: Forest, grassland, urban areas</li><li><strong>Elevation</strong>: Topography, hills, valleys</li><li><strong>Soil types</strong>: Sand, clay, loam</li><li><strong>Environmental data</strong>: Temperature, precipitation, air quality</li></ul></td></tr></tbody></table></div><hr><h2 id="mysql-spatial-data-types"><a href="https://planetscale.com/blog/geospatial-features-mysql#mysql-spatial-data-types">MySQL spatial data types</a></h2><p>In the paragraph above, we covered what Spaces and Entities are. How exactly are these geographic features represented in MySQL and other relational databases? These real-world objects and areas can be modeled within the database by utilizing specific data types and spatial functions. MySQL&#x27;s capabilities revolve around three core geospatial object types: points, paths, and polygons.</p><p><picture class="block"><img alt src="https://planetscale.com/assets-2024/blog/content/geospatial-features-mysql/spatial-data-types.png" loading="lazy"></picture></p><p>In MySQL, spatial data types store geometry and geography values in the table column. Both single-geometry and multi-geometry types are supported. Single-geometry values include <code>GEOMETRY</code>, <code>POINT</code>, <code>LINESTRING</code>, <code>POLYGON</code>. Multi-geometry types that represent multiple objects of the same type include <code>MULTIPOINT</code>, <code>MULTILINESTRING</code>, <code>MULTIPOLYGON</code>, and <code>GEOMETRYCOLLECTION</code>.</p><div class="table-overflow"><table><thead><tr><th>Type</th><th>Description</th><th>Examples</th></tr></thead><tbody><tr><td><code>GEOMETRY</code></td><td>Stores any type of geometry value. It is a noninstantiable class but has a number of properties common to all geometry values.</td><td><a href="https://dev.mysql.com/doc/refman/8.0/en/gis-class-geometry.html">Link to documentation</a></td></tr><tr><td><code>POINT</code></td><td>Stores a MySQL single X and Y coordinate value</td><td><code>POINT(-74.044514 40.689244)</code></td></tr><tr><td><code>LINESTRING</code></td><td>Stores a set of points that form a curve. An ordered list of points connected by edges</td><td><code>LINESTRING(0 0, 0 1, 1 1)</code></td></tr><tr><td><code>POLYGON</code></td><td>Stores a set of points in a multi-sided geometry. Similar to a linestring, but closed (must have at least three unique points, and the first and last point-pairs must be equal)</td><td><code>POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7, 5 5))</code>. Each ring is represented as a set of points.</td></tr><tr><td><code>MULTIPOINT</code></td><td>Stores a set of multiple point values</td><td><code>MULTIPOINT(0 0, 20 20, 60 60)</code></td></tr><tr><td><code>MULTILINESTRING</code></td><td>Stores a set of multiple <code>LINESTRING</code> values</td><td><code>MULTILINESTRING((10 10, 20 20), (15 15, 30 15))</code></td></tr><tr><td><code>MULTIPOLYGON</code></td><td>Stores a set of multiple <code>POLYGON</code> values</td><td><code>MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)),((5 5,7 5,7 7,5 7, 5 5)))</code></td></tr><tr><td><code>GEOMETRYCOLLECTION</code></td><td>Stores a set of multiple <code>GEOMETRY</code> values. Note that MySQL does NOT support empty GeometryCollections except for the single GeometryCollection object itself.</td><td><code>GEOMETRYCOLLECTION(POINT(10 10), POINT(30 30), LINESTRING(15 15, 20 20))</code></td></tr></tbody></table></div><h2 id="supported-spatial-data-formats"><a href="https://planetscale.com/blog/geospatial-features-mysql#supported-spatial-data-formats">Supported spatial data formats</a></h2><p>MySQL supports several spatial data formats for storing and manipulating geospatial data within its database. Here are the three primary formats:</p><ul><li><strong>Well-Known Text (WKT) format</strong>: Which is a human-readable text format for representing geometric objects<ul><li>Uses keywords like <code>POINT</code>, <code>LINESTRING</code>, <code>POLYGON</code> followed by coordinates and optional metadata.</li></ul></li></ul><ul><li><strong>Well-Known Binary (WKB) format</strong>: Which is a compact binary format for representing geometric objects. It’s not human-readable but it’s easily parsed by software and tends to have more efficient storage and transmission than WKT.</li><li><strong>Internal Format</strong>: MySQL stores spatial data internally in a format similar to WKB but with an additional 4 bytes for storing the Spatial Reference Identifier (SRID). SRID defines the coordinate system of the geometry, ensuring accurate interpretation.</li></ul><h2 id="working-with-geospatial-features-in-mysql"><a href="https://planetscale.com/blog/geospatial-features-mysql#working-with-geospatial-features-in-mysql">Working with geospatial features in MySQL</a></h2><p>Geospatial objects are just another data type in MySQL and can be used right alongside numbers, strings, and JSON.</p><div class="mb-3 border p-3 border-blue-600 dark:border-blue-500"><p><span class="bg-blue-600 px-sm text-white dark:bg-blue-500 dark:text-black">Note</span></p><p><a href="https://planetscale.com/docs/what-is-planetscale">PlanetScale</a> supports geospatial objects. If you&#x27;d like to follow along with these examples, <a href="https://auth.planetscale.com/sign-up">sign up</a> to spin a database cluster in seconds.</p></div><h3 id="creating-a-geospatial-table"><a href="https://planetscale.com/blog/geospatial-features-mysql#creating-a-geospatial-table">Creating a geospatial table</a></h3><p>Use the <a href="https://dev.mysql.com/doc/refman/8.0/en/create-table.html"><code>CREATE TABLE</code></a> statement to create a table with a spatial column. Here we have a table named <code>geom</code> that has a column named <code>g</code> that can store values of any geometry type. We also defined the column with a spatial data type to have an SRID attribute and explicitly indicated the spatial reference system (SRS) for values stored in the column:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">    `locations`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `city`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `city_ascii`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `country`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `iso3`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `admin_name`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `capital`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">255</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `population`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">        `g`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> geometry</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NOT NULL</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> SRID </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4326</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        PRIMARY KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        SPATIAL</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> KEY</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `g`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`g`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">        FULLTEXT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> KEY</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `city_ascii`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`city_ascii`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    );</span></span>
<span class="line"></span></code></pre></div></div><p>Use the <a href="https://dev.mysql.com/doc/refman/8.0/en/alter-table.html"><code>ALTER TABLE</code></a> statement to add or drop a spatial column to or from an existing table:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> geom </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ADD</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> pt </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">POINT</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> geom </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DROP</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> pt;</span></span>
<span class="line"></span></code></pre></div></div><h2 id="querying-geospatial-data"><a href="https://planetscale.com/blog/geospatial-features-mysql#querying-geospatial-data">Querying geospatial data</a></h2><p>Understanding geographic features and their representation in MySQL is crucial for working with spatial data effectively. This allows you to store, retrieve, analyze, and visualize geographic information efficiently within your database.</p><p>MySQL provides various spatial functions for manipulating and analyzing geographic data. Let&#x27;s cover some of the common spatial functions.</p><h3 id="location-functions"><a href="https://planetscale.com/blog/geospatial-features-mysql#location-functions">Location functions</a></h3><p>Location functions are used to extract coordinates.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_GeomFromText(wkt_string) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to convert WKT to a geometry object</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_X(geom), ST_Y(geom) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to extract coordinates.</span></span>
<span class="line"></span></code></pre></div></div><h3 id="distance-calculations"><a href="https://planetscale.com/blog/geospatial-features-mysql#distance-calculations">Distance calculations</a></h3><p>Distance calculations are used to measure the distance between features.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Distance(geom1, geom2)</span></span>
<span class="line"></span></code></pre></div></div><h3 id="area-and-perimeter-calculations"><a href="https://planetscale.com/blog/geospatial-features-mysql#area-and-perimeter-calculations">Area and perimeter calculations</a></h3><p>Area and perimeter calculations are used to determine the area and perimeter of polygons.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Area(geom) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- calculate the area of a polygon</span></span>
<span class="line"></span></code></pre></div></div><h3 id="intersection-and-containment"><a href="https://planetscale.com/blog/geospatial-features-mysql#intersection-and-containment">Intersection and containment</a></h3><p>Intersecton and containment are used to find features that overlap or are contained within others.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Contains(geom1, geom2) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to check if one feature contains another</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Intersects(geom1, geom2) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to check if features intersect.</span></span>
<span class="line"></span></code></pre></div></div><h3 id="buffering"><a href="https://planetscale.com/blog/geospatial-features-mysql#buffering">Buffering</a></h3><p>Buffering is used to create zones around features based on a specified distance.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Buffer(geom, distance) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to create a zone around a feature with a specified distance.</span></span>
<span class="line"></span></code></pre></div></div><h3 id="analysis-functions"><a href="https://planetscale.com/blog/geospatial-features-mysql#analysis-functions">Analysis functions</a></h3><p>Analysis functions can be used to combine or diff geometries.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Union(geom1, geom2) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to combine geometries</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Difference(geom1, geom2) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to obtain the difference between geometries.</span></span>
<span class="line"></span></code></pre></div></div><h3 id="relationship-functions"><a href="https://planetscale.com/blog/geospatial-features-mysql#relationship-functions">Relationship functions</a></h3><p>Relationship functions are used to detect relationships between features.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Touches(geom1, geom2), ST_Crosses(geom1, geom2)</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">ST_Overlaps(geom1, geom2) </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- to determine various spatial relationships between features</span></span>
<span class="line"></span></code></pre></div></div><h2 id="examples-using-these-spatial-functions"><a href="https://planetscale.com/blog/geospatial-features-mysql#examples-using-these-spatial-functions">Examples using these spatial functions</a></h2><p>Remember, the specific functions and queries you use will depend on your specific data and analysis goals. Let&#x27;s look through some common examples of spatial functions.</p><h3 id="distance-between-two-cities"><a href="https://planetscale.com/blog/geospatial-features-mysql#distance-between-two-cities">Distance between two cities</a></h3><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> st_distance_sphere( (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Santos'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        ), (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Sao Paulo'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    );</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> st_distance_sphere( (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'New York'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        ), (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Blauvelt'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    );</span></span>
<span class="line"></span></code></pre></div></div><h3 id="find-cities-in-a-radius"><a href="https://planetscale.com/blog/geospatial-features-mysql#find-cities-in-a-radius">Find cities in a radius</a></h3><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    city,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    st_astext(g),</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    st_distance_sphere(</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        g, (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'New York'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    )</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> st_distance_sphere(</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        g, (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'New York'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    ) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 15000</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">order by</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 3</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> desc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    city,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    st_astext(g),</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    st_distance_sphere(</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        g, (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Santos'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    )</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> st_distance_sphere(</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        g, (</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> g</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> locations</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">            where</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">                city_ascii </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Santos'</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">        )</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    ) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 15000</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">order by</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 3</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> desc</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><h3 id="other-examples-not-related-to-the-table-that-we-previously-created"><a href="https://planetscale.com/blog/geospatial-features-mysql#other-examples-not-related-to-the-table-that-we-previously-created">Other examples (not related to the table that we previously created)</a></h3><ul><li>Find all restaurants within 1 km of a specific point:</li></ul><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> restaurants</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ST_Distance(ST_GeomFromText(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'POINT(10 20)'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">location</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">&#x3C;=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1000</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><ul><li>Find all parks that intersect with a specified polygon:</li></ul><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parks</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">WHERE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ST_Intersects(geom, ST_GeomFromText(</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'POLYGON((10 20, 20 30, 30 20, 10 20))'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">));</span></span>
<span class="line"></span></code></pre></div></div><ul><li>Find the total area of all forests:</li></ul><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">SELECT</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> SUM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(ST_Area(geom)) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">FROM</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> forests;</span></span>
<span class="line"></span></code></pre></div></div><h2 id="addtional-geospatial-feature-notes"><a href="https://planetscale.com/blog/geospatial-features-mysql#addtional-geospatial-feature-notes">Addtional geospatial feature notes</a></h2><h3 id="spatial-databases-for-geographic-features-mysql-8-vs-57-compared"><a href="https://planetscale.com/blog/geospatial-features-mysql#spatial-databases-for-geographic-features-mysql-8-vs-57-compared">Spatial databases for geographic features: MySQL 8 vs 5.7 compared</a></h3><p>Better support for spatial data handling was one of the major improvements included in the MySQL 8 release. However, it was already possible to store and process geographic features using earlier MySQL versions as well as competing database systems. The first major improvement brought forth by MySQL 8 was better support for coordinate reference systems a.k.a. spatial reference systems (SRS). In the table below, we can see that there are now three kinds of spatial reference system available in MySQL 8:</p><div class="table-overflow"><table><thead><tr><th>Type of spatial reference system (SRS)</th><th>Explanation</th><th>Coordinates</th><th>Units</th></tr></thead><tbody><tr><td>Projected SRS</td><td>Projection of a globe onto a flat surface — a map</td><td>Cartesian</td><td>Distance units: meters, feet, etc.</td></tr><tr><td>Geographic SRS</td><td>Non-projected — an ellipsoid</td><td>Latitude-longitude</td><td>Any angular unit</td></tr><tr><td>SRS with SRID 0</td><td>Default SRID for spatial data in MySQL</td><td>Infinite flat Cartesian plane</td><td>Unitless</td></tr></tbody></table></div><p>While spatial reference systems are not a new concept in MySQL, with version 8.0 they directly affect computation. Each spatial reference system is denoted by a spatial reference system identifier (SRID). There are more than 5,000 spatial reference systems to choose from.</p><p>The second major improvement to spatial data handling in MySQL 8 pertains to spatial indexing, that is, the optimization of columns holding spatial data. Two requirements have to be met for spatial indexing to work properly:</p><ol><li>The geometry columns to be included in the index need to be defined as NOT NULL.</li><li>Columns need to be restricted to a spatial reference system identifier (SRID), and all column values must have the same SRID.</li></ol><p>Prior to MySQL 8.0, spatial features were stored with a spatial reference system identifier (SRID), but the database couldn&#x27;t utilize this information for calculations. Instead, all functions operated on a flat plane (SRID 0). This meant users had to create custom functions to convert units and perform accurate calculations, requiring a deep understanding of math and geometry.Furthermore, many spatial relationship functions only used the minimum bounding rectangle (MBR) instead of the object&#x27;s actual shape, limiting their accuracy.</p>]]></content>
        <summary><![CDATA[In this blog post, we explore how complex data and geographic features can be represented in MySQL.]]></summary>
      </entry>
    
      <entry>
        <title>MySQL isolation levels and how they work</title>
        <link href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work" />
        <id>https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work</id>
        <published>2024-01-08T15:00:00.000Z</published>
        <updated>2024-01-08T15:00:00.000Z</updated>
        
        <author>
          <name>Brian Morrison II</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>In the early 1980’s, computer scientists Andreas Reuter and Theo Harder <a href="https://dl.acm.org/doi/10.1145/289.291">coined the term ACID</a> to describe a set of properties related to database transactions designed to keep data stored reliably and with integrity.</p><p>Most (if not all) modern database systems are built around ACID compliance. By adhering to these fundamentals, businesses can confidently trust the data within their database, whether it’s for a small project management app, or a large banking system. Isolation levels, as well as the related concepts, are cornerstones that enable MySQL to fulfill ACID guarantees.</p><p>In this article, we’ll break down how multiple clients can work with a single database and maintaining data consistency by using isolation levels.</p><h2 id="what-is-a-mysql-isolation-level"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#what-is-a-mysql-isolation-level">What is a MySQL isolation level?</a></h2><p>A MySQL isolation level is one of four modes that can be set on a MySQL session that controls how transactions should behave when executing concurrently.</p><p>This concept relates directly to the <em>Isolation</em> requirement of ACID compliant databases, which states that transactions should be executed in a way that does not affect other transactions. Depending on the isolation level set on a session, various locking mechanisms are used to manage which transactions have access to what data at any given time. This also determines the different inconsistencies (known as violations) that can surface when running multiple transactions concurrently.</p><p>With that brief explanation on what a MySQL isolation level is and what benefit it provides, let’s further explore the different isolation levels, transactions, violations, locks, and how they all relate to each other.</p><h2 id="what-is-acid-compliance"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#what-is-acid-compliance">What is ACID compliance?</a></h2><p>Before we dive deeper into how MySQL keeps data consistent, it’s worth briefly explaining the concepts that make up ACID.</p><h3 id="atomicity"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#atomicity">Atomicity</a></h3><p>Atomicity means that any number of SQL operations can be executed as a single unit of work known as a transaction.</p><p>Transactions are used in MySQL to execute one or more statements as a group, satisfying the Atomicity requirement of an ACID compliant database. When statements are executed as part of a transaction, no data within the database is actually changed until the transaction is committed. Alternatively, if something goes wrong within the transaction, you are given the opportunity to roll back any changes, which can prevent issues with the underlying data if committed.</p><p>By default, MySQL has <a href="https://dev.mysql.com/doc/refman/en/innodb-autocommit-commit-rollback.html"><code>autocommit</code></a> turned on, which means that individual statements issued are automatically committed without you having to manually start a transaction.</p><h3 id="consistency"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#consistency">Consistency</a></h3><p>Consistency ensures that the database can confidently be moved from one state to another, avoiding any adverse effects or corruption of the data. This is done by enforcing things like constraints, cascading effects, triggers, etc.</p><h3 id="isolation"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#isolation">Isolation</a></h3><p>Isolation states that transactions are executed independently and in a controlled and ordered way since many clients and threads are often connected to a single database and executing transactions concurrently. MySQL isolation levels relate directly to this part of ACID compliance.</p><h3 id="durability"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#durability">Durability</a></h3><p>Durability simply means that once a transaction completes, the data can survive system failures and outages by storing the data on persistent storage. Memcached, for example, stores all of its data in memory, so it would fail this requirement.</p><h2 id="example-a-music-store"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#example-a-music-store">Example: A music store</a></h2><p>The remainder of this article will use a common example of an online store that specializes in selling units of an antique audio medium known as compact discs, or “CDs”.</p><p>The schema will be relatively simple with four entities:</p><ul><li><code>products</code> will contain the available stock of a given CD, including the name, artist, and quantity available.</li><li><code>unit_status</code> will list the quantity of CDs for any product that are currently available, on hold, or pending shipment.</li><li><code>customer_transactions</code> will contain records of when sales are made.</li><li><code>customers</code> will list all of the customers that have purchased CDs.</li></ul><p>Here is a diagram of what the database will look like:</p><p><picture class="block"><img alt="A music store database diagram" src="https://planetscale.com/assets-2024/blog/content/mysql-isolation-levels-and-how-they-work/diagram-database.svg" loading="lazy"></picture></p><h2 id="isolation-violations"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#isolation-violations">Isolation violations</a></h2><p>Violations are various read phenomena that can occur when transactions are executed concurrently, and are specifically what isolation levels seek to prevent. Depending on the selected MySQL isolation level (each of which will be covered later in this article), one or more violations may be permitted in the name of database performance and query consistency requirements.</p><p>Let’s take a look at the three common violations you may encounter while building around your database.</p><h3 id="dirty-read"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#dirty-read">Dirty read</a></h3><p>A dirty read occurs when a query within one transaction returns inconsistent data because it may read a new version of the data that has not yet been committed to the database based on another concurrent transaction.</p><p>To demonstrate this, let’s say that two customers are interested in purchasing the same CD and two units have a status of <code>available</code>. One customer purchases both units, which prompts the system to move the status of two units to <code>sold_not_shipped</code> before reducing the quantity available. At the same time, the other customer checks the inventory to see if the CD is available, and the system returns that there are still two units available.</p><p>The transaction used to update the status for the CD is as follows:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 2</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'sold_not_shipped'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 2</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Assuming the following query is executed during the above transaction just before <code>commit;</code> is called, the result would be <code>0</code> even though the transaction is not finished yet.</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Here is a timeline of what the above transactions would look like that would result in the customer seeing a potentially unexpected result for quantity available:</p><p><picture class="block"><img alt="A timeline diagram demonstrating dirty reads" src="https://planetscale.com/assets-2024/blog/content/mysql-isolation-levels-and-how-they-work/diagram-dirty-reads-timeline.png" loading="lazy"></picture></p><p>At first glance, this may not appear to be an issue since the database reflects that the values of the two rows in the <code>unit_status</code> table after both <code>UPDATES</code> occurred. However, if that transaction issued a <code>rollback;</code> instead of <code>commit;</code>, the data returned would be inaccurate since the changes were reverted after the customer was shown that no units were available.</p><h3 id="non-repeatable-reads"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#non-repeatable-reads">Non-repeatable reads</a></h3><p>Non-repeatable reads occur when a transaction with multiple select queries reads the same rows within that transaction, but the data within those rows is different between selects since another transaction has modified the data in that time.</p><p>For this example, the store owner is looking to read the number of units available for a given CD across two <code>select</code> queries, however they receive a different result between those two statements since it occurs at the same time that a customer is purchasing the CD.</p><p>Here are the queries used to get the quantity available:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Get the current available quantity of the CD, will return 15</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- The same query will now return 13 (see the transaction below)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>And here is the same transaction used in the previous example to update the inventory of a given CD when it is purchased:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Move the units from available to sold_not_shipped</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 2</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'sold_not_shipped'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unit_status </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> quantity </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 2</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> product_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> and</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> status</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'available'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Because these two transactions are executed simultaneously, they overlap each other, resulting in the second statement in the first transaction returning a different value.</p><p><picture class="block"><img alt="A diagram demonstrating a non-repeatable read violation" src="https://planetscale.com/assets-2024/blog/content/mysql-isolation-levels-and-how-they-work/diagram-non-repeatable-read.png" loading="lazy"></picture></p><h3 id="phantom-reads"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#phantom-reads">Phantom reads</a></h3><p>Phantom reads occur when the actual rows returned between select statements in a transaction differ because another transaction has inserted rows before the first can complete.</p><p>Using the CD store example, lets say that the owner wants to know the count of all products in the database. The following transaction is used to get that information:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- This will return 100.</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> count</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Add another one for good measure, or for demo purposes, you decide.</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Either way this would return 101.</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> count</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">*</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span></span>
<span class="line"></span></code></pre></div></div><p>At the same time, an employee receives a shipment a new CD, which needs to be added to the database. The following transaction is used to add the new units:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> album </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'The Battle Of Los Angeles'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, artist </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'Rage Against The Machine'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, release_year </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1999</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, cost </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1500</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Finally, here is what the timeline would look like to cause this phenomena:</p><p><picture class="block"><img alt="A diagram demonstrating a phantom read" src="https://planetscale.com/assets-2024/blog/content/mysql-isolation-levels-and-how-they-work/diagram-phantom-reads.png" loading="lazy"></picture></p><p>Because of the newly inserted records in <code>unit_status</code> table, the summation of the quantity available would be different between the two select statements.</p><h2 id="locks-and-how-they-relate-to-isolation"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#locks-and-how-they-relate-to-isolation">Locks and how they relate to isolation</a></h2><p>Transaction isolation is enforced by using various types of locks, or flags that can be set on rows and tables to prevent data from being read or modified.</p><p>MySQL will use one of two types of locks depending on the isolation level: shared or exclusive locks.</p><h3 id="shared-locks"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#shared-locks">Shared locks</a></h3><p>A shared lock is one that can be created by transactions for ensuring the data they are reading doesn’t change. When a shared lock is created on a row, that row is still readable by other transactions. Other transactions, however, cannot modify the data within that row. Any number of transactions can create a shared lock on a row and all locks will need to be released before the data can be modified.</p><h3 id="exclusive-locks"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#exclusive-locks">Exclusive locks</a></h3><p>When a transaction creates an exclusive lock on a row, only that transaction can read or write the data. If another transaction attempts to read or write data to that row, it will be prevented from doing so. This is especially useful during transactions where you expect data to be updated to prevent some of the violations outlined above.</p><h3 id="gap-locking"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#gap-locking">Gap locking</a></h3><p>Gap locking is a special type of lock that is used by MySQL to prevent phantom reads. Gap locking will use criteria in <code>where</code> clauses to lock the space around the read data, preventing rows from being inserted that may alter the query if its run a second time.</p><p>To demonstrate this, let’s use the music store example and assume the owner wants to run a sale on any CDs released in 1999:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> release_year </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1999</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> for</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> cost </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 800</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> release_year </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1999</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>If an employee decides to add a new CD to the products table that was also released in 1999, MySQL would make that transaction wait until the previous one had completed before proceeding:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products (album, artist, release_year, cost)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'The Battle Of Los Angeles'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'Rage Against The Machine'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1999</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1500</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p><picture class="block"><img alt="A diagram demonstrating gap locking" src="https://planetscale.com/assets-2024/blog/content/mysql-isolation-levels-and-how-they-work/diagram-gap-locking.png" loading="lazy"></picture></p><p>This prevents the second transaction from inserting a new row that would alter the results of the first transaction.</p><h3 id="controlling-locks"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#controlling-locks">Controlling locks</a></h3><p>It’s worth mentioning that <code>select</code> statements can actually be altered to indicate that MySQL should place a specific type of lock on a row. By adding the <code>for share</code> keywords on the end of a <code>select</code> statement, MySQL will create a shared lock on that row. And using the <code>for update</code> keywords on the end of a <code>select</code> statement creates an exclusive lock.</p><p>Here is a modified version of the “dirty read” example from above that uses a locking read. This approach would prevent another transaction from reading the data before it was updated:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">start transaction</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> for</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> products </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> cost </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 800</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 20</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><h2 id="the-four-mysql-isolation-levels"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#the-four-mysql-isolation-levels">The four MySQL isolation levels</a></h2><p>Now that we’ve covered much of the prerequisite knowledge, let’s talk about how MySQL adheres to the Isolation requirement with various Transaction Isolation Levels.</p><p>Isolation levels instruct the database engine on how to manage multiple transactions being performed concurrently, and what violations are possible.</p><p>Isolation levels are set per session and each database has a default mode that will be used by every session if it is not changed.</p><p>Let’s explore the four isolation levels and what violations they each prevent and allow.</p><h3 id="read-uncommitted"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#read-uncommitted">Read uncommitted</a></h3><p>Read uncommitted is the lowest isolation level, effectively allowing all violations. This is because transactions with this isolation level will always read the latest version of a row that&#x27;s been modified by any transaction, whether it&#x27;s been committed or not (more on row versioning in a bit). This mode should be used when performance takes priority over data consistency.</p><p>Deviating from the example outlined above, one situation where this might fit well is in a social media app when finding the number of likes a popular post has. Many social media apps shorten their numbers after a certain point (ie; <code>1k</code> over <code>1,000</code>). If a client needs to know how many likes the post has, returning an approximation over the exact value is usually acceptable.</p><h3 id="read-committed"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#read-committed">Read committed</a></h3><p>Read committed is the step above <code>read uncommitted</code> and prevents dirty reads.</p><p>In this mode, each row modified will create a version of that row which is tagged by the transaction ID. Subsequent operations on that row will use the version belonging to that transaction. Each <code>select</code> within that transaction will create and use a fresh snapshot (a version of the read rows at that point in time). This means that if a row is modified and has been comitted by another transaction, the next <code>select</code> will create a new snapshot which now represents the latest committed version of that data.</p><p>The key difference between <code>read committed</code> and <code>read uncommitted</code> is that <code>read committed</code> will always use the latest comitted row, whereas <code>read uncommitted</code> will the latest version of that row even if it hasn&#x27;t yet been committed.</p><p>This mode will still allow non-repeatable read and phantom read violations.</p><h3 id="repeatable-read"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#repeatable-read">Repeatable read</a></h3><p>Repeatable read is the default MySQL isolation level used for all connections unless configured otherwise.</p><p>With InnoDB&#x27;s MVCC (Multi Version Concurrency Control), a row may have multiple versions at any given time, depending on open transactions. In repeatable read level, queries will use a consistent snapshot of the data, pinning the reads to a single transaction ID throughout the transaction. Compare that with <code>read committed</code> where reads always pick the latest committed version of any row, and where subsequent reads may return different results.</p><p>With locking reads, the method that MySQL uses to lock the data depends on whether a unique index is used.</p><p>If an index can be used based on the <code>where</code> condition, MySQL will only lock the necessary rows that match the query. If an index is NOT used and the table is scanned, MySQL will lock all of the rows it reads regardless if they match the <code>where</code>, as well as perform gap locking to prevent inserts that may alter the data if the query is run multiple times.</p><p>Using the repeatable read isolation level prevents dirty reads, non-repeatble reads, and phantom reads.</p><h3 id="serializable"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#serializable">Serializable</a></h3><p>Setting the isolation level to Serializable also prevents all violations, but it has the most performance impact on your MySQL server. This mode works exactly like Repeatable read, but implicitly creates a shared lock on all <code>select</code> statements whether you use <code>for share</code> or not. Due to the excessive locks used, there is a greater risk of deadlocks occuring.</p><h2 id="how-to-set-a-mysql-isolation-level"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#how-to-set-a-mysql-isolation-level">How to set a MySQL isolation level</a></h2><p>Setting a specific MySQL isolation level is relatively straightforward, and can be done on a specific session or on the entire server.</p><p>The following statements will set the isolation level on the current session. The <code>SESSION</code> keyword is optional if you run it prior to starting a transaction, but will error out if you try:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> transaction</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> isolation</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> level</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> read</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> uncommitted</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> session</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> transaction</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> isolation</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> level</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> read</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> committed</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">; </span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- 'session' is optional</span></span>
<span class="line"></span></code></pre></div></div><p>If you have the <code>CONNECTION_ADMIN</code> user permissions, you can also use the following command to set the default isolation level across the server. This only affects future sessions, and any session is open to change its own isolation level regardless of what the server has it set to:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">set</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> global</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> transaction</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> isolation</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> level</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> serializable</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>Finally, isolation levels can be configured prior to MySQL starting, by using the <code>--transaction-isolation=READ-UNCOMMITED</code> or in the configuration file:</p><div class="code-block" data-language="ini"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">[</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">mysqld</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1">]</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">transaction-isolation</span><span style="--shiki-light:#616161;--shiki-dark:#C1C1C1"> =</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> REPEATABLE-READ</span></span>
<span class="line"></span></code></pre></div></div><h2 id="conclusion"><a href="https://planetscale.com/blog/mysql-isolation-levels-and-how-they-work#conclusion">Conclusion</a></h2><p>As you can see, MySQL uses a combination of several different concepts to ensure that it’s ACID compliant, making it a reliable and trustworthy database. Many of these concepts revolve around transaction isolation and the various levels available, allowing you to fine tune the balance between safety and performance!</p>]]></content>
        <summary><![CDATA[Learn about the various isolation levels used by MySQL to allow concurrency in your database.]]></summary>
      </entry>
    
      <entry>
        <title>Introducing the schemadiff command line tool</title>
        <link href="https://planetscale.com/blog/schemadiff-command-line-tool" />
        <id>https://planetscale.com/blog/schemadiff-command-line-tool</id>
        <published>2023-12-18T09:00:00.000Z</published>
        <updated>2023-12-18T09:00:00.000Z</updated>
        
        <author>
          <name>Shlomi Noach</name>
        </author>
        
        
        <category term="product" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>One of the core benefits of PlanetScale database clusters is the workflow enabling zero downtime schema migrations, which are made possible by database branching.</p><p>A <a href="https://planetscale.com/docs/vitess/schema-changes/branching">branch in PlanetScale</a> is technically its own independent MySQL cluster. <a href="https://planetscale.com/docs/vitess/schema-changes/deploy-requests">Deploy requests</a> are used to apply the schema changes from one branch to another. As part of this workflow, we heavily utilize the <code>schemadiff</code> command provided by <a href="https://vitess.io/">Vitess</a> to calculate the schema differences between branches, determine the order of changes (including the potential for <a href="https://planetscale.com/blog/deploying-multiple-schema-changes-at-once">migration concurrency</a>), and participate in <a href="https://planetscale.com/blog/database-branching-three-way-merge-schema-changes">three-way merge</a> logic to apply changes to an upstream database branch.</p><p>Today we are releasing the <a href="https://github.com/planetscale/schemadiff"><code>schemadiff</code> command line tool</a>, a thin wrapper around Vitess&#x27;s <code>schemadiff</code> library.</p><h2 id="getting-started-with-schemadiff"><a href="https://planetscale.com/blog/schemadiff-command-line-tool#getting-started-with-schemadiff">Getting started with <code>schemadiff</code></a></h2><p>The <code>schemadiff</code> command line tool makes it easy to validate, normalize, and diff schemas, loaded from the standard input, the file system, or from your MySQL database.</p><p>To try out <code>schemadiff</code> yourself, <a href="https://github.com/planetscale/schemadiff">check out the <code>schemadiff</code> repository&#x27;s</a> releases page for the latest binaries available for Linux and Mac. The README also contains additional information on how to use the tool, when you might want to use it, and how to build it from source if you want to target another platform.</p><p>Let&#x27;s take a look at a few use cases.</p><h3 id="describe-a-database"><a href="https://planetscale.com/blog/schemadiff-command-line-tool#describe-a-database">Describe a database</a></h3><p>The <code>load</code> can be used with a source to show what&#x27;s your existing database:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">schemadiff</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> load</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-source</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'myuser:mypass@tcp(127.0.0.1:3306)/test'</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Output:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> `</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">t</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">` (</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	PRIMARY KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> `</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">` (</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`name`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">128</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> DEFAULT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> ''</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	PRIMARY KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>It can also accept a string to prettify and normalize it, which can make a schema much easier to read:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9">echo</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "create table t (id int(11) unsigned primary key)"</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> |</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> schemadiff</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> load</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Output:</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> `</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">t</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">` (</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> unsigned,</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	PRIMARY KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>Attempting to load an incorrectly formatted SQL file will result in an error. This can be extremely useful as part of a CI/CD pipeline to validate change scripts before they are applied to your database:</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">schemadiff</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> load</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-source</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> mydb.sql</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ></span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> /dev/null</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ||</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> echo</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> "FAIL"</span></span>
<span class="line"></span></code></pre></div></div><h3 id="showing-changes"><a href="https://planetscale.com/blog/schemadiff-command-line-tool#showing-changes">Showing changes</a></h3><p>The <code>diff</code> command can compare two schemas and write the necessary DDL to execute to get them in sync. This is how we generate our change statements when merging branches in PlanetScale.</p><div class="code-block" data-language="bash"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">schemadiff</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> diff</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-source</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> 'myuser:mypass@tcp(127.0.0.1:3306)/test'</span><span style="--shiki-light:#0B6EC5;--shiki-dark:#73C7F9"> -</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">-target</span><span style="--shiki-light:#414141;--shiki-dark:#C1C1C1"> /path/to/repo/source/code/schema</span></span>
<span class="line"></span></code></pre></div></div><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">DROP</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> VIEW</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `v`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> MODIFY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> COLUMN </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">CREATE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> `</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB">t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">` (</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`id`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">	`name`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">128</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">NOT NULL</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> DEFAULT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> ''</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">	PRIMARY KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>For more use cases, be sure to review the <a href="https://github.com/planetscale/schemadiff/blob/main/README.md">README</a> for this project. The <code>schemadiff</code> command line tool supports MySQL 8 syntax and is released under <a href="https://github.com/planetscale/schemadiff/blob/main/LICENSE">Apache 2.0 license</a>.</p><p>We hope you find it useful!</p>]]></content>
        <summary><![CDATA[We are releasing schemadiff, an open source command line tool to generate diffs between two MySQL databases.]]></summary>
      </entry>
    
      <entry>
        <title>The challenges of supporting foreign key constraints</title>
        <link href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints" />
        <id>https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints</id>
        <published>2023-12-05T09:00:00.000Z</published>
        <updated>2023-12-05T09:00:00.000Z</updated>
        
        <author>
          <name>Shlomi Noach</name>
        </author>
        
        <author>
          <name>Manan Gupta</name>
        </author>
        
        
        <category term="engineering" />
        
        <category term="vitess" />
        
        <category term="mysql" />
        
        <content type="html"><![CDATA[<p>Today, <a href="https://planetscale.com/blog/announcing-foreign-key-constraints-support">PlanetScale announced</a> support for foreign key constraints.</p><p>This has been in the making for quite a while — around a year in fact. So, what was the problem? Why did it take so long? It&#x27;s something that any and every database supports, right? Yes and no. To support foreign key constraints is one thing. To do so while still delivering <a href="https://planetscale.com/docs/vitess/schema-changes/how-online-schema-change-tools-work">Online DDL</a>, <a href="https://planetscale.com/docs/vitess/schema-changes/deploy-requests#gated-deployments">gated deployments</a>, <a href="https://planetscale.com/docs/vitess/imports/database-imports">online imports</a>, and eventually cross shard support, is another.</p><p>When we launched PlanetScale, we refrained from supporting foreign key constraints, first and foremost because we could not make them work with branching and Online DDL. It was a realization that came while we were building the product, and <a href="https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/">we took it as a fact of life</a>, something we’d have to work around. Around a year ago, we decided to really dive in to see what it would actually take to support them.</p><p>It turns out, we had to overcome challenges in almost every single layer of the product: some due to MySQL limitations, some due to how we want to incorporate foreign key support with PlanetScale’s (through Vitess) Online DDL, and some due to branching and schema analysis logic. As you will see in this post, these are all closely intertwined.</p><h2 id="branching-and-deploy-requests"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#branching-and-deploy-requests">Branching and Deploy requests</a></h2><p>The first challenge we had to deal with was handling branching and deploy requests. With <a href="https://planetscale.com/docs/vitess/schema-changes/branching">PlanetScale branching</a>, the user works in a development environment where they’re free to make schema changes. Once they’re ready to deploy those changes, they submit a <a href="https://planetscale.com/docs/vitess/schema-changes/deploy-requests">Deploy request</a>, which lets them review the schema changes they’ve made before deploying them to production.</p><p>The Deploy request page shows the user the semantic <em>diff</em> between the main (<code>base</code>) branch and their own (<code>head</code>) branch. PlanetScale uses <a href="https://planetscale.com/blog/database-branching-three-way-merge-schema-changes">three-way merge</a> to determine the diffs.</p><p>Supporting the foreign key constraint schema definitions is relatively simple, and is but a matter of understanding the SQL syntax involved. However, the semantic analysis is much more involved. Consider that in MySQL (or in InnoDB, rather, as we will discuss later on), the following rules must apply for any foreign key constraint definition:</p><ul><li>A foreign key constraint is a relationship between a parent table and a child table. As a special case, a table may reference itself as its parent.</li><li>The referenced (parent) table must exist.</li><li>The foreign key reference columns in the parent table must exist.</li><li>The referenced columns in the parent must be indexed in-order. There has to be an index covering the referenced columns in the same order they’re referenced by the constraint. Optionally, the index may proceed to cover more columns.</li><li>The child’s columns must match the referenced parent columns in count and in data type. An <code>INT</code> column on the child may not reference a <code>BIGINT</code> column on the parent. Interestingly, it’s okay for <code>VARCHAR(32)</code> vs <code>VARCHAR(64)</code> in child and parent.</li></ul><p>These rules are strongly enforced by the MySQL server (assuming <code>FOREIGN_KEY_CHECKS=1</code>; things get less consistent when <code>FOREIGN_KEY_CHECKS=0</code>), which means that by the time the user submits their Deploy request, it should be safe to assume that the branch adheres to those rules. However, when PlanetScale evaluates a Deploy request, it not only computes the <em>diff</em> between branches, but also the <em>path</em> towards converting one branch (<code>base</code>) into another (<code>head</code>). That <em>path</em> is a valid sequence of steps which maintain the schema in a valid state at all times. Moreover, it evaluates whether, and how, these steps may be <a href="https://planetscale.com/blog/deploying-multiple-schema-changes-at-once">deployed all at once</a>.</p><p>Foreign key constraints introduce a new complexity to that evaluation. In the simplest form, they may require a specific order of deployment. As a trivial example, suppose we were to create a parent-child pair of tables, such as these (simplified):</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> parent</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, parent_id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">constraint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent_id_fk </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">foreign key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (parent_id) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">references</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent (id));</span></span>
<span class="line"></span></code></pre></div></div><p>The deployment plan must of course first apply the creation of <code>parent</code>, and then the creation of <code>child</code>. The reverse order is invalid because <code>child</code> must reference an existing <code>parent</code> table.</p><p><em>Some</em> changes are eligible to run concurrently. Suppose we have the following:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, ref </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ref_idx (ref));</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, ref </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ref_idx (ref));</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, ref </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ref_idx (ref));</span></span>
<span class="line"></span></code></pre></div></div><p>And, suppose we add foreign key constraints onto <code>t2</code> and <code>t3</code>, such that the diff evaluates to:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t2`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ADD</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> CONSTRAINT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t2_ref_fk`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FOREIGN KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`ref`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">REFERENCES</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON DELETE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ACTION</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t3`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ADD</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> CONSTRAINT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t3_ref_fk`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FOREIGN KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`ref`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">REFERENCES</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t2`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`id`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON DELETE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ACTION</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>The two diffs may run concurrently, even though they both affect table <code>t2</code> (one directly, and one indirectly). More on why this is at all possible when we discuss <a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#online-ddl">Online DDL</a>.</p><p>Even more complex is a scenario where two migrations cannot execute concurrently. Assume the base schema looks like this:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, info </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> not null</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, ts </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">timestamp</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>And that our branch schema is:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, info </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> not null</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, p </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> p_idx (p));</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> t2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, ts </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">timestamp</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, t1_p </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">foreign key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (t1_p) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">references</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> t1 (p) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on delete</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> no</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> action</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>The diffs are:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t1`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ADD</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> COLUMN </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`p`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ADD</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INDEX</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `p_idx`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`p`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ALTER</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> TABLE</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t2`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ADD</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> COLUMN </span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`t1_p`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> int</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ADD</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> INDEX</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t1_p`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`t1_p`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ADD</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> CONSTRAINT</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t2_ibfk_1`</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> FOREIGN KEY</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`t1_p`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">REFERENCES</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C"> `t1`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">`p`</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">ON DELETE</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NO</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> ACTION</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>However, it is impossible to even begin the migration on <code>t2</code> before the migration on <code>t1</code> is fully complete. That’s because MySQL strictly requires an index to exist on the parent table’s referenced columns. Assuming the parent table is already populated, we’re looking at a potentially significant time running the first migration before we can begin the second.</p><p>Last, and of course this is not strictly limited to foreign key constraints, you <em>can go too far</em> with your branch changes. PlanetScale attempts to reduce the diff to a single change per table/view. If a branch differs so much from <code>base</code> that it takes multiple steps to operate on the same table to get it into <code>base</code>, then the Deploy request is not deployable.</p><p>Courtesy of <a href="https://vitess.io/blog/2023-04-24-schemadiff"><code>schemadiff</code></a>, this is all evaluated in-memory at the time a Deploy request is created.</p><h3 id="handling-reverts"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#handling-reverts">Handling reverts</a></h3><p>Within branching, we also had to deal with handling reverts. PlanetScale’s deployments are <a href="https://planetscale.com/docs/vitess/schema-changes/deploy-requests#revert-a-schema-change">revertible</a>. If you deploy a schema change, and then realize you made a mistake, we offer you the option to revert that migration while still keeping all the data that may have changed during and after the deployment.</p><p>There exist some scenarios where <a href="https://planetscale.com/docs/vitess/schema-changes/deploy-requests#when-are-you-unable-to-revert-a-schema-change">reverts are not possible</a>. For example, if you modify a column from <code>TINYINT</code> to <code>INT</code>, complete the deployment, and populate some row with the value of <code>256</code>, then that value is outside <code>TINYINT</code> range, and the change cannot propagate back to the original table.</p><p>Foreign key constraints create a different limitation. Let’s illustrate with a simple example. Say you have <code>parent</code> and <code>child</code> with foreign key relationship. You choose to drop the <code>child</code> table, meaning there’s now no foreign key constraint. You then proceed to <code>DELETE FROM parent</code>. You <em>then</em> wish to revert dropping <code>child</code>. This is <em>possible</em>, but it creates an inconvenient situation: the restored <code>child</code> table will have orphaned rows, for we have deleted all rows from <code>parent</code>.</p><p>The same logic applies to any destruction or editing of a constraint. You can’t trust that the reverted table complies with the constraint, for you may have modified the data in an incompatible way while the constraint was removed.</p><p>PlanetScale will allow you to make such reverts, but we will warn you that the change may not be revertible. The schema will be fine, but orphaned rows are possible.</p><h2 id="query-serving"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#query-serving">Query serving</a></h2><p>The next challenge we dealt with was query serving. At this time, foreign key constraint support is limited to unsharded/single shard databases. We expect to support shard-scoped foreign key constraints in a multi-shard environment, or even cross-shard foreign keys, but we limit the current discussion to a single shard. This means that a query that uses a table that’s in a foreign key relationship will only operate on a single backend database server.</p><p>Vitess, the <a href="https://planetscale.com/blog/what-is-vitess">underlying engine behind PlanetScale</a>, normally optimizes execution of such queries by delegating them to the backend MySQL server. However, a MySQL limitation that affects other critical components of Vitess calls for a different course of action. To understand why, let’s digress and discuss Online DDL. We will then return to complete the query serving story.</p><h2 id="online-ddl"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#online-ddl">Online DDL</a></h2><p>Perhaps the largest challenge so far has been with Online DDL. PlanetScale offers <a href="https://planetscale.com/docs/vitess/schema-changes">non-blocking schema changes</a>, which are very compelling, especially when altering large tables in production. The way they work is depicted in detail in <a href="https://planetscale.com/docs/vitess/schema-changes/how-online-schema-change-tools-work">our Online schema change tools documentation</a>, but, in short, altering a table works as follows:</p><ul><li>We create a new, empty table, termed the &quot;shadow&quot; table, with the same definition as the original table.</li><li>We apply the <code>ALTER</code> statement to the shadow table.</li><li>We copy over all existing data from the original table to the shadow table, taking into consideration the schema difference between the two.</li><li>We follow the changelog on the original table, to capture any ongoing changes, and apply them on the shadow table.</li><li>Finally, when we are satisfied the shadow table is in sync or almost in-sync with the original table, we take a lock to prevent writes to the original table, apply what remaining changelog there may be, and finalize the operation by swapping the two tables. The shadow table takes the original table’s place, and the original table becomes the shadow.</li></ul><p>This technique, used by several tools for online schema changes, cannot work with the existing MySQL foreign key constraints implementation. Online DDL has issues with:</p><ul><li>Tailing the binary logs for child table with a cascading (<code>SET NULL</code> or <code>CASCADE</code>) action.</li><li>Finalizing a foreign key parent table, as children’s foreign key constraints <a href="https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/#changing-a-parent-table">migrate with the old table as it is being swapped</a>.</li><li>Backfilling the <a href="https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/#changing-a-child-table">shadow of a foreign key child table</a>, where as a copy of the child table’s schema, the shadow table itself has a foreign key referencing the same parent.</li></ul><p>Each of these limitations calls for a bespoke solution. Let’s break it down.</p><h3 id="tailing-the-changelog"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#tailing-the-changelog">Tailing the changelog</a></h3><p>To backfill the shadow table, Online DDL uses <a href="https://vitess.io/docs/reference/vreplication/vreplication/"><code>VReplication</code></a>, one of the most powerful components in Vitess. <code>VReplication</code> is the component behind <a href="https://planetscale.com/docs/vitess/imports/database-imports">online imports</a>, materialization, live resharding, Online DDL, and more. It can live stream data from a source (or from multiple sources) to a target (or to multiple targets), while possibly manipulating or aggregating the data in transit.</p><p><code>VReplication</code> works by both reading the existing data on the source table(s), as well as by following the <em>changelog</em>: the live, ongoing manipulation of data. The changelog is available in MySQL as the <em>binary logs</em>. <code>VReplication</code> subscribes as a replica and pulls binary log changes from the source server. This is where a major MySQL limitation hits.</p><h4 id="mysql-foreign-key-constraint-support"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#mysql-foreign-key-constraint-support">MySQL foreign key constraint support</a></h4><p>To the casual MySQL user, MySQL is known to support foreign key constraints. However, this is nuanced. MySQL has a pluggable storage engine architecture. Historically, the company MySQL AB focused on non-transactional storage engines. The MySQL engines did not support foreign key constraints. It was a 3rd party pluggable engine named InnoDB that fast became the engine of choice to most users, offering transactional (MVCC) support, as well as foreign key constraint support. There was some period in time when MySQL began work towards implementing foreign key constraints at the server level, but given how MySQL adopted InnoDB as its primary storage engine (now both under Oracle’s ownership), that effort was dropped.</p><p>The most important implication, for our discussion, of MySQL not supporting foreign keys at the server level, is this: any cascading of operations (say an <code>ON DELETE SET NULL</code> or <code>ON UPDATE CASCADE</code>) is only done by InnoDB. If you <code>DELETE</code> or <code>UPDATE</code> a row on a parent table, and that, in turn, affects rows in a child table, those changes to the child are done internally in InnoDB, and <strong>are never logged to the binary log</strong>.</p><p>Those changes are hidden from anyone consuming the binary log. MySQL <em>trusts</em> the replica server’s InnoDB engine to correctly replay those cascaded writes for it to remain consistent with the primary server.</p><p>Any Change Data Capture (CDC) tool, that tails the binary log or masquerades as a replica, will be <em>missing data</em> when reading a child table’s events when the child table has <code>SET NULL</code> or <code>CASCADE</code> foreign key actions. You cannot reliably replay events on those tables, and you will end up with corrupt data, or trying to apply an impossible statement. By way of experiment (not on your production environment), try removing the foreign key constraints on a <em>replica</em> server, then see how long it takes for replication to break.</p><p>For Online DDL, this means we cannot apply a change to a table that has a <code>CONSTRAINT ... FOREIGN KEY ON [DELETE|UPDATE] [SET NULL|CASCADE]</code>.</p><p>One way of solving this issue would be to make InnoDB log cascaded statements. PlanetScale maintains a <a href="https://github.com/planetscale/mysql-server">fork of MySQL</a>, and we looked into making these changes in MySQL. However, we ended up choosing to implement foreign key constraint logic in Vitess itself. It’s a trade off, with these considerations:</p><ul><li>The InnoDB codebase is very much detached from the MySQL codebase. There are parallel constructs, parallel entities in MySQL and in InnoDB, which are completely different and do not translate well to one another. Making the necessary changes appears to be a substantial undertaking with high risks.</li><li>Adding the necessary logic to Vitess was also a substantial amount of work. However, our Vitess maintainers are authoritative on the topic and had clarity of the scope of work. The risk was low.</li><li>In the future, we would like to be able to support foreign key constraints in a multi-shard environment. No matter how well we patch MySQL, and even if did provide the necessary cascading changelog, foreign keys would always remain an in-server constraint. There <em>is</em> some technology for running cross-server queries via <em>connectors</em>, but it is subpar (or incapable) in its ability to collaborate inside transactions, or in its ability to apply the correct locking. By implementing foreign key logic in Vitess early on, we pave the way towards multi-shard environments in the future.</li><li>It should come as no surprise that there is a downside to implementing foreign key constraint logic in the Vitess level. It requires more locking and more communication with the MySQL server. Vitess optimizes where it can and delegates some queries directly to the underlying server where possible, but there is a non-zero performance impact to using foreign key constraints with Vitess/PlanetScale.</li></ul><p>How Vitess implements foreign key logic is described later on, when we revisit <a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#query-serving-revisited">Query serving</a>. In the meantime, let’s proceed to discuss the next Online DDL limitation.</p><h3 id="altering-a-parent-table"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#altering-a-parent-table">Altering a parent table</a></h3><p>Described in more detail in <a href="https://vitess.io/blog/2021-06-15-online-ddl-why-no-fk/#changing-a-parent-table">this blog post</a>, if you have a foreign key pair of a <code>parent</code> and a <code>child</code>, and you <code>RENAME TABLE parent TO old_parent</code>, the child’s foreign key constraint follows the parent table onto its new name, <code>old_parent</code>. In the internal InnoDB implementation, the parent table retains its memory address, and, in a way, &quot;following the parent to its new name&quot; really means nothing changes in the children’s foreign key references, as they keep using the same pointers. It’s mostly the external facing schema definition that changes to reflect a <code>FOREIGN KEY (...) REFERENCED old_parent (...)</code>.</p><p>Running Online DDL on a parent table, we want to swap the parent table and its shadow. We want the children to be oblivious to the swap. We want them to point to the <code>parent</code> table name. Of course, we need to take it upon ourselves to ensure the replacement table, the shadow table, is compatible with the existing foreign keys. Imagine swapping in a shadow table that doesn’t have the expected referenced column(s)!</p><p>In <a href="https://github.com/planetscale/mysql-server/commit/bb777e3e86387571c044fb4a2beb4f8c60462ced">this patch to MySQL</a>, we introduce a new server variable, <code>rename_table_preserve_foreign_key</code>. When set to <code>1</code>, a <code>RENAME TABLE</code> preserves the foreign key definition on the children by pinning it to the name of the table.</p><h3 id="altering-a-child-table"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#altering-a-child-table">Altering a child table</a></h3><p>Suppose we alter a foreign key child table. Say we just modify some column from <code>INT</code> to <code>BIGINT</code>. The foreign keys that exist on the table should be reflected in the shadow table. For the duration of the Online DDL operation, however, the shadow table is incomplete. It it not in sync with the original table, and may be missing rows that exist in the original table, or may contain rows that do not exist anymore in the original table. But if this shadow table has a foreign key constraint pointing to the same parent as the original table’s parent, then that parent is affected by the very existence of the shadow table. It’s possible for a <code>DELETE</code> on the parent to be rejected due to a matching row existing in the shadow table, even if that row does not exist in the actual child table.</p><p>When backfilling the shadow table, we may choose to <code>SET FOREIGN_KEY_CHECKS=0</code>, but the application and users of the database will naturally expect to work with <code>FOREIGN_KEY_CHECKS=1</code>.</p><p>Moreover, even if we somehow manage to pull through and swap the tables, what happens now with the old child table? It becomes stale, as production traffic keeps changing the new table. But it still keeps the foreign key constraints to the parent. Again, preventing legitimate queries from executing on the parent. If only it were possible to create the shadow table without foreign key constraints, and just add them at the time of the final swap. Alas, in MySQL, foreign key constraint definitions are created in the scope of a child table. To add a foreign key constraint, or to remove a foreign key constraint, is a full table rebuild operation, locking and blocking; the very thing Online DDL was designed to overcome.</p><p>In our patch to the MySQL server, we introduced the notion of <em>internal operations tables</em>, something already well established within Vitess. Essentially, we tell MySQL: ignore any foreign key checks for these special tables. Do not try and validate row data. Do not attempt to cascade anything. It’s as if we <code>SET FOREIGN_KEY_CHECKS=0</code> for specific tables, even while the transaction otherwise applies foreign key logic to other tables.</p><p>This way, MySQL/InnoDB completely ignore our shadow table. It can be out of sync, the parent table won’t mind. And as we swap away the original table, MySQL ignores it under its new name. It will not affect production traffic.</p><h2 id="query-serving-revisited"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#query-serving-revisited">Query serving, revisited</a></h2><p>Earlier, we discussed the reasoning behind Vitess owning foreign key logic. What does it mean for Vitess to own that logic?</p><p><a href="https://vitess.io/docs/concepts/vtgate/"><code>VTGate</code></a> is the Vitess component that handles all query serving. It is a proxy between an app and the underlying database, which intercepts queries and creates concrete execution plans, sent to the backend databases. For example, in a multi-sharded environment, VTGate analyzes which shard or shards should be queried for a given <code>SELECT</code> and possibly combines results from multiple shards before returning them to the app.</p><p>VTGate can make various optimizations based on the fact that it knows the structure of the underlying schema, the sharding scheme, and the production traffic patterns. It can cache, lock, buffer or altogether modify queries. It is now also aware of foreign key constraints and the specific rule actions. We will limit the discussion to unsharded or single-shard (the two look the same to the app) environments.</p><p>Starting with the simplest scenario, an <code>ON UPDATE RESTRICT ON DELETE RESTRICT</code> foreign key can be simply handled by the underlying MySQL server, with no additional planning required from VTGate. A <code>RESTRICT</code> (aka <code>NO ACTION</code>) rule means if you’re deleting or updating a parent row, and such an action would invalidate a child’s row, then the action is rejected. There’s no cascading effect: either your query is rejected, or is accepted and only modifies the parent table. Since there’s no cascading effect, we do not need to account for missing entries in the binary logs.</p><p>Let’s now illustrate the case for an <code>ON DELETE CASCADE</code>. A user issues a <code>DELETE FROM parent WHERE id=7</code>. There could be entries in a <code>child</code> table with matching <code>parent_id=7</code>. If we ran the query directly in MySQL, it would only apply the <code>DELETE</code> on <code>parent</code>, letting InnoDB take care of deleting whatever matching rows are found on <code>child</code>. But those deletes will not be visible in the binary logs. Vitess, therefore, has to take control.</p><p>It must first explicitly <code>DELETE FROM child WHERE parent_id=7</code> — thereby ensuring that any affected rows are recorded in the binary log — and only then issue a <code>DELETE FROM parent WHERE id=7</code>. When faced with this latter <code>DELETE</code> on <code>parent</code>, InnoDB still checks for matching rows in <code>child</code>, but finds none, because Vitess had already purged them.</p><p>At least, that’s the naive outline. The implementation is more complicated:</p><ul><li>The <code>DELETE</code>s on <code>parent</code> and <code>child</code> must be part of the same transaction. The changes made by the statement should be atomic, i.e. all or none are applied.</li><li>While we <code>DELETE FROM child</code>, we must acquire a write lock on <code>parent</code>, specifically for row <code>id=7</code>.</li><li>To do that, we must first issue a <code>SELECT col FROM parent WHERE id=7 FOR UPDATE</code>.</li><li>Using the <code>foreign key column</code> values from the parent select, execute the <code>DELETE FROM child WHERE col in (parent_column_values)</code>.</li><li>The child table could itself be a foreign key parent for another table, and let’s say it also has an <code>ON DELETE CASCADE</code> child. The same logic is applied recursively: lock the relevant rows on <code>child</code>, <code>DELETE</code> from the grandchild, then delete from <code>child</code>, and then back to <code>parent</code>.</li></ul><p><picture class="block"><img alt src="https://planetscale.com/assets-2024/blog/content/challenges-of-supporting-foreign-key-constraints/foreign-keys-animated.gif" loading="lazy"></picture></p><p>As you can see, this implementation introduces more back-and-forth communication between VTGate and the backend MySQL server, as well as having additional locking for correctness.</p><p>To show how weird things can get, let’s also illustrate an <code>ON UPDATE CASCADE</code> scenario. Let’s assume we issue a <code>UPDATE parent SET id=8 WHERE id=7</code>. This is a generalized example; as a rule of thumb, one should avoid modifying <code>PRIMARY KEY</code> values, but foreign keys do not necessarily need to refer to <code>PRIMARY KEY</code> columns, or even to <code>UNIQUE</code> values.</p><p>Similarly to the <code>DELETE</code> scenario, we want to ensure the cascaded updates appear in the binary log. InnoDB won’t do it for us, so we have to do it ourselves. We want to first issue an <code>UPDATE child SET parent_id=8 WHERE parent_id=7</code>, and only then update the <code>parent</code> table. However, we cannot run that update on <code>child</code>, because there is yet no <code>parent</code> row where <code>id=8</code>!</p><p>In this scenario, Vitess must turn off <code>FOREIGN_KEY_CHECKS</code> for the update. This leads Vitess to take responsibility for the integrity of the data, as disabling <code>FOREIGN_KEY_CHECKS</code> leads to MySQL/InnoDB skipping all the foreign key validations for child and parent. Any <code>ON UPDATE RESTRICT</code> for the grandchild or existence of foreign key value in a different parent table needs to be handled by Vitess.</p><p>More complex scenarios involve self-referencing tables or statements with non-literal values. They all take a great amount of attention. Later on, we will explain how we test all the above and what gives us confidence that we aren’t missing anything.</p><h2 id="database-imports"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#database-imports">Database imports</a></h2><p>PlanetScale lets you <a href="https://planetscale.com/docs/vitess/imports/database-imports">import your data</a> from an external MySQL environment, and without interruption to that environment. Like Online DDL, Imports utilize <code>VReplication</code>. The external MySQL servers are outside PlanetScale&#x27;s control, and, of course, will not include any of the changes discussed above. In light of that, how can PlanetScale use <code>VReplication</code> to import a database that has foreign key constraints, specifically with <code>SET NULL</code> or <code>CASCADE</code> actions?</p><p>Normally, <code>VReplication</code> moves data from a source (or multiple sources) to a target (or multiple targets) by alternating these two operations:</p><ul><li>Transactionally reading existing rows from the source table(s) and copying them over.</li><li>Tailing the changelog (binary logs) and applying them to capture ongoing changes.</li></ul><p><code>VReplication</code> takes a surgical approach where it keeps track of per-transaction GTID (global transaction IDs, also known as positioning). When it reads existing rows data, it marks the GTID value that applies to the read transaction. That read will take a while on large tables, and during that time, new events will modify the tables. When <code>VReplication</code> switches to applying the changelog, it only evaluates events occurring <em>as of</em> the last read transaction. A third, &quot;fast-forward&quot; smaller phase then glues the next read transaction with the remaining changelog events up to that transaction. <code>VReplication</code> is also able to entirely skip changes to rows that it has not copied yet, by way of reducing unnecessary work.</p><p>With cascading foreign key rules, information is lost. <code>VReplication</code> may attempt to import a child table that has, for example, <code>FOREIGN KEY ... ON DELETE CASCADE</code>. As parent rows get deleted, so do matching rows on the child. However, those deletes on the child never make it to the binary logs. <code>VReplication</code> cannot trust that the binary log is complete.</p><p>In closer examination, if the <code>DELETE</code> on the parent makes it to the binary log, and is captured by <code>VReplication</code> and replayed on the target server, won’t the InnoDB engine also replay the cascade on the target server? Yes — assuming the relevant row is already in position on the target, but that’s not guaranteed. At any given time, only parts of any certain table will have been copied. You cannot apply the <code>DELETE</code> to row <code>1000</code> on the target, if you’ve only copied rows <code>1..500</code>.</p><p>The solution for database Imports is to import the existing table data in a single, large bulk, followed by tailing the changelog. The two do not alternate. For people familiar with MySQL operations, this is not dissimilar to a point-in-time recovery method, where you first restore a full snapshot of the database, followed by applying a long sequence of binary logs. In this approach, all replayed events necessarily operate on existing data. And while there are still no events for some of the child table changes, we can trust the InnoDB engine on the target side to replay them appropriately.</p><p>It’s worth noting that Imports is not expected to work in conjunction with other <code>VReplication</code> operations, i.e. we won’t be running, in PlanetScale, an Online DDL on a table that is being imported. This distinction is important because the import process relies on InnoDB to apply cascading changes, which in turn means some changes are missing in the <em>target’s</em> changelog. In the future, and because some of Vitess’ workflows do integrate with each other, we can expect <code>VReplication</code> to apply changes to foreign key tables through the query serving mechanism discussed above.</p><h2 id="testing-foreign-key-constraint-support"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#testing-foreign-key-constraint-support">Testing foreign key constraint support</a></h2><p>These changes call for rigorous testing. We use multiple testing techniques, from unit testing to end-to-end (e2e) testing, from planned to unplanned scenarios. Here, we illustrate two specific test suites:</p><ul><li>Application-oriented e2e stress tests with Online DDL.</li><li>Fuzzer stress tests, comparing Vitess to MySQL behavior.</li></ul><h3 id="application-stress-tests"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#application-stress-tests">Application stress tests</a></h3><p>In this test suite, we create a hierarchical foreign key structure of tables, and emulate an app that writes to those tables. The app keeps tracks of the changes it makes and the response it gets from the (Vitess) database. At the end of the test, we compare the app’s expectations with the actual table data.</p><p>That’s just the high level description. The suite also:</p><ul><li>Sets up a variety of foreign key situations, with one parent, two children, and one grandchild table.</li><li>Ensures high concurrency of writes.</li><li>Ensures high contention writes take place on all tables. Changes made by different connections are highly likely to collide and conflict.</li><li>Tests all foreign key rules: <code>ON DELETE NO ACTION / SET NULL / CASCADE</code> and <code>ON UPDATE NO ACTION / SET NULL / CASCADE</code>. Cascading (<code>SET NULL</code> and <code>CASCADE</code>) rules add to the write contention even more.</li><li>Runs the stress tests first without, and then with, Online DDL, on each of the hierarchy tables.</li><li>The underlying database cluster uses MySQL replication, with foreign key constraints completely removed on the replica.</li></ul><p>Let’s take a look at some deeper impact of this test design.</p><p>The four table structure showcases a parent-only table, two child-only tables, one table which is both parent and child, and a multi-child scenario. This hierarchy does not represent all possible variants of foreign key relationships (for example, it does not depict a multi-parent setup, or a self-referencing table), but does have enough variance to cover the MySQL fork changes, as well as the fundamental VTGate logic and locking scenarios. We use a classic foreign key relationship: child tables reference the <code>PRIMARY KEY</code> of the parent. This is to assist with expectation management. For other scenarios, see <em>fuzzer tests</em> discussion, below.</p><p>The app emulation uses classic <code>INSERT</code>, <code>UPDATE</code>, and <code>DELETE</code> statements, randomly generated. The app does not know in advance what end result it generates. However, it intentionally limits the range of affected rows to a small enough scope, that, with concurrency and with many cycles, creates high contention on all table rows. Some of the app’s auto-generated queries will have no effect and will go to waste. For example, the app may randomly attempt a <code>DELETE FROM stress_parent WHERE id=7 AND updates=1</code>. Possibly, <code>id=7</code> row does not exist, and the statement affects zero rows. In high volumes, however, many queries will have actual effect. The app records the success and effect of any of its statements, and is able to finally conclude how many rows it expects to find in the table, and what actual data should be there.</p><p>We run a test where <code>ON DELETE</code> has <code>NO ACTION</code> rule, and a test where <code>ON DELETE</code> has <code>CASCADE</code> rule, etc. We run combinations of rules. In a <code>CASCADE</code> scenario, it is difficult, or impossible, given the random nature of the query design, for the app to know what data to expect in children tables following a <code>DELETE</code> on a parent. How can we verify that VTGate has made the correct choices, and has cascaded the <code>DELETE</code> correctly? This is where MySQL replication comes in handy.</p><p>As we illustrated before, VTGate implements a <code>ON DELETE CASCADE</code> by first applying the <code>DELETE</code> on children (and, recursively, first on grandchildren) before applying on the parent. As we mentioned, by the time InnoDB sees the <code>DELETE</code> on the parent, there’s nothing for it to cascade. We expect the binary logs to fully represent our cascading logic. As mentioned above, we use a MySQL replica, and on that replica we actually strip away all foreign keys on all tables via <code>ALTER TABLE ... DROP FOREIGN KEY</code>. MySQL allows that. However, the InnoDB engine on the replica now has no knowledge of foreign keys, and will not attempt to replay/reproduce any InnoDB cascading logic made on the primary server. That might break replication! At the very least, it will cause data drift between parent and child, and due to the high contention nature of the test this will soon lead to an unresolved inconsistency.</p><p>If VTGate does everything right, though, the data will remain consistent. If VTGate leaves nothing for the InnoDB engine to cascade on the primary server, then no binlog events will be missing on the replica. The replica will be able to replay VTGate’s explicit cascaded operations and to remain consistent with the primary server. The test suite does exactly that, and validates not only that replication is unbroken at the end of each test, but also that both primary and replica report the same data metrics.</p><p>Foreign key constraints in MySQL introduce more locking, by nature, and, as we’ve seen, Vitess adds even more locking scenarios. These workloads ultimately run into locking scenarios. The test ensures we are still able to make progress and that we don’t end up in a complete lock down. It goes without saying that any database can be overwhelmed by too much traffic or by too many connections, and it’s always possible to shoot yourself in the foot by introducing extremely contentious scenarios. The test suite keeps load under reasonably contentious control.</p><p>Some of the tests suffice with the above workload, and some add an Online DDL operation on top. While the workload runs, and under high contention, we pick any of the tables and run Online DDL, wait for the cut-over to complete, and, only then, give the green light to complete the test. This tests the changes in the MySQL server under load. We verify that no rows are lost when the original and shadow tables are swapped. We verify that no child table ends up with orphaned rows. We verify none of the shadow tables, or the aftermath artifact tables, has any logical effect on the app’s traffic.</p><h3 id="fuzzer-tests"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#fuzzer-tests">Fuzzer tests</a></h3><p>We initially started with adding tests for individual cases and queries as we went along adding support for them. But we soon realized that with the amount of possibilities of <code>CASCADE</code>s, <code>SET NULL</code>s, <code>RESTRICT</code>s and their interactions with each other, we would have to add a lot of tests to ensure everything worked. So, instead, we tried a different approach. We started with a set of 20 tables having foreign key relations amongst each other, such that we had good coverage of all the different possibilities. We ensure that we had cases like having a cascade rule on a child that has another cascade rule, having a restrict on a child that has a cascade rule, amongst others. Then, we wrote a fuzzer to generate different DML queries to hammer the database and verify we do the right thing in Vitess.</p><p>We introduced two distinct types of tests. The first type involves single concurrency tests, where we initiate a Vitess cluster alongside a separate MySQL instance. Both instances are initialized with identical schemas, and we execute the same queries on both through a single thread. This approach guarantees deterministic output, serving as a validation that Vitess and MySQL exhibit consistent behavior for all DML queries supported by Vitess.</p><p>The second type of tests encompasses multi-concurrency scenarios. In this case, we exclusively establish a Vitess cluster and initiate multiple threads, each executing DML queries concurrently. This particular test is designed to ensure that Vitess implements adequate locking mechanisms, preventing any correctness issues when numerous concurrent DML queries are executed. The primary goal is to ascertain that the database remains consistent throughout this concurrent operation.</p><p>As we added support for different query types, we kept expanding the fuzzer’s range of generated queries to ensure the correct implementation of each addition. Adding fuzzer tests to our suite was incredibly useful. They helped uncover issues that would have been hard to find using manual queries alone. Let’s take a look at some problems identified by the fuzzer and how we fixed them.</p><h4 id="updating-a-child-table-with-cascade-foreign-key-and-grandchild-restrict-foreign-key"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#updating-a-child-table-with-cascade-foreign-key-and-grandchild-restrict-foreign-key">Updating a child table with <code>CASCADE</code> foreign key and grandchild <code>RESTRICT</code> foreign key</a></h4><p>Our first test failure came when updating a table that has a child with <code>CASCADE</code> foreign key and a grandchild with <code>RESTRICT</code> foreign key, such that the update doesn’t cause an actual row change on the child.</p><p>Clearly, from the length of that header, we are talking about a very specific scenario!</p><p>Consider the scenario where you have the following schema:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> parent</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col)) Engine </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> InnoDB;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">foreign key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">references</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent(col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on delete cascade</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> on update cascade</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) Engine </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> InnoDB;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> grandchild</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">foreign key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">references</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child(col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on delete</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> restrict </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> restrict) Engine </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> InnoDB;</span></span>
<span class="line"></span></code></pre></div></div><p>We initially have the following data in the three tables:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent (id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child (id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> grandchild (id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>Now, if you run a query like <code>update parent set col = 2</code>, it succeeds on MySQL. But Vitess was failing this query with a <code>Cannot delete or update a parent row: a foreign key constraint fails</code> error.</p><p>Upon diving further in, we found out the issue. When Vitess receives a query that requires cascades, we issue a <code>SELECT</code> query on the parent to lock the rows that are being updated, and then update the child first. As stated before, this update would fail on MySQL if we don’t run it with foreign key checks off. But, because we are running the query with foreign key checks turned off, it falls on Vitess to validate the <code>RESTRICT</code> foreign keys as well.</p><p>The update we construct for the child table looks like <code>update child set col = 2 where col in (2, 3)</code>. To validate that none of the rows in the grandchild would prevent the updates from going through, Vitess was running a <code>JOIN</code> query to ensure that none of the rows being updated had a foreign key constraint with matching rows.</p><p>The SELECT query we used for validation looked like this:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> grandchild</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  join</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> grandchild</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  where</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> IN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  limit</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"></span></code></pre></div></div><p>From the outset, this looks correct. We are trying to find if there are any rows in the <code>grandchild</code> table that match the <code>col</code> of the child table for the rows being updated.</p><p>But here is the kicker. The row <code>(2,2)</code> is not actually resulting in any update! So, MySQL still allows the update to go through, because it doesn’t cause the data in <code>grandchild</code> to become orphaned!</p><p>The fix wasn’t too hard once we understood the problem. All we had to do was also exclude the rows that weren’t actually changing in the <code>SELECT</code> Vitess was running for verification.</p><p>So, we updated the query to look like this, and then everything worked as intended:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> grandchild</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  join</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> grandchild</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> =</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  where</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> IN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  and</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">.</span><span style="--shiki-light:#7D5903;--shiki-dark:#FED54A">col</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> NOT</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> IN</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">2</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">  limit</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082"> 1</span></span>
<span class="line"></span></code></pre></div></div><p>This case illustrates just how powerful the fuzzer is and how good it is at finding cases that are rare enough that we wouldn’t have written manual tests for.</p><h4 id="arithmetic-operations-on-a-varchar-column-causing-0-vs-0-problems"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#arithmetic-operations-on-a-varchar-column-causing-0-vs-0-problems">Arithmetic operations on a <code>VARCHAR</code> column causing <code>0</code> vs <code>-0</code> problems</a></h4><p>Consider the scenario where you have the following schema:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> parent</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col)) Engine </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> InnoDB;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> child</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col), </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">foreign key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">references</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent(col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">on delete cascade</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> on update cascade</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) Engine </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">=</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> InnoDB;</span></span>
<span class="line"></span></code></pre></div></div><p>And let’s say we initially have the following data in the three tables:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent (id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child (id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span></code></pre></div></div><p>Now, if you run a query like <code>update parent set col = col * (col - (col))</code>, then Vitess would end up with inconsistent data in the database:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> parent;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| id | col  |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|  </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> | </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">-</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> *</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> child;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">| id | col  |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">|  </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">1</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> |  </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">0</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">   |</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">+</span><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">----+------+</span></span>
<span class="line"></span></code></pre></div></div><p>After investigation, we found out that the problem was coming from the part where we are doing arithmetic operations on a varchar column.</p><p>Vitess first runs a <code>SELECT</code> query to get the final updated values for non-literal updates. Vitess runs <code>SELECT id, col, col * (col - (col)) from parent</code>. It then uses the output of this query to cascade the update onto the child. The problem was that the result of <code>col * (col - (col))</code> evaluation is a value <code>-0</code> of type <code>FLOAT</code>.</p><p>This caused Vitess to issue a query like <code>update child set col = -0 where col IN (&#x27;-5&#x27;)</code>. MySQL, however, interprets <code>0</code> and <code>-0</code> as the same values and ends up setting the col to <code>0</code> causing inconsistencies.</p><p>Once we realized the issue, the fix was again not too difficult. All we had to do was type cast the expression into the type of the column. Now, Vitess issues a query like <code>SELECT id, col, CAST(col * (col - (col)) AS CHAR) from parent</code> and this in turn causes the update to look like <code>update child set col = &#x27;-0&#x27; where col IN (&#x27;-5&#x27;)</code>, thus fixing the issue.</p><p>It is valuable to appreciate that the problem only surfaces when the arithmetic expression evaluates to <code>-0</code>. If it evaluates to any other value, it doesn’t cause any issues. This case illustrates just how useful the fuzzer is in running so many queries that it is eventually able to find cases that only happen in very specific scenarios requiring multiple conditions to be met.</p><h4 id="parent-table-unique-key-locking-issue"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#parent-table-unique-key-locking-issue">Parent table unique key locking issue</a></h4><p>While adding <code>REPLACE INTO</code> support for foreign keys and making adequate changes to fuzzer, we discovered that select queries executed for foreign key cascade were not able to acquire an adequate level of lock on the unique key due to missing gap locks. This led to incorrect results, and further, plan execution would result in missing cascading rows, leading to incomplete data in the binary logs.</p><p>Let’s look at a simple example:</p><div class="code-block" data-language="sql"><div class="max-w-full min-w-0"><pre class="shiki shiki-themes planetscale-light planetscale-dark" style="--shiki-light:#2b2b2b;--shiki-dark:#e1e1e1;--shiki-light-bg:#ebebeb;--shiki-dark-bg:#1a1a1a" tabindex="0"><code><span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">drop</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> if</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> exists</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">create</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> table</span><span style="--shiki-light:#5E49AF;--shiki-dark:#B7A5FB"> some_table</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">bigint</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">,</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">    col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">varchar</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">10</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">),</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    primary key</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (id),</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">    unique</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> index</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">(col)</span></span>
<span class="line"><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) ;</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table(id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">null</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">), (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">4</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Session 1</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">begin</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">or</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)  </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">for</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Session 2</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">begin</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ((</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'5'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">for</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- This should block</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Session 1</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">delete</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">or</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> id </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">) ;</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">insert into</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table(id, col) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">values</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> (</span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">3</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">, </span><span style="--shiki-light:#D92038;--shiki-dark:#FF7082">5</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">);</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">commit</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span>
<span class="line"></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Session 2</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- Unblocked but returns 0 rows</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">select</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">from</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> some_table </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">where</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> col </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">in</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1"> ((</span><span style="--shiki-light:#13862E;--shiki-dark:#75DB8C">'5'</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">)) </span><span style="--shiki-light:#F35815;--shiki-dark:#F35815">for</span><span style="--shiki-light:#F35815;--shiki-dark:#F35815"> update</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"><span style="--shiki-light:#818181;--shiki-dark:#A1A1A1">-- This repeatable query in Session 2 now returns 1 row</span></span>
<span class="line"><span style="--shiki-light:#F35815;--shiki-dark:#F35815">rollback</span><span style="--shiki-light:#2B2B2B;--shiki-dark:#E1E1E1">;</span></span>
<span class="line"></span></code></pre></div></div><p>As a solution, we went ahead using the <code>NOWAIT</code> lock to promptly acquire the lock for cascade selection. <code>NOWAIT</code> ensures immediate lock acquisition or failure, which may result in more foreign key-related DMLs failing, necessitating query or transaction rollback.</p><p>This approach, however, effectively addresses the problem of lock waiting and prevents incorrect results.</p><h2 id="foreign-key-constraints-support-limitations"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#foreign-key-constraints-support-limitations">Foreign key constraints support limitations</a></h2><p>There still exist some limitations in our support of foreign key constraints. Many have been touched on throughout this article, but we will recap them here:</p><ul><li>While self-referencing tables are supported, cyclic foreign key references between different tables is not allowed.</li><li>Foreign key constraints names change on every deployment. This is largely due to the MySQL limitation (compatible with ANSI SQL specification) where constraint names must be unique to the schema.</li><li>Foreign key constraints are currently only supported in unsharded environments.</li><li>There are some scenarios where schema reverts can create orphaned rows.</li></ul><p>For a full list of limitations, see our <a href="https://planetscale.com/docs/vitess/foreign-key-constraints#limitations">foreign key constraint documentation</a>.</p><h2 id="summary"><a href="https://planetscale.com/blog/challenges-of-supporting-foreign-key-constraints#summary">Summary</a></h2><p>In summary, we faced several challenges in supporting foreign key constraints in Vitess and PlanetScale, but are extremely pleased with where we landed.</p><p>If lack of foreign key constraint support has been a barrier to you trying PlanetScale in the past, we welcome you to enable foreign key constraint support in your database settings page, and give PlanetScale a try. If you have any questions, don’t hesitate to <a href="https://planetscale.com/contact">reach out to us</a>.</p>]]></content>
        <summary><![CDATA[Today, PlanetScale launched support for foreign key constraints. This article covers some of the behind-the-scenes technical challenges we had to overcome to support them.]]></summary>
      </entry>
    
    </feed>