<script data-pm-proxy="intercept"></script><?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Microservices Insider]]></title><description><![CDATA[A deep dive into the world of microservices, exploring architectures, system design, best practices, scalability, and real-world implementations. All with a bit of Java, Spring, SQL and NoSQL.]]></description><link>https://microservicesinsider.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!40_j!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdb9c4167-fb03-43de-9f73-18a253e3c2b5_706x706.png</url><title>Microservices Insider</title><link>https://microservicesinsider.substack.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 04 Sep 2026 01:47:55 GMT</lastBuildDate><atom:link href="/__u/microservicesinsider.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Yoandy]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[microservicesinsider@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[microservicesinsider@substack.com]]></itunes:email><itunes:name><![CDATA[Yoandy]]></itunes:name></itunes:owner><itunes:author><![CDATA[Yoandy]]></itunes:author><googleplay:owner><![CDATA[microservicesinsider@substack.com]]></googleplay:owner><googleplay:email><![CDATA[microservicesinsider@substack.com]]></googleplay:email><googleplay:author><![CDATA[Yoandy]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[When a single MySQL instance isn't enough]]></title><description><![CDATA[A single MySQL instance isn't enough when you start scaling. Here you'll learn how Database Sharding works with virtual shards.]]></description><link>https://microservicesinsider.substack.com/p/when-a-single-mysql-instance-isnt</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/when-a-single-mysql-instance-isnt</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Sun, 26 Jul 2026 12:30:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!A7wE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When starting a new project, developers usually choose simple, widely-known technologies that work perfectly for 95% of use cases. The problem arises when the business begins to scale. Databases like MySQL or PostgreSQL offer native replication mechanisms based on the primary/replica pattern. This means all writes hit a single instance, while reads can eventually be distributed across any of the replicas.</p><p>Once you reach tens of millions of users and hundreds or thousands of transactions per second, issues start to surface&#8212;such as lock contention, replication lag, slow queries, and data no longer fitting into RAM, which leads to frequent disk reads. All of this significantly degrades service quality. While you can always scale vertically by adding more CPU or RAM, that approach has a hard ceiling: vertical scaling isn&#8217;t infinite.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Back in 2011, Instagram had just three engineers and over 25 million users. They were running on a single primary PostgreSQL instance, and their breaking point wasn&#8217;t storage space&#8212;it was RAM. When the dataset stopped fitting into memory, every write started hitting the disk, causing p95/p99 latency to skyrocket. Even adding replicas couldn&#8217;t solve the issue.</p><p>Pinterest hit the exact same wall with MySQL, but with a twist: they initially tried to scale by throwing different technologies at the problem (Cassandra, MongoDB, Membase). Their takeaway? Every new system introduced a completely new set of failure modes and trade-offs.</p><p>Ultimately, they returned to MySQL, but this time partitioned&#8212;applying the Database Sharding pattern.</p><p>Understanding sharding is fundamental if you work with large-scale systems.</p><h3>What problem does Sharding solve?</h3><p>Before resorting to sharding in a relational database, there is a sequence of optimization steps typically taken first. Sharding is the last resort.</p><ul><li><p><strong>Vertical Scaling:</strong> Upgrading CPU, adding RAM, using faster storage hardware (SSD, NVMe), tuning file systems, etc.</p></li><li><p><strong>Replicas:</strong> Offloading read operations to dedicated instances that consume data from the primary writer via replication. Note that not all reads can go to a replica; replica data is only eventually consistent, meaning there will always be some degree of replication lag or delay.</p></li><li><p><strong>Partitioning:</strong> Data remains on a single instance but is internally partitioned (often horizontally/vertically depending on scheme, e.g., range partitioning by date). For instance, all July data lands in its own partition, resulting in smaller, local indices and allowing the query engine to apply optimizations like partition pruning and partition-wise joins.</p></li><li><p><strong>Sharding:</strong> Splitting rows of the same table across independent nodes, also known as horizontal sharding.</p></li></ul><p>Implementing sharding solves the single-writer bottleneck by distributing both write and read workloads across independent database instances, spreading the overall operational volume across multiple distinct nodes.</p><blockquote><p>If you want to dive deeper into system design and patterns, check out <a href="https://systemdesign.sacavix.com/">SACAViX System Design</a>. It features real system design exercises, technical interview prep, key terminology, and a complete guide explaining 95 must-know patterns</p></blockquote><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!qJz-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 424w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 848w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 1272w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!qJz-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png" width="1321" height="576" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:576,&quot;width&quot;:1321,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 424w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 848w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 1272w, /__u/substackcdn.com/image/fetch/$s_!qJz-!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa40eaa61-ccd5-4024-a42c-6b9253f38190_1321x576.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The shard key: The most critical decision</strong></p><p>Choosing the partition key (shard key) is the single most important decision when implementing sharding. This choice is fundamental because it directly dictates whether future queries will remain simple or become extraordinarily complex.</p><p>A well-chosen shard key should allow for &#8220;point queries&#8221; (routing directly to a single node) while avoiding what is technically known as <strong>Scatter-Gather</strong>.</p><ul><li><p><strong>Single-Shard Queries (Point Queries):</strong> The system routes the query directly to a specific node without needing cross-shard joins, making execution simple and efficient.</p></li><li><p><strong>Scatter-Gather:</strong> There is no way to know which node holds the requested data. The system must query multiple nodes and aggregate the results, drastically increasing complexity and degrading performance.</p></li></ul><p><strong>Example:</strong></p><p>If the shard key is <code>user_id</code> and you need a user&#8217;s profile information, you only query the specific shard hosting that user&#8212;and that&#8217;s it:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;aa6706ea-e8ab-4e8d-a9ac-a7d0e0fc3ea4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">SELECT * FROM orders WHERE user_id = 456</code></pre></div><p>En cambio, si la clave es <code>user_id</code>, pero por cuestiones del negocio tienes que hacer una consulta como la siguiente, entonces la shard key correcta no ser&#237;a <code>user_id</code>.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;5993cc24-a2b4-4dd2-b341-51ba2a7d48aa&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">SELECT * FROM orders WHERE status = &#8216;PENDING&#8217;</code></pre></div><p>In this latter case, to find pending orders across all users, you would have to execute the query across every shard and then aggregate the results.</p><h3>Properties of a good shard key</h3><p>The core properties to look for when choosing a shard key are:</p><ul><li><p><strong>High cardinality:</strong> Having many distinct values allows for fine-grained distribution across shards. Poor choices include fields like <code>country</code> (only ~200 values) or <code>status</code> (<code>active</code>, <code>inactive</code>). Excellent choices include <code>user_id</code>, <code>customer_id</code>, or <code>account_number</code>.</p></li><li><p><strong>Uniform distribution:</strong> Both data volume and incoming traffic must be spread as evenly as possible. Otherwise, the system creates <strong>hotspots</strong>&#8212;where a handful of shards absorb almost all the load.</p></li><li><p><strong>Alignment with dominant Access Patterns:</strong> Your most frequent and critical queries should naturally include the shard key in their <code>WHERE</code> clause.</p></li><li><p><strong>Immutability:</strong> Once written, a partition key should never be updated or modified.</p></li></ul><p>The cardinal sin of sharding is executing <strong>cross-shard queries</strong>. If rendering a user&#8217;s feed requires querying 4,000 nodes and merging the responses in the application layer, you haven&#8217;t solved a scaling problem&#8212;you&#8217;ve simply traded it for latency and availability issues. Latency becomes tied to the slowest responding shard (tail latency), and if even a single node out of 4,000 goes down, the entire query fails. <strong>Always avoid cross-shard operations and cross-shard joins.</strong></p><h3>How do you determine which shard a record belongs to?</h3><p>The most common strategies are:</p><ul><li><p><strong>Range-Based sharding:</strong> Users 1 to 1M live on Shard A, 1M to 2M on Shard B, and so on. While simple to reason about and highly efficient for range scans, it creates major <strong>hotspots</strong>: newly registered users (who are typically the most active) all land on the latest shard.</p></li><li><p><strong>Hash-Based sharding:</strong> Assigning shards using a function like <code>hash(key)%N</code>. This distributes data uniformly and prevents hotspots. However, using modulo <span>N </span>comes with a severe flaw: if <span>N</span> represents the total number of servers and you add a new one, the modulo result changes for almost every row, forcing a near-total database migration. This is a massive operational headache.</p></li></ul><p>To solve the challenge of scaling from <span>N</span> to <span>N+1</span> nodes without massive data movement, two popular rebalancing algorithms are often used: <strong>Consistent Hashing</strong> and <strong>Jump Consistent Hash</strong>&#8212;though these fall outside the scope of this article.</p><h4><strong>Virtual Shards</strong></h4><p>Both Instagram and Pinterest solved the challenge of adding new nodes by introducing the concept of <strong>virtual shards</strong>. Virtual shards break the direct mapping between keys and physical database servers.</p><p>Without virtual shards, routing is typically straightforward. For example, with 8 physical servers, you might route like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;0c4e3817-0cb6-4597-95f7-1726e7076f5b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">server = user_id % 8</code></pre></div><p>User 31,341 <span>% 8 = 5 then </span>this user resides on server 5.</p><p>As the business grows, you add a 9th node and change the modulo to <code>%9</code>. Now, <code>31,341 % 9 = 6</code>. The user has shifted to a new server. And it&#8217;s not just one record: switching from modulo 8 to modulo 9 changes the destination for almost every row in your database. </p><p>Adding a server forces a massive database re-shuffling&#8212;something practically impossible to pull off in a live production environment. Every time you scale, you alter the mapping function, and changing the mapping function relocates virtually everything.</p><p><strong>The solution: Adding an immutable intermediate layer</strong></p><p>Instead of mapping the key directly to a physical server, we map it to a <strong>virtual shard</strong>. This number&#8212;for instance, 8,192 virtual shards&#8212;is fixed forever and never touched again:</p><pre><code><code>shard_virtual = user_id % 8192      // Fixed. Never change.</code></code></pre><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!A7wE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 424w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 848w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 1272w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!A7wE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png" width="1456" height="764" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:764,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 424w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 848w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 1272w, /__u/substackcdn.com/image/fetch/$s_!A7wE!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87eb5cbe-d1f4-4454-856a-f01dba8dcdcc_2280x1197.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The divisor is now a fixed system constant, not the number of physical machines. User 31,341 evaluates to <code>31,341% 8192 = 6981</code>&#8212;and it will evaluate to 6981 today with 8 servers, and tomorrow with 800. The key-to-shard mapping function is immutable.</p><p>Now, where does virtual shard 6981 physically live? That is defined by a second lookup table&#8212;one that is small, lightweight, and mutable:</p><pre><code><code>shard   0    &#8211; 1023  &#8594;  mysql-01
shard 1024   &#8211; 2047  &#8594;  mysql-02
...
shard 6144   &#8211; 7167  &#8594;  mysql-07 ## &lt;&lt;&lt;&lt;&lt;
shard 7168   &#8211; 8191  &#8594;  mysql-08</code></code></pre><h3><strong>How is this implemented in practice?</strong></h3><p>Writing the actual code implementation is something any AI assistant can easily handle, so I will focus on the high-level design principles to give you a clear architectural picture.</p><p>Once you&#8217;ve selected your shard key (or ensured it can be derived from an ID), the first step is defining the virtual sharded mechanism. To do this, you implement a database structure like the following:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;sql&quot;,&quot;nodeId&quot;:&quot;d0dbe28d-ec84-41f1-9000-f910182d4e3c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-sql">CREATE DATABASE IF NOT EXISTS shard_topology;
USE shard_topology;

-- Inventory of physical MySQL servers hosting the shards.
CREATE TABLE physical_server (
  server_id   VARCHAR(32)  NOT NULL,            -- &#8216;mysql-01&#8217;
  host        VARCHAR(255) NOT NULL,            -- &#8216;mysql-01.internal&#8217;
  port        INT          NOT NULL DEFAULT 3306,
  role        ENUM(&#8217;PRIMARY&#8217;,&#8217;REPLICA&#8217;) NOT NULL DEFAULT &#8216;PRIMARY&#8217;,
  status      ENUM(&#8217;ACTIVE&#8217;,&#8217;DRAINING&#8217;,&#8217;OFFLINE&#8217;) NOT NULL DEFAULT &#8216;ACTIVE&#8217;,
  updated_at  TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                                  ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (server_id)
) ENGINE=InnoDB;

-- The map where all the magic happens: each of the 8,192 virtual shards
-- points to ONE physical server and ONE database on that server.
CREATE TABLE shard_map (
  virtual_shard SMALLINT UNSIGNED NOT NULL,     -- 0 .. 8191
  server_id     VARCHAR(32) NOT NULL,
  db_name       VARCHAR(64) NOT NULL,           -- &#8216;shard6981&#8217;
  updated_at    TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
                                  ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (virtual_shard),
  KEY idx_server (server_id),
  CONSTRAINT fk_shard_server
    FOREIGN KEY (server_id) REFERENCES physical_server(server_id)
) ENGINE=InnoDB;

-- Global version counter: incremented on EVERY reshard operation.
-- The application polls it every few seconds; if it changes, the map is reloaded.
CREATE TABLE topology_version (
  id      TINYINT NOT NULL DEFAULT 1,
  version BIGINT UNSIGNED NOT NULL DEFAULT 1,
  PRIMARY KEY (id),
  CHECK (id = 1)
) ENGINE=InnoDB;
INSERT INTO topology_version (id, version) VALUES (1, 1);</code></pre></div><p><code>topology_version</code> serves as the cache invalidation mechanism. The application caches the mapping table in memory; to know when to reload it, it simply executes the lightweight query below every few seconds instead of continuously fetching all 8,192 rows.</p><p>At the application level, the flow looks like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;plaintext&quot;,&quot;nodeId&quot;:&quot;31ce4e8d-2737-4ffb-a89c-0720541cc0c5&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-plaintext">user_id  --(% 8192)--&gt;  virtual_shard  --(shard_map)--&gt;  (server_id, db_name)
                                              |
                              server_id --&gt; HikariDataSource (pool cacheado)
                                              |
                              getConnection() + setCatalog(db_name)</code></pre></div><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;78f28fbe-c03a-40b8-bd8c-ed048c253e4c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Component
public class ShardRouter {

    public static final int NUM_VIRTUAL_SHARDS = 8192;

    private final TopologyRepository topology;
    private final ShardProperties props;

    // UN pool por servidor f&#237;sico. serverId -&gt; DataSource.
    private final Map&lt;String, HikariDataSource&gt; pools = new ConcurrentHashMap&lt;&gt;();
    // El mapa cacheado: virtual_shard -&gt; mapping.  Se reemplaza at&#243;mico al recargar.
    private volatile Map&lt;Integer, ShardMapping&gt; shardMap = Map.of();
    private volatile long loadedVersion = -1;

    public ShardRouter(TopologyRepository topology, ShardProperties props) {
        this.topology = topology;
        this.props = props;
    }

    @PostConstruct
    public void init() { reload(); }

    /** Paso 1: user_id -&gt; shard virtual. Determinista, nunca cambia. */
    public int virtualShard(long userId) {
        return (int) (Long.remainderUnsigned(userId, NUM_VIRTUAL_SHARDS));
    }

    /** Ruteo por objeto: el shard viaja embebido en los bits altos del id. */
    public int virtualShardOfObject(long objectId) {
        return (int) ((objectId &gt;&gt;&gt; 46) &amp; 0xFFFF);
    }

    /**
     * Paso 2 y 3: shard virtual -&gt; (servidor, db) -&gt; conexi&#243;n ya posicionada.
     * El caller usa la Connection y la cierra (vuelve al pool).
     */
    public Connection connectionForUser(long userId) throws SQLException {
        return connectionForShard(virtualShard(userId));
    }

    public Connection connectionForObject(long objectId) throws SQLException {
        return connectionForShard(virtualShardOfObject(objectId));
    }

    private Connection connectionForShard(int vshard) throws SQLException {
        ShardMapping m = shardMap.get(vshard);
        if (m == null) throw new IllegalStateException(&#8221;Shard sin mapear: &#8220; + vshard);

        HikariDataSource pool = pools.get(m.serverId());
        if (pool == null) throw new IllegalStateException(&#8221;Sin pool: &#8220; + m.serverId());

        Connection c = pool.getConnection();
        c.setCatalog(m.dbName());   // &lt;-- el &#8220;USE shardNNNN&#8221;: salta a la base del shard
        return c;
    }

    /** Recarga mapa y pools si la versi&#243;n de topolog&#237;a cambi&#243; (reshard). */
    @Scheduled(fixedDelayString = &#8220;${sharding.refresh-interval-ms}&#8221;)
    public void refreshIfChanged() {
        long v = topology.currentVersion();
        if (v != loadedVersion) reload();
    }

    private synchronized void reload() {
        // 1. Asegura un pool por cada servidor activo.
        for (PhysicalServer s : topology.loadServers()) {
            pools.computeIfAbsent(s.serverId(), id -&gt; buildPool(s));
        }
        // 2. Reemplaza el mapa de shards de forma at&#243;mica.
        Map&lt;Integer, ShardMapping&gt; fresh = topology.loadShardMap().stream()
            .collect(Collectors.toMap(ShardMapping::virtualShard, m -&gt; m));
        this.shardMap = fresh;
        this.loadedVersion = topology.currentVersion();
        // (opcional) cerrar y quitar pools de servidores ya sin shards asignados.
    }

    private HikariDataSource buildPool(PhysicalServer s) {
        HikariConfig cfg = new HikariConfig();
        // Sin base por defecto en la URL: elegimos la BD por conexi&#243;n con setCatalog.
        cfg.setJdbcUrl(String.format(
            &#8220;jdbc:mysql://%s:%d/?rewriteBatchedStatements=true&#8221;, s.host(), s.port()));
        cfg.setUsername(props.getShardUser());
        cfg.setPassword(props.getShardPass());
        cfg.setMaximumPoolSize(props.getPoolSizePerServer());
        cfg.setPoolName(&#8221;shardpool-&#8221; + s.serverId());
        return new HikariDataSource(cfg);
    }
}</code></pre></div><p>Example of using the Router in a Spring Boot application.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;a8ade188-6b5f-46ad-8228-e45d280badfc&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@Repository
public class PinRepository {
    private final ShardRouter router;
    private final IdGenerators ids;   // un IdGenerator por shard virtual

    public PinRepository(ShardRouter router, IdGenerators ids) {
        this.router = router; this.ids = ids;
    }

    public long create(long userId, long boardId, String title) {
        int vshard = router.virtualShard(userId);
        long pinId = ids.forShard(vshard).nextId(); // shard embebido en el id
        try (Connection c = router.connectionForUser(userId);
             PreparedStatement ps = c.prepareStatement(
                 &#8220;INSERT INTO pins(local_id, user_id, board_id, title, created_at) &#8220; +
                 &#8220;VALUES (?,?,?,?, NOW())&#8221;)) {
            ps.setLong(1, pinId); ps.setLong(2, userId);
            ps.setLong(3, boardId); ps.setString(4, title);
            ps.executeUpdate();
            return pinId;
        } catch (SQLException e) {
            throw new DataAccessResourceFailureException(&#8221;insert pin&#8221;, e);
        }
    }

    //.....
}</code></pre></div><p>I hope you found this breakdown useful. While virtual sharding is a powerful and elegant pattern, implementing it successfully involves significant real-world complexity.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[High-Scale Credit Card Processing: How distributed systems avoid double charges at thousands of TPS]]></title><description><![CDATA[Making an online purchase is a complex process. Preventing duplicate payments is essential for building trust, which is why major companies implement idempotency and distributed locking.]]></description><link>https://microservicesinsider.substack.com/p/high-scale-credit-card-processing</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/high-scale-credit-card-processing</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Sun, 31 May 2026 21:34:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!m-q2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was once talking to a senior fintech executive who shared a line that stuck with me: 'Nothing is worse for a payment processor than double-charging a customer for the same order.' At that moment, I started thinking about how furious I would be if I bought something online and was charged twice or more for the exact same product or service. Preventing duplicate charges is a foundational element of trust.</p><p>Handling high payment volumes drives processing companies toward distributed architectures. This often entails multiple independently deployed and scaled applications, database systems where strict ACID properties are no longer feasible, asynchronous processing, and more.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Distributed architectures bring an inherent challenge: transaction management. We shift toward eventual consistency issues, which are potentially much more difficult to handle.</p><p>In practical terms, a monolith or a single SQL database lacks the capacity to scale vertically to handle high transactional throughput; that is precisely why a distributed environment becomes necessary.</p><h2><strong>Idempotency: The core of preventing duplication</strong></h2><p>To solve the issue of duplicate payments, the fundamental element for any payment processing entity is guaranteeing the application of the concept of idempotency the moment a payment is received.</p><p>An operation is considered idempotent if executing it N times always produces the exact same outcome. In mathematics: <code>f(f(x)) = f(x)</code>. In payment systems: processing the same request 10 times must charge the customer exactly once.</p><p>For this to work, payment processing systems must receive a unique ID alongside the client&#8217;s request. While a request with that ID is currently in-flight, no other request with the same ID can be processed. Once the operation is completed, the payment processing outcome is stored along with that ID. If another payment attempt is made with that same ID, it is not processed again; instead, the system simply returns the previously saved result.</p><p>Stating it this way is simple, but implementing it within a distributed system requires deep technical expertise from the solution&#8217;s development team.</p><h4><strong>&#8220;Standard&#8221; Architecture of a payment processing system</strong></h4><p>The following diagram outlines, in general terms, the architecture of a payment processing system:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!4jwb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 424w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 848w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 1272w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!4jwb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png" width="832" height="1001" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1001,&quot;width&quot;:832,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:79908,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://sacavix.substack.com/i/200013145?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 424w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 848w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 1272w, /__u/substackcdn.com/image/fetch/$s_!4jwb!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3a744452-dffb-41db-901d-e979d6a4ba38_832x1001.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Clients or merchants within the payment processing ecosystem submit requests to create card payments. The payment processor receives the request and, after security checks, access control, tokenization, and other policies are enforced at the API Gateway, routes it to the card processing system. This system interacts with external downstream systems to execute the actual payment and persists the processing outcome into its database.</p><p>If the client sends an ID alongside their request, the Card Processor must store the transaction outcome in the StorageDB together with that ID. If another payment attempt is made with the exact same ID, the Card Processor will bypass the external system and simply return the previously saved result. While this seems straightforward, this specific design has inherent flaws that prevent it from being truly idempotent, making it highly unreliable.</p><p><strong>Scenarios that expose the flaws in this design:</strong></p><ul><li><p><strong>Race conditions via simultaneous requests:</strong> A client accidentally submits duplicate requests for the same order (using the identical ID) only a few milliseconds apart. The timing is so close that Request 1 is still in-flight and hasn&#8217;t finished processing. Because these systems typically rely on horizontal scaling and load balancers (using Round Robin), the Card Processor service runs on multiple instances. Request 1 lands on Card Processor Instance 1, while Request 2 is routed to Card Processor Instance 3. Since Instance 1 has not yet persisted the completed transaction, Instance 3 remains completely unaware of it, starts its own execution loop, and ultimately triggers a duplicate payment.</p></li><li><p><strong>Transient network failures and retry Storms:</strong> Another scenario demonstrating this fragility involves a transient network failure between the API Gateway and the Card Processor during a request transmission. Because the API Gateway fails to receive a timely response, it executes a retry following its built-in resiliency strategy. However, Request 1 actually <em>did</em> reach the processor successfully, but the gateway never found out. This drops the system into a split-state scenario nearly identical to the race condition described above.</p></li><li><p><strong>Additional edge cases:</strong> Other common failure modes include the StorageDB failing to provide immediate consistency (I highly recommend reading up on the CAP and PACELC theorems), or the system dispatching the payment to the external provider <em>before</em> safely persisting the state as &#8220;in-progress&#8221; within the StorageDB, among others.</p></li></ul><h4><strong>Distributed mutual exclusion</strong></h4><p>If the previous architecture were a monolith, we would rely on a local mutual exclusion mechanism (mutex). In Java, it would look something like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;66ad4f8d-969c-47f2-bb60-630f3efb031b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;

public class IdempotencyLocker {

    // Concurrent map to store a dynamic lock per each idempotency key
    private final ConcurrentHashMap&lt;String, ReentrantLock&gt; locks = new ConcurrentHashMap&lt;&gt;();

    /**
     * Executes a critical section by ensuring mutual exclusion based on an idempotency key.
     *
     * @param key    The unique transaction key (e.g., request UUID from HTTP headers)
     * @param action The business logic or code block to be executed exclusively
     * @param &lt;T&gt;    The return type of the action
     * @return The result produced by the action
     */
    public &lt;T&gt; T executeWithLock(String key, Supplier&lt;T&gt; action) {
        if (key == null) {
            throw new IllegalArgumentException("Idempotency key cannot be null");
        }

        // Atomically retrieves the existing lock or maps a new one if it's absent
        ReentrantLock lock = locks.computeIfAbsent(key, k -&gt; new ReentrantLock());

        // Attempts to acquire the mutual exclusion lock
        lock.lock();
        try {
            // Execute the critical section (e.g., database verification and API third-party calls)
            return action.get();
        } finally {
            // Crucial: always release the lock inside the finally block to avoid deadlocks
            lock.unlock();
            
            // Memory leak mitigation: remove the key from the map 
            // only if no other threads are currently waiting in the queue for this lock
            if (!lock.hasQueuedThreads()) {
                locks.remove(key, lock);
            }
        }
    }
}</code></pre></div><p>When a transaction arrives, the very first thing we do is generate a &#8220;lock&#8221;&#8212;essentially putting a padlock on that specific idempotency key before starting to process the transaction (creating the payment entry in the StorageDB, calling the external processor, and updating the response outcome). Once processing is complete, we &#8220;release&#8221; the lock, opening the padlock. If a second attempt arrives while an active transaction already holds the lock, it will be queued and placed in a waiting state until the in-flight operation finishes. As soon as the current transaction completes, the waiting request wakes up and begins execution; however, since the record was already persisted in the database by the previous execution, the Card Processor will bypass the external downstream provider and simply return the saved result associated with that idempotency key.</p><p>The explanation and code sample above apply specifically to local mutual exclusion. In a highly distributed architecture like the one illustrated in our diagram, a local mutex is simply not enough. We must shift toward a distributed mutual exclusion strategy. The most popular solution for this is leveraging Redis by implementing the microservices pattern known as <strong>Distributed Lock</strong>. The implementation would look like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;856e8f50-f826-4d5c-950e-b642b80e5cc4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class DistributedIdempotencyLocker {

    private final RedissonClient redissonClient;
    private static final String LOCK_KEY_PREFIX = "lock:idempotency:";

    public DistributedIdempotencyLocker(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    /**
     * Executes a critical section by queuing concurrent requests with the same key.
     *
     * @param key    The unique transaction key (e.g., UUID from HTTP headers)
     * @param action The business logic or code block to be executed exclusively
     * @param &lt;T&gt;    The return type of the action
     * @return The result produced by the action
     */
    public &lt;T&gt; T executeWithLock(String key, Supplier&lt;T&gt; action) {
        if (key == null) {
            throw new IllegalArgumentException("Idempotency key cannot be null");
        }

        String redisLockKey = LOCK_KEY_PREFIX + key;
        RLock lock = redissonClient.getLock(redisLockKey);

        boolean isAcquired = false;
        try {
            // PARAMETERS:
            // waitTime = 15s -&gt; Concurrent requests will now QUEUE and WAIT up to 15 seconds.
            // leaseTime = 30s -&gt; Safety lease time in Redis if the instance completely crashes.
            isAcquired = lock.tryLock(15, 30, TimeUnit.SECONDS);

            if (!isAcquired) {
                // This only triggers if the queue wait time exceeds 15 seconds (e.g., a massive timeout downstream)
                throw new IllegalStateException("Lock timeout exceeded for key: " + key + ". Queue wait time expired.");
            }

            // Execute the critical section (e.g., check DB -&gt; call API -&gt; save DB)
            return action.get();

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Thread interrupted while waiting in Redis queue", e);
        } finally {
            // Only release if this specific thread holds the lock active
            if (isAcquired &amp;&amp; lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}</code></pre></div><p>The explanation of how it works is exactly the same as for local mutual exclusion. The fundamental difference now is that the idempotency key map resides in Redis, meaning all Card Processor nodes share and view the exact same state. This way, it makes no difference whether the incoming request lands on Card Processor Instance 1 or Instance 3.</p><h4><strong>Database consistency</strong></h4><p>This is a critical factor that directly impacts idempotent transactions. It is vital that the data store caching the idempotency key and its final outcome provides <strong>strong consistency</strong>. If the database relies on eventual consistency, you run a severe risk: the moment Redis releases the lock, a previously queued request with the same idempotency key will wake up and proceed. If this second request attempts to read the outcome from a read-replica of your StorageDB before the data has fully synchronized, it will find nothing.</p><p>The ultimate success of idempotent architectures relies on a robust mutual exclusion system combined with a <strong>strongly consistent persistence layer</strong>.</p><p>The general end-to-end workflow operates as follows:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!m-q2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 424w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 848w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 1272w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!m-q2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png" width="868" height="635" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:635,&quot;width&quot;:868,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:73640,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://sacavix.substack.com/i/200013145?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 424w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 848w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 1272w, /__u/substackcdn.com/image/fetch/$s_!m-q2!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f013b9e-e31e-4c87-9348-2f0ecf31c1e0_868x635.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>The Steps:</strong></p><ol><li><p><strong>Request Initiation:</strong> The user/client submits a request to create a payment, including the <code>Idempotency-Key</code> in the HTTP headers.</p></li><li><p><strong>Lock Acquisition:</strong> A Card Processor instance receives the request and immediately requests an exclusive lock from Redis.</p></li><li><p><strong>Exclusivity Granted:</strong> Redis grants exclusive processing rights for the request associated with that specific idempotency key.</p></li><li><p><strong>State Initialization:</strong> The Card Processor persists the idempotency key in the StorageDB along with the payment details and an initial status, such as <code>"Running"</code>.</p></li><li><p><strong>External Execution:</strong> The Card Processor invokes the necessary external downstream systems to process the actual payment.</p></li><li><p><strong>State Finalization:</strong> The Card Processor updates and stores the final transaction outcome and status in the database.</p></li><li><p><strong>Lock Release:</strong> The Card Processor requests to release the key in Redis, relinquishing its exclusive processing condition.</p></li></ol><h4><strong>Edge cases and failure modes</strong></h4><p>Although this is a highly robust architecture for managing idempotency in distributed systems, there is at least one vector where&#8212;under very rare conditions&#8212;certain factors can align to cause an exclusive lock acquisition failure, opening the door to a potential duplicate payment: Redis itself.</p><p>While Redis is an extremely battle-tested system, it is not immune to infrastructure failures. The most widely deployed architecture is a <strong>Master/Replica</strong> topology, where data is written to the master node and can be read from either the master or its replica(s). In this setup, two main issues can occur:</p><ol><li><p><strong>Asynchronous replication lag:</strong> Because Redis replication is asynchronous by default, a subsequent request might attempt to read a key from a replica before the state has been fully synchronized.</p></li><li><p><strong>Failover and data loss:</strong> If the master node suffers a sudden crash, a failover mechanism will promote a replica to master. Any data or lock that had not yet been replicated to that specific replica will be permanently lost.</p></li></ol><p>More powerful consensus algorithms with a different level of complexity, such as <strong>Raft</strong>, can completely eliminate these specific vulnerabilities. We will deep dive into that topic in a future article.</p><p>Thanks for reading this far!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The 7 killers of microservices architecture]]></title><description><![CDATA[We explore the anti-patterns that ruin the implementation of a microservices architecture]]></description><link>https://microservicesinsider.substack.com/p/the-7-killers-of-microservices-architecture</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/the-7-killers-of-microservices-architecture</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Sun, 29 Mar 2026 20:22:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CTr1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The promise of microservices is always seductive: infinite scalability, independent deployments, separate teams, better governance, and so on. However, for many teams, this promise becomes an operational problem. It&#8217;s not that the architectural style itself is flawed, but rather that the way it&#8217;s implemented acts as a silent killer, eroding the benefits until the system is more fragile than the monolith it was meant to replace.</p><p>In many scenarios, these operational problems don&#8217;t appear on day one, but rather over time, manifesting as operational failures, increased latency, coordination difficulties between teams, component failures that ultimately trigger cascading problems, and so forth. In most cases, this stems from the implementation of poor practices across the platform, not from a single component.</p><h2>What problem do microservices solve?</h2><p>To better understand this, it&#8217;s important to first examine what problem microservices solve.</p><p>In my opinion, the best definition of this concept is offered by Martin Fowler. I&#8217;ve simplified it in the following paragraph:</p><blockquote><p>It is an architectural style that structures an application as a set of small, autonomous, and flexibly coupled services, organized around business capabilities and communicating with each other often using lightweight communication mechanisms.</p></blockquote><p>This style addresses the following problems by offering the advantages illustrated below.</p><ul><li><p>Independent scalability: Allows scaling only the services that need it, without affecting the entire application.</p></li><li><p>Faster and more secure deployments: Each service can be updated without redeploying the entire system, reducing risks. Significant improvement in time-to-market.</p></li><li><p>Better code maintainability: Divides large applications into smaller parts, easier to understand and modify.</p></li><li><p>Autonomous teams: Different teams can work on different services without blocking each other.</p></li><li><p>Fault isolation: An error in one service does not necessarily impact the entire system.</p></li><li><p>Technological flexibility: Each service can use the language or technology best suited to its needs.</p></li><li><p>Agile business evolution: Enables rapid implementation of changes without affecting other areas of the system.</p></li><li><p>Improved domain modeling: Facilitates the application of concepts such as Domain-Driven Design, aligning services with business capabilities.</p></li><li><p>Complexity management: Divides large systems into more manageable components.</p></li><li><p>Resource optimization: Allows for more efficient use of infrastructure according to the needs of each service. (Although it is much more expensive overall).</p></li></ul><p>All these points sound great, but to achieve them you have to avoid certain practices that are already known in the market as the anti-patterns of microservices architecture.</p><h3>Microservices killers</h3><p>The following are the 7 fundamental killers of microservices architecture.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!CTr1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 424w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 848w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!CTr1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png" width="1057" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1057,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:255582,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://microservicesinsider.substack.com/i/192540570?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 424w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 848w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 1272w, /__u/substackcdn.com/image/fetch/$s_!CTr1!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F706e8e1b-bd5f-460b-a690-ad063dcf925c_1057x736.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><h4>Problem 1: Shared databases</h4><p>This is perhaps the most common killer. It occurs when multiple services read from and write to the same database schema.</p><ul><li><p>It eliminates autonomy: If Service A changes the name of a column, it breaks Service B.</p></li><li><p>It eliminates the freedom to choose the appropriate database engine for each domain.</p></li><li><p>It eliminates data ownership; with multiple systems and likely several teams, it becomes unclear who owns each piece of data.</p></li><li><p>It limits scalability, or when scaling is implemented, there is a risk that one system could end up affecting the database and, consequently, impacting the stability of the platform.</p></li></ul><p>Coupling ends up being shifted from the code to the data layer.</p><h4>Problem 2: Distributed Monolith</h4><p>This is a classic problem that occurs in some implementations. It happens when coupling is so high that it&#8217;s impossible to leverage the clear advantages of microservices related to independence.</p><ul><li><p>Waterfall deployments: The ability to truly deliver continuous service is lost. If Service A requires the exact version of Service B to function, the risk of errors in production increases exponentially.</p></li><li><p>Complexity without benefit: You pay the &#8220;network tax&#8221; (latency, serialization) without gaining the agility of microservices.</p></li><li><p>Runtime fragility: If one service goes down, the entire system collapses because dependencies are rigid and synchronous instead of being decoupled by events.</p></li><li><p>Cognitive overload: Developers must know the state of multiple repositories and services just to make a small change to business logic.</p></li></ul><p>Ultimately, the monolith problem ends up being distributed across multiple teams, without any benefit.</p><h4>Problem 3: Chatty services API</h4><p>This problem is more common in teams with limited experience adopting microservices. When an inexperienced team begins migrating to microservices, it&#8217;s very common to turn &#8220;anything&#8221; into a small service. This is extremely dangerous and leads to excessive communication between components to resolve a request.</p><ul><li><p>Accumulated latency: The total response time for the user is the sum of all internal network calls, resulting in a poor user experience and a degraded overall API level.</p></li><li><p>Inefficient resource Consumption: Each call involves TLS negotiation (if present), HTTP headers, and JSON/gRPC serialization, which unnecessarily consumes CPU and network bandwidth. Furthermore, it leads to having more services and therefore a larger infrastructure to maintain.</p></li><li><p>Larger failure surface: The more calls, the greater the likelihood that a timeout or a 500 error in an intermediate service will break the entire transaction. Although this can be mitigated by applying stability patterns such as timeout limiters, retries, or circuit breakers, it unnecessarily increases the possibility of failures, and the fallacies of distributed computing must always be considered.</p></li><li><p>Interface coupling: The client (or the calling service) must know too many internal details of other services&#8217; structures to construct a complete response.</p></li></ul><p>Excessive communication occurs when there are too many services due to incorrect context delimitation during the architecture design.</p><h4>Problem 4: The God Service</h4><p>This problem usually arises when there is a central, complex, and extremely fundamental service in the execution of the business process. This is the service that some companies call &#8220;the service&#8221; or &#8220;the API.&#8221; It has complex logic, is large, and few people touch it, or no one wants to touch it. An error in this service has a high operational impact on the business.</p><p>Some problems with having a god service are:</p><ul><li><p>Organizational bottleneck: Waiting queues are created in teams because everyone needs to modify the same service for their respective functionalities.</p></li><li><p>Inefficient scaling: You are forced to scale giant instances of a service (with a lot of RAM/CPU) just because a small functionality within it is under heavy load or so important that it&#8217;s better to have it scaled &#8220;just in case.&#8221;</p></li><li><p>Long compilation and test times: The service becomes so heavy that unit and integration tests take too long, slowing down the CI/CD pipeline.</p></li><li><p>Resistance to change: The code becomes &#8220;spaghetti&#8221; and difficult to refactor due to the enormous number of responsibilities mixed together. Sometimes it&#8217;s better to &#8220;leave it alone,&#8221; as doing so poses a very high risk.</p></li></ul><p>Having a &#8220;god service&#8221; is sometimes a normal part of architecture during a transition from monolithic to microservices decomposition. What is NOT normal is failing to recognize the need to work on improving or decomposing it into simpler components adapted to the business&#8217;s capabilities.</p><h4>Problem 5: Shared Infrastructure</h4><p>This problem is more general than Problem 1 (shared database), but it&#8217;s common for not only the database to be shared. In some microservices implementations, other components are also shared, which negatively impacts the architecture. Examples include deployment nodes (VMs, VPSs, etc.), shared distributed caching systems like Redis, and so on.</p><p>The fundamental problems with sharing infrastructure are:</p><ul><li><p>A traffic spike in a low-priority microservice can exhaust resources (CPU, memory, I/O) and affect a critical service hosted on the same node.</p></li><li><p>Single point of failure (SPOF): If the shared Redis or Kubernetes cluster isn&#8217;t properly partitioned, a misconfiguration in one area can affect the entire ecosystem.</p></li><li><p>Difficulty in cost management: It&#8217;s nearly impossible to determine how much each service consumes individually if they all share the same resources without clear labels or boundaries.</p></li><li><p>Configuration conflicts: Limitations in environment variables or library versions at the operating system level that affect all services equally.</p></li></ul><h4>Problem 6: Nanoservices</h4><p>This is the opposite of a monolith. It occurs when we see extremely simple applications in the architecture, for example: a service that performs a simple calculation, such as taking an amount and calculating the VAT by simply adding 21% of its value, or whose logic is to return a person&#8217;s age based on their date of birth, etc.</p><p>If it is too simple or does not cover a bounded context (DDD), it is probably a bad idea to convert it into a service. I personally like to look at how many tables or entities in the database the service has to manage. If it is only one, for example, a User&#8217;s Address, it is a red flag to consider.</p><p>Nanoservices involve some significant problems:</p><ul><li><p>High operational complexity: Managing 50 services for simple logic implies configuring 50 CI/CD pipelines, 50 monitoring systems, and 50 security schemes.</p></li><li><p>High latency: A simple business operation that should take 20ms in memory ends up taking 500ms due to jumps between services (resulting in chatty services).</p></li><li><p>Data inconsistency: Maintaining data integrity across so many small services forces the use of distributed transactions (Sagas) for things that shouldn&#8217;t be so complex.</p></li><li><p>Knowledge fragmentation: It&#8217;s difficult to understand the complete flow of an application when the logic is scattered across insignificant pieces of 10 lines of code each.</p></li></ul><h4>Problem 7: Everything is a Service</h4><p>Closely related to the previous problem, this is more of the same. Every new feature to be developed is conceived as a microservice and not as a feature in itself, or ultimately, as a reusable library that will be used by several components.</p><p>Thinking of everything as a service ends up combining all the negative aspects of the previous anti-patterns:</p><ul><li><p>Nanoservices are created.</p></li><li><p>Maintenance/infrastructure costs increase.</p></li><li><p>Code duplication sometimes occurs.</p></li><li><p>Additional points of failure are introduced.</p></li><li><p>Excessive communication (chatty services) is generated.</p></li></ul><p>Not everything is a service; sometimes it just needs a little organization and to be placed in the right bounded context.</p><p>I hope you enjoyed the article. In these times when everyone&#8217;s talking about AI (including this blog), it&#8217;s good to review some architecture.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p></p>]]></content:encoded></item><item><title><![CDATA[Expose your REST endpoints as MCP tools in Java]]></title><description><![CDATA[Turn your endpoints into MCP tools to be run by LLMs]]></description><link>https://microservicesinsider.substack.com/p/convert-your-rest-endpoints-to-mcp</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/convert-your-rest-endpoints-to-mcp</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Mon, 23 Mar 2026 00:50:07 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!cAwN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Over the past decade, millions of REST APIs have been built for all kinds of industries. However, we are now facing a paradigm shift: the consumer of our APIs is no longer just a React frontend or a mobile application, but an Artificial Intelligence Agent.</p><div><hr></div><p>Spanish version is available in SACAViX Tech Newsletter, read <a href="/__u/open.substack.com/pub/sacavix/p/de-rest-a-mcp-como-exponer-tu-api?r=1vm4at&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=true">here</a> </p><div><hr></div><p></p><p>For an LLM, a traditional REST endpoint is a black box requiring external documentation (OpenAPI) and rigid manual integration. This is where the Model Context Protocol (MCP) comes in. MCP is the standard that allows language models to &#8220;see&#8221; and &#8220;touch&#8221; business logic natively, securely, and, above all, in a standardized way. It&#8217;s an open standard that facilitates the connection of AI assistants with systems where data resides, such as content repositories and enterprise tools. Its goal is to allow language models to access relevant and up-to-date information, improving the quality of their responses. (Thanks to Anthropic for this &#129653;)</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>In this article, we&#8217;ll explore how to transcend the REST backend. Using the power of Spring AI, we&#8217;ll explain the process of exposing a REST API as an MCP service. We&#8217;ll see how to move from data exposure to providing dynamic tools that models can reason, invoke, and orchestrate in real time.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!cAwN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 424w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 848w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!cAwN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png" width="595" height="498" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/acab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:498,&quot;width&quot;:595,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 424w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 848w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 1272w, /__u/substackcdn.com/image/fetch/$s_!cAwN!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Facab08ed-d046-40c5-b979-23d7e9e1b964_595x498.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>What is Spring AI?</h2><p>Spring AI is a Spring ecosystem project designed to facilitate the integration of artificial intelligence (AI) capabilities into Java applications, especially those based on Spring/Boot. Its best feature is that it provides a single interface for interacting with different models; using Spring AI, you can connect to OpenAI, Amazon Bedrock, Vertex Gemini, or a local model in Ollama, among others, without changing your application&#8217;s code. And no, it&#8217;s not what you might think; it&#8217;s not just a client for connecting to LLMs&#8212;it&#8217;s much more than that.</p><h3>From a REST API to an MCP Server</h3><p>Any Spring application that exposes an HTTP REST service can expose its endpoints via MCP, but some concepts change.</p><p>In the REST world, applications expose endpoints; in modern MCP, these are called tools. End of story.</p><p>So, the way to do this is to create a &#8220;Controller&#8221; that exposes the &#8220;tools&#8221; instead of the endpoints.</p><p>Before showing you how to do this with a practical example, it&#8217;s important to understand some things related to the types of transport that MCP supports. Learn more about transports in <a href="https://modelcontextprotocol.io/specification/2025-11-25/basic/transports">MCP official doc</a>.</p><h4>Project dependencies</h4><p>We&#8217;re going to implement our sample REST API and expose endpoints as tools. We&#8217;ll use the streamable HTTP transport mechanism.</p><p>Currently, MCP in Spring is at version 2.x, but it&#8217;s still under development (Milestone versions). We&#8217;ll use version 1.x. It&#8217;s important to note that whether our API uses servlets or the reactive stack, we can do this; it&#8217;s independent.</p><p>The main dependencies are the web starter (to create the standard REST API), and the mcp server webmvc; this is important, if we were using webflux we would have to use the corresponding dependency of the mcp server.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;xml&quot;,&quot;nodeId&quot;:&quot;392abe0e-09b8-4b79-ba8c-6c8415483c20&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-xml">&lt;dependency&gt;
   &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
 &lt;/dependency&gt;

 &lt;dependency&gt;
    &lt;groupId&gt;org.springframework.ai&lt;/groupId&gt;
    &lt;artifactId&gt;spring-ai-starter-mcp-server-webmvc&lt;/artifactId&gt;
 &lt;/dependency&gt;</code></pre></div><h4>REST Controller + Streamable MCP</h4><p>The REST API is the same as always; let&#8217;s see an example in the code.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;4b52a1ff-9f6b-4a00-a70f-b2e87eea2e1b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">@RestController
@RequestMapping(&#8221;/rates&#8221;)
public class ExchangeRateController {

    private final ExchangeRateService exchangeRateService;

    public ExchangeRateController(ExchangeRateService exchangeRateService) {
        this.exchangeRateService = exchangeRateService;
    }

    @GetMapping
    public ExchangeRate getExchangeRate(
            @RequestParam(&#8221;from&#8221;) String from,
            @RequestParam(&#8221;to&#8221;) String to) {
        return this.exchangeRateService.getExchangeRate(from, to);

    }
}</code></pre></div><p>As the code itself explains, it&#8217;s a service that allows you to obtain the exchange rate from a source currency to a destination currency using `GET /rates`.</p><p>Now, <strong>how do we convert this into a tool exposed via MCP so that LLMs can run it?</strong></p><p>In Spring AI, it&#8217;s quite simple; the dependency we included does the &#8220;magic&#8221; behind the scenes.</p><p>You just need to annotate the methods appropriately, and you&#8217;re done. To keep the project clean, I prefer not to &#8220;clutter&#8221; the Spring controllers with MCP annotations. Therefore, I organize the tools in a package called &#8220;tools,&#8221; which would be the equivalent of &#8220;controller&#8221; in an n-tier architecture.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!KOS5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 424w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 848w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 1272w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!KOS5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png" width="413" height="325" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:325,&quot;width&quot;:413,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22961,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://sacavix.substack.com/i/191804394?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 424w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 848w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 1272w, /__u/substackcdn.com/image/fetch/$s_!KOS5!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F904ea6b7-175d-4cee-901b-50f86f12b0f1_413x325.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the tools folder, we add as many as we need; we can follow a structure based on resources or however we want.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;java&quot;,&quot;nodeId&quot;:&quot;b72a6f58-bd93-44e8-b802-dd7d48de5600&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-java">import com.yoandypv.exchangerate.apirest2mcp.service.ExchangeRate;
import com.yoandypv.exchangerate.apirest2mcp.service.ExchangeRateService;
import org.springaicommunity.mcp.annotation.McpTool;
import org.springaicommunity.mcp.annotation.McpToolParam;
import org.springframework.stereotype.Component;

@Component
public class ExchangeRateTool {

    private final ExchangeRateService exchangeRateService;

    public ExchangeRateTool(ExchangeRateService exchangeRateService) {
        this.exchangeRateService = exchangeRateService;
    }

    @McpTool(
            name = &#8220;getExchangeRate&#8221;,
            description = &#8220;Get the exchange rate between two currencies using ISO codes (e.g., USD to EUR)&#8221;
    )
    public ExchangeRate getExchangeRate(
            @McpToolParam(description = &#8220;Source currency in ISO format (e.g., USD)&#8221;)
            String from,

            @McpToolParam(description = &#8220;Target currency in ISO format (e.g., USD)&#8221;)
            String to) {
        return exchangeRateService.getExchangeRate(from, to);
    }
}</code></pre></div><p>As you can see, it&#8217;s a simple Spring component that injects the bean with the service logic (which is shared with the REST controller).</p><p>The @McpTool and @McpToolParam annotations.</p><ul><li><p>@McpTool: Allows you to define the tool (&#8221;the endpoint&#8221;). It&#8217;s important that it has a name so that LLMs know how to invoke it, and a clear description that expresses exactly what the tool does, so LLMs know what it&#8217;s used for.</p></li><li><p>@McpToolParam: Used to annotate parameters. Similarly, the description should be clear so that LLMs know what value to use when invoking the tool. It allows other options, such as indicating whether the parameter is mandatory or not.</p></li></ul><h3>MCP Server configuration</h3><p>It is important to define the appropriate configurations for the MCP service to function properly.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;toml&quot;,&quot;nodeId&quot;:&quot;120bd901-fc15-4f5a-91c2-23c5c2d864a4&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-toml">spring.ai.mcp.server.name=exchange-rate-mcp
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.protocol=streamable
spring.ai.mcp.server.stdio=false</code></pre></div><p>That's enough.</p><p>An additional topic is the issue of CORS. Since it&#8217;s HTTP, keep in mind that enabling CORS may be important depending on the client. The example code provides access to everything, but follow CORS best practices if you&#8217;re going to expose the API.</p><p>To connect an MCP client like Claude, Visual Studio Code, Cursor, or any other, the endpoint will be: http://localhost:8080/mcp.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Java is now unstoppable. Pinning in Virtual Threads has been resolved.]]></title><description><![CDATA[The problem of pinned virtual threads has been solved, now Java will scale even further.]]></description><link>https://microservicesinsider.substack.com/p/java-is-now-unstoppable-pinning-in</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/java-is-now-unstoppable-pinning-in</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Sun, 23 Mar 2025 00:58:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a previous article, we talked about virtual threads and the pinning problem. Virtual threads are part of Project Loom, an initiative aimed at revolutionizing Java's concurrency model with three main elements:</p><ul><li><p>The introduction of lightweight and cheaps threads controlled by the JVM to optimize traditional platform threads; initially known as fibers, now called virtual threads.</p></li><li><p>Providing a structured concurrency mechanism to the Java platform.</p></li><li><p>Adapting ThreadLocal for virtual threads through Scoped Values.</p></li></ul><p>As an interesting fact, virtual threads are daemon threads.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="pullquote"><p>If you speak Spanish, you can read this article (and many others) on <a href="/__u/sacavix.substack.com/p/virtual-threads-ahora-mas-escalables">SACAViX's Substack</a>.</p></div><p>In Java 21, <a href="https://openjdk.org/jeps/444">JEP 444</a> was officially released, providing the JDK with "stable" virtual threads. However, the pinning problem still remained to be solved. If you're not very familiar with it, I recommend reading <a href="https://sacavix.com/2024/11/los-hilos-virtuales-y-el-problema-del-pinning/">this article</a> (Spanish version) on the blog.</p><p>To understand virtual threads, you need to know four basic concepts:</p><ul><li><p><strong>Carrier threads</strong>: These are the platform threads on which virtual threads run. By default, the number of platform threads created matches the value of <code>Runtime.getRuntime().availableProcessors()</code>, though this is configurable.</p></li><li><p><strong>Mount</strong>: The process by which the Virtual Thread Scheduler assigns the execution of a virtual thread to a platform thread (putting it on a carrier thread so it can run).</p></li><li><p><strong>Unmount</strong>: The opposite of mounting. If a virtual thread enters a waiting state, the scheduler unmounts it to free up the platform thread, preventing it from staying blocked and allowing it to be used for another task.</p></li><li><p><strong>Pinning</strong>: The issue that occurred before Java 24, where a virtual thread, under certain conditions, remained mounted on a carrier thread, preventing the resource from being freed.</p></li></ul><p>With these concepts and this image, you&#8217;ll be able to see how virtual threads operate on top of platform (carrier) threads.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!DrC5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 424w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 848w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 1272w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!DrC5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp" width="755" height="899" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:899,&quot;width&quot;:755,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="/__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 424w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 848w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 1272w, /__u/substackcdn.com/image/fetch/$s_!DrC5!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4b3268ec-440d-4573-a756-2a259fc095f1_755x899.webp 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h1>Java 21 vs 24: Pinning solved</h1><p>To see if the pinning problem was resolved, we conducted an experiment with a block of code that produces pinning in Java 21 using the Java synchronized primitive.</p><p>The example:</p><pre><code><code>private static final Object lock = new Object();

.....

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i &lt; 100; i++) {
        executor.submit(() -&gt; {
            synchronized (lock) {
                System.out.println(Thread.currentThread());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
    }
}</code></code></pre><p>In short, we create an executor that will execute each task submitted in a virtual thread. We sent it a group of tasks with a block synchronized by a global variable.</p><p>To see if the execution of a code produces pinning, we have the option: <code>-Djdk.tracePinnedThreads=full</code>.</p><p>For the experiment, we executed the previous code with the flag enabled in JDK 21 and 24. We also visualized the execution with the Visual VM profiling tool.</p><h2>Java 21: With pinning</h2><p>Java version used was:</p><pre><code>openjdk version "21.0.6" 2025-01-21 LTS
OpenJDK Runtime Environment Corretto-21.0.6.7.1 (build 21.0.6+7-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.6.7.1 (build 21.0.6+7-LTS, mixed mode, sharing)</code></pre><p>When executing the code block on standard output we have:</p><pre><code>VirtualThread[#27]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#27]/runnable@ForkJoinPool-1-worker-2 reason:MONITOR
    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)
    java.base/jdk.internal.vm.Continuation.onPinned0(Continuation.java:393)
    java.base/java.lang.VirtualThread.parkNanos(VirtualThread.java:635)
    java.base/java.lang.VirtualThread.sleepNanos(VirtualThread.java:807)
    java.base/java.lang.Thread.sleep(Thread.java:507)
    com.yoandypv.vt.PinnedThreadsExample.lambda$sample2$1(PinnedThreadsExample.java:50) &lt;== monitors:1
    java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    java.base/java.lang.VirtualThread.run(VirtualThread.java:329)
VirtualThread[#33]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#35]/runnable@ForkJoinPool-1-worker-8
VirtualThread[#32]/runnable@ForkJoinPool-1-worker-7
VirtualThread[#37]/runnable@ForkJoinPool-1-worker-7
......</code></pre><p>This section is relevant:</p><pre><code>VirtualThread[#27]/runnable@ForkJoinPool-1-worker-2 reason:MONITOR
    java.base/java.lang.VirtualThread$VThreadContinuation.onPinned(VirtualThread.java:199)</code></pre><p>It indicates that some threads remained pinned during execution, specifically that virtual thread #27 was pinned to carrier thread <code>worker-2</code>. This is a problem because the thread remains busy and is not freed to take on other work, which impacts the scalability promised by virtual threads.</p><p>In the code, you'll see <code>Continuation.onPinned</code>&#8212;this is crucial. Virtual threads run thanks to <code>Continuation</code>, which I believe is the real revolution behind Loom; virtual threads are just one application of continuations. We&#8217;ll discuss this in another article.</p><p>If we analyze this execution in VisualVM, it looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!m5oN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 424w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 848w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 1272w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!m5oN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png" width="1456" height="744" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:744,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 424w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 848w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 1272w, /__u/substackcdn.com/image/fetch/$s_!m5oN!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ce42b15-b6df-4345-a3d7-bf7d5f77f909_2244x1146.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The most important thing is that the carrier threads (workers 1 to 8) remain in the WAIT state during execution, which effectively represents a blockage. The pinning problem is present.</p><h2>Java 24: Bye bye pinning</h2><p>With JDK 24 we execute the experiment with the same example, the specific data of the JDK are:</p><pre><code>openjdk version "24" 2025-03-18
OpenJDK Runtime Environment Corretto-24.0.0.36.2 (build 24+36-FR)
OpenJDK 64-Bit Server VM Corretto-24.0.0.36.2 (build 24+36-FR, mixed mode, sharing)</code></pre><p>After code execution the output was:</p><pre><code><code>VirtualThread[#26]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#103]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#132]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#131]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#134]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#133]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#130]/runnable@ForkJoinPool-1-worker-1
VirtualThread[#129]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#128]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#127]/runnable@ForkJoinPool-1-worker-1
....</code></code></pre><p>The first point to note is that the log indicating pinning didn't appear when using the flag. If we go to VisualVM, it looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!8fyf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 424w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 848w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!8fyf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png" width="1456" height="690" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:690,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 424w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 848w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8fyf!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5895839-b9f2-4f64-9e6b-2860779032f1_2224x1054.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Carrier threads are shown in the Parked state. The Parked state indicates in this context that the scheduler has unmounted the virtual thread, avoiding blocking the platform thread. The carrier thread has been "parked" and could receive more work.</p><p>As Neil Armstrong's famous phrase says: </p><blockquote><p><strong>One small step for man, one giant leap for mankind</strong></p></blockquote><h2>Final thoughts</h2><p>I haven't yet fully confirmed that the pinning issue has been resolved in other scenarios, such as those related to JNI. We'll have to wait for this new version to mature a bit and undergo further testing. But the fact that it's resolved for Synchronized is already a fundamental leap forward.</p><p>See you in the next one. Share if you think it might help others learn.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[MySQL query performance: a deep dive into indexes]]></title><description><![CDATA[Learn how MySQL works internally and how to optimize your queries.]]></description><link>https://microservicesinsider.substack.com/p/mysql-queries-perfomance-a-deep-dive</link><guid isPermaLink="false">https://microservicesinsider.substack.com/p/mysql-queries-perfomance-a-deep-dive</guid><dc:creator><![CDATA[Yoandy]]></dc:creator><pubDate>Tue, 11 Mar 2025 01:15:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!k-y8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello developers, <em>welcome everyone to Microservices Insiders</em>. After many years working with MySQL I wanted to write this article to explain how this popular and always reliable database engine works inside.</p><p>The article delves into the internal workings of MySQL, focusing on how data is stored and how indexes function to optimize query performance. Key topics include:</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><ul><li><p><strong>Data Storage in MySQL/InnoDB:</strong> Data is stored in structures called "pages," each typically 16KB in size. These pages contain rows of data, and when one is filled, a new page is created. Pages are grouped into "extents," and extents into "segments."</p></li><li><p><strong>Page Structure:</strong> Each page comprises a header with metadata, the main body with data rows, and a footer acting as an internal directory or index for quick data location within the page.</p></li><li><p><strong>Indexes in MySQL:</strong> Indexes are structures that enhance search operations on data tables, improving retrieval performance. There are two main types:</p><ul><li><p><strong>Clustered Indexes:</strong> In InnoDB, there's a single clustered index per table, built using the primary key. Data is stored in a B-Tree structure, with leaf nodes containing the actual data rows.</p></li><li><p><strong>Non-Clustered (Secondary) Indexes:</strong> These indexes don't store data themselves but hold pointers to data in the data pages.</p></li></ul></li><li><p><strong>Analyzing and Optimizing Queries:</strong> The article emphasizes the importance of understanding how data is stored and indexed to effectively analyze and optimize query performance.</p></li></ul><p>Let&#8217;s start.</p><h2>How is information stored in MySQL?</h2><p>In MySQL, when we create a table and add data, the information is stored in structures called <em>pages</em>. A page is the basic unit of information and has a default size of 16KB. <em>Pages</em> store the rows of data that we add. When a <em>page</em> is full, a new one is created, and so on.</p><p>Two other important concepts for understanding MySQL's internal structure are <em>extends</em> and <em>segments</em>. Extends are a group of contiguous pages, and segments are a collection of extends.</p><p>Regarding pages, there are two main types: <strong>data pages</strong> and <strong>index pages</strong>. In this post, we will discuss both, starting with data pages.</p><p>A data page can be graphically represented as follows:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!k-y8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 424w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 848w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 1272w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!k-y8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png" width="687" height="335" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:335,&quot;width&quot;:687,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74726,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://microservicesinsider.substack.com/i/158813882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 424w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 848w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 1272w, /__u/substackcdn.com/image/fetch/$s_!k-y8!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F41eef1ed-7ee1-468b-855d-170c853a3942_687x335.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Each page contains a header with general information and metadata about the page, such as the number of records it holds and the available space, among other details. The main body of the page contains the data we add to the table, and at the end, there is a kind of directory or internal index for the page, which allows direct access to the position of each record. Do not confuse these indexes with the ones we create; these are specific to the page itself.</p><p>In MySQL with InnoDB, if a table is not explicitly assigned a primary key, one will be implicitly assigned. This ensures that created tables already have an implicit clustered index (we will discuss this later). In other database engines, this does not happen, and in tables without an explicit index defined, the pages are grouped in a structure called a heap.</p><p>As data is added and pages are filled, new pages of information are created.</p><p><strong>How to know how many pages your table has? </strong></p><p>In reality, there is no easy way to know this, and I don&#8217;t think it&#8217;s very relevant. However, if you&#8217;re as curious as I am, with the command I show below, you can get the size of the table. By dividing it by 16K, you can estimate the approximate number of pages (16K is the default size, but this value can be configured).</p><p><code>SHOW TABLE STATUS LIKE 'tu-tabla';</code></p><p>Now that we know how information is stored, let&#8217;s talk about indexes.</p><h2>What is an index in MySQL?</h2><p>An index in MySQL is a data structure that improves the speed of data retrieval operations on a database table. It works similarly to an index in a book, allowing the database to quickly locate and access specific rows without having to scan the entire table. Indexes are particularly useful for speeding up queries that involve filtering, sorting, or joining data.</p><p>When you create an index on a column (or a set of columns), MySQL builds a separate structure that stores a sorted version of the data in that column, along with pointers to the corresponding rows in the table. This allows the database engine to find the required data much faster than if it had to scan the entire table row by row.</p><p>However, while indexes improve read performance, they can slightly slow down write operations (such as inserts, updates, and deletes) because the index must be updated whenever the data in the table changes. Therefore, it&#8217;s important to use indexes strategically, balancing the benefits of faster queries with the overhead of maintaining the index.</p><p>Indexes do not store data; they only store pointers to data that resides in the data pages.</p><p>The <strong>clustered index</strong> in MySQL with InnoDB is built using the primary key, where the data is stored in a BTree structure sorted (fundamentally) by the primary key. Each node of the tree, which is an index page, contains a reference to either a data page or another index page. In the data page, the data is ordered by the primary key, which is why there can only be one clustered index per table. If we do not define a primary key, MySQL will assign one by default. It is called "clustered" because it refers to a group of keys (see the graphic below for better understanding).</p><p>The <strong>secondary index</strong> is any additional index created on a table, used to improve searches on columns other than the PRIMARY KEY. It is also based on a BTree, but it only stores the indexed column and a pointer to the primary key. This allows for faster record retrieval without the need to perform a full scan of the table. Secondary indexes can be simple or composite. A simple index uses a single column, while a composite index uses more than one column.</p><p>Both types of indexes are represented in a BTree structure, as mentioned earlier. A binary tree structure looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!vHZ9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 424w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 848w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!vHZ9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png" width="489" height="301" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:301,&quot;width&quot;:489,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21593,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://microservicesinsider.substack.com/i/158813882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 424w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 848w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 1272w, /__u/substackcdn.com/image/fetch/$s_!vHZ9!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F755e4a97-d53a-4ed6-9b0b-c83c85942b3e_489x301.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In the case of the tree used to represent indexes, the leaves of the tree will be the <strong>data pages</strong>, while the intermediate nodes will be the <strong>index pages.</strong></p><p>Let&#8217;s look at an example of a <strong>clustered index</strong> (based on the primary key).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="/__u/substackcdn.com/image/fetch/$s_!8mKM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="/__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 424w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 848w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_webp, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 1456w" sizes="100vw"><img src="/__u/substackcdn.com/image/fetch/$s_!8mKM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png" width="610" height="578" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:578,&quot;width&quot;:610,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82153,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://microservicesinsider.substack.com/i/158813882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="/__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_424, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 424w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_848, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 848w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_1272, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 1272w, /__u/substackcdn.com/image/fetch/$s_!8mKM!, /__u/microservicesinsider.substack.com/w_1456, /__u/microservicesinsider.substack.com/c_limit, /__u/microservicesinsider.substack.com/f_auto, /__u/microservicesinsider.substack.com/q_auto:good, /__u/microservicesinsider.substack.com/fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51641689-8f40-4b53-ae69-f3e9ef2b8840_610x578.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If, using these tables, a query were executed to retrieve the data of players whose IDs are between 1 and 5, only the leftmost branch of the tree would be traversed, avoiding a full scan of the table. Let&#8217;s see this in practice below.</p><p>The representation of secondary indexes will be covered in another article, but we will see how to handle them next.</p><p><strong>Let&#8217;s look at the importance of indexes.</strong></p><p>Suppose we have a table like the following:</p><pre><code><code>CREATE TABLE employees (
    id INT, 
    name VARCHAR(100),
    last_name VARCHAR(100),
    email VARCHAR(50) UNIQUE,
    department VARCHAR(100), 
    salary DECIMAL(10, 2) 
) ENGINE=InnoDB;</code></code></pre><p>As you can see, it is a simple table that does <strong>not</strong> have a primary key defined. For our example, we have created a stored procedure that will help us populate this table. I&#8217;ll leave the code below in case you&#8217;re interested (I made this procedure with the assistance of DeepSeek AI).</p><pre><code><code>DELIMITER $$

CREATE PROCEDURE InsertRandomEmployees()
BEGIN
    DECLARE i INT DEFAULT 0;
    DECLARE random_name VARCHAR(100);
    DECLARE random_last_name VARCHAR(100);
    DECLARE random_email VARCHAR(50);
    DECLARE random_department VARCHAR(100);
    DECLARE random_salary DECIMAL(10, 2);

    WHILE i &lt; 100000 DO
        SET random_name = CONCAT('Name', FLOOR(RAND() * 100000));
        SET random_last_name = CONCAT('LastName', FLOOR(RAND() * 100000));
        SET random_email = CONCAT('email', i + 1, '@example.com');  -- 
        SET random_department = ELT(FLOOR(1 + RAND() * 5), 'Sales', 'Marketing', 'IT', 'HR', 'Finance');
        SET random_salary = RAND() * 10000;

        INSERT INTO employees (id, name, last_name, email, department, salary)
        VALUES (i + 1, random_name, random_last_name, random_email, random_department, random_salary);

        SET i = i + 1;
    END WHILE;
END$$

DELIMITER ;</code></code></pre><p>As you can see, we inserted about 100K records. Once the table is populated, we execute a query like the following:</p><p><code>select count(*) from employees where department='Marketing';</code></p><p>At first glance, nothing seems wrong&#8212;it is syntactically correct. However, if we analyze the query, we will see:</p><pre><code> explain select * from employees where department='Marketing';
+----+-------------+-----------+------------+------+---------------+------+---------+------+-------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 99661 |    10.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+-------+----------+-------------+
1 row in set, 1 warning (0.00 sec)</code></pre><p>No index could be used, and the query performed a full scan, going through all 100K records in our table. At this rate, as the table continues to grow, the query performance will deteriorate further. (The column "rows" shows 99661 because it is only an approximate value.)</p><p>Let&#8217;s take the EXPLAIN to the next level by using **ANALYZE** for more details:</p><pre><code> explain analyze select * from employees where department='Marketing';
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                               |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -&gt; Filter: (employees.department = 'Marketing')  (cost=10102.35 rows=9966) (actual time=0.037..50.405 rows=20052 loops=1)
    -&gt; Table scan on employees  (cost=10102.35 rows=99661) (actual time=0.017..42.679 rows=100000 loops=1)
 |
+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+</code></pre><p>Here the problem is clearer. Explanation of the result:</p><p><strong>Operation: Filter:</strong></p><ul><li><p><strong>Description:</strong> A filter is applied to select only the rows where `department = 'Marketing'`.</p></li><li><p><strong>Estimated cost</strong>: 10102.35 cost units. This value is an estimate of how much effort MySQL requires to perform this operation.</p></li><li><p><strong>Estimated rows</strong>: 9966 rows. MySQL estimates that approximately 9966 rows will meet the condition `department = 'Marketing'`.</p></li><li><p><strong>Actual time</strong>:`0.037..50.405`: The time it took to perform this operation. The first value is the initial time to find the first row, and the second value is the total time to process all rows.</p></li><li><p><strong>Actual rows</strong>: 20052 rows. This is the actual number of rows that meet the condition.</p></li><li><p><strong>Loops</strong>: 1. Indicates that this operation was executed once.</p></li></ul><p><strong>Operation: Table scan on employees:</strong></p><ul><li><p><strong>Description</strong>: A full table scan is performed on the `employees` table to find the rows that meet the condition.</p></li><li><p><strong>Estimated cost</strong>: 10102.35 cost units.</p></li><li><p><strong>Estimated rows</strong>: 99661 rows. MySQL estimates that it will scan approximately 99661 rows.</p></li><li><p><strong>Actual time</strong>:`0.017..42.679`: The time it took to perform the full table scan.</p></li><li><p><strong>Actual rows</strong>: 100000 rows. This is the total number of rows in the `employees` table.</p></li><li><p><strong>Loops</strong>: 1. Indicates that this operation was executed once.</p><p></p></li></ul><p>If we finally execute the query:</p><pre><code>select count(*) from employees where department='Marketing';
+----------+
| count(*) |
+----------+
|    20052 |
+----------+
1 row in set (0.04 sec)</code></pre><p>The estimates were not wrong&#8212;the actual filtered rows from above were 20052, and the estimated scan time was 0.04.</p><p>While this works, it is not acceptable and needs improvement. Let&#8217;s try making two changes: first, by creating a primary key on the `id` column (this will require rebuilding the table), and second, by creating a secondary index on the `department` column since it is part of the search condition (WHERE clause).</p><p><strong>Adding PK and secondary index:</strong></p><p>(table will be re-created and re-populated again)</p><pre><code>CREATE TABLE employees2 (
    id INT PRIMARY KEY AUTO_INCREMENT, 
    name VARCHAR(100),
    last_name VARCHAR(100),
    email VARCHAR(50) UNIQUE,
    department VARCHAR(100), 
    salary DECIMAL(10, 2) 
) ENGINE=InnoDB;</code></pre><p>New index:</p><p><code>CREATE INDEX idx_department ON employees2 (department);</code></p><p>Once created, let&#8217;s repeat the EXPLAIN.</p><pre><code> explain analyze select * from employees2 where department='Marketing';
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                |
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
| -&gt; Index lookup on employees2 using idx_department (department='Marketing')  (cost=4335.55 rows=39268) (actual time=0.200..32.259 rows=20062 loops=1)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.04 sec)</code></pre><p>Only the created index will be used for the execution of the query in a single iteration.</p><p>The results:</p><pre><code>select count(*) from employees2 where department='Marketing';
+----------+
| count(*) |
+----------+
|    20062 |
+----------+
1 row in set (0.01 sec)</code></pre><p>The query went from taking 0.04 to 0.01. Just 25% of execution time.</p><p>I had fun and learned a lot while writing this article. I hope you enjoyed it too.</p><p><strong>See you in the next one!</strong></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://microservicesinsider.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Microservices Insider! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>