<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kirill Tolmachev</title>
    <description>The latest articles on DEV Community by Kirill Tolmachev (@kirill_tolmachev).</description>
    <link>https://dev.to/kirill_tolmachev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1919339%2Fdec5f47b-960c-4b09-b257-460242d2e99f.jpg</url>
      <title>DEV Community: Kirill Tolmachev</title>
      <link>https://dev.to/kirill_tolmachev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kirill_tolmachev"/>
    <language>en</language>
    <item>
      <title>ORMs Are a Lie We Tell Junior Developers So They Don't Have to Learn SQL</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Fri, 27 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/orms-are-a-lie-we-tell-junior-developers-so-they-dont-have-to-learn-sql-k7h</link>
      <guid>https://dev.to/kirill_tolmachev/orms-are-a-lie-we-tell-junior-developers-so-they-dont-have-to-learn-sql-k7h</guid>
      <description>&lt;h1&gt;
  
  
  ORMs Are a Lie We Tell Junior Developers So They Don't Have to Learn SQL  Every time I see another developer struggle with a performance issue that could be solved in 30 seconds with raw SQL, I wonder: what have we done? We've created an entire generation of backend developers who think databases are magic boxes that respond to method chains.  Here's the uncomfortable truth: teaching ORMs first is like teaching someone to drive using only cruise control and lane assist. Sure, they'll get from point A to point B, but the moment something goes wrong, they're helpless.  ## The Comfortable Lie  ORMs promise productivity. They promise safety. They promise that you never have to think about the database layer again. These promises aren't just false—they're actively harmful.  When you use an ORM, you're not writing database queries. You're writing code that generates database queries. That extra layer of abstraction doesn't eliminate complexity; it hides it. And hidden complexity is the most dangerous kind.  I worked on a project where a single API endpoint was taking 8 seconds to respond. The ORM was generating 47 separate queries to fetch data that could have been retrieved with one well-crafted JOIN. The developer responsible had three years of experience and genuinely didn't understand why &lt;code&gt;User.orders.items.categories&lt;/code&gt; was slow. The ORM made it look so simple.  ## What We're Actually Teaching  When we start junior developers with ORMs, we're teaching them that databases are implementation details. We're teaching them that performance is someone else's problem. We're teaching them to trust magic.  But databases aren't implementation details. They're the foundation everything else sits on. A poorly designed database will cripple your application no matter how clean your business logic is. A poorly understood database will generate bugs that are invisible until production load hits.  The N+1 query problem isn't an edge case. It's a fundamental misunderstanding of how relational data works. When your ORM lets you write &lt;code&gt;user.posts.each { |post| puts post.comments.count }&lt;/code&gt;, you're not writing elegant code. You're writing a database destroyer.  ## The Performance Trap  ORMs optimize for developer convenience, not database efficiency. They generate safe, generic SQL that works in all cases but excels in none.  Your ORM will happily SELECT * from a table with 50 columns when you need just 2. It will use subqueries where JOINs would be faster. It will generate WHERE clauses that can't use indexes. And because it's hidden behind a friendly API, you won't notice until it's too late.  I've seen production systems where adding eager loading (.includes() in Rails, .Include() in Entity Framework) improved performance by 95%. That's not optimization—that's fixing fundamentally broken data access patterns.  ## SQL Is Not That Hard  The real reason we reach for ORMs isn't complexity. SQL is not a difficult language. Basic CRUD operations take minutes to learn. JOINs take an afternoon. Window functions take a weekend.  The real reason is that SQL feels old-fashioned. It doesn't look like the modern languages we use everywhere else. It doesn't have method chaining or fluent interfaces. It looks like what it is: a specialized tool for a specific job.  But specialized tools exist for good reasons. You wouldn't use a Swiss Army knife to build a house, and you shouldn't use an ORM to handle complex data relationships.  ## When ORMs Make Sense  I'm not saying ORMs are always wrong. They excel at simple CRUD operations on single tables. They handle migrations well. They provide useful abstractions for connection pooling and transaction management.  But they should be tools in your toolkit, not the foundation of your data layer. Use them for the easy stuff. Drop down to SQL for the complex stuff. And always, always understand what queries your ORM is generating.  ## Teaching SQL First  Imagine starting junior developers with SQL instead of ORMs. They would:  - Understand exactly what their code is doing to the database - Write more efficient queries from day one   - Debug performance problems with confidence - Design better database schemas - Ask the right questions about data access patterns  Would they be slower initially? Maybe. But they'd be building on solid foundations instead of convenient abstractions.  ## The Way Forward  Stop treating SQL like assembly language. It's not a low-level detail you graduate away from. It's a powerful, expressive language for working with relational data.  Start new developers with SQL. Teach them to write queries by hand. Show them how to read execution plans. Make them understand indexes. Then, and only then, introduce ORMs as productivity tools.  Your junior developers will thank you when they're not debugging mysterious performance issues at 2 AM. Your database will thank you when it's not processing 50 queries instead of 1.  Most importantly, you'll have developers who understand their tools instead of just using them. And that makes all the difference.  ## What You Can Do Right Now  If you're a senior developer, stop accepting "the ORM handles it" as an answer. Require your team to explain the SQL their code generates.  If you're learning, spend a week writing raw SQL before you touch an ORM. Create tables, insert data, write JOINs. Understand what you're abstracting away.  If you're hiring, ask candidates to write a SQL query during interviews. Not a trick question—just a basic JOIN. You'll be shocked how many can't do it.  The lie we tell junior developers isn't protecting them. It's crippling them. Time to tell the truth.
&lt;/h1&gt;

</description>
      <category>sql</category>
      <category>orm</category>
      <category>backend</category>
      <category>programming</category>
    </item>
    <item>
      <title>I Replaced a 2,000-Line Service with a SQL Query. It's Been Running for 3 Years.</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Wed, 25 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/i-replaced-a-2000-line-service-with-a-sql-query-its-been-running-for-3-years-4gb5</link>
      <guid>https://dev.to/kirill_tolmachev/i-replaced-a-2000-line-service-with-a-sql-query-its-been-running-for-3-years-4gb5</guid>
      <description>&lt;h1&gt;
  
  
  I Replaced a 2,000-Line Service with a SQL Query. It's Been Running for 3 Years.
&lt;/h1&gt;

&lt;p&gt;Most developers are afraid of their database. I mean truly, deeply afraid. They'll spend weeks building elaborate application-layer solutions to problems that SQL could solve in fifteen minutes. I've watched brilliant engineers write thousands of lines of Python to do what a window function handles elegantly. Stop it.&lt;/p&gt;

&lt;p&gt;Three years ago, I inherited a service that was the poster child for this madness. A sprawling, 2,000-line Python monster that calculated user engagement scores by pulling data from six different tables, running complex transformations in memory, and somehow managing to be both slow and wrong. The original author had clearly never heard of a JOIN.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem Was Simple (The Solution Wasn't)
&lt;/h2&gt;

&lt;p&gt;We needed to calculate engagement scores for users based on their activity patterns. The score combined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comment frequency over the past 90 days&lt;/li&gt;
&lt;li&gt;Post quality metrics (views, likes, shares)&lt;/li&gt;
&lt;li&gt;Session duration patterns&lt;/li&gt;
&lt;li&gt;Peak activity timing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Python service did this by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetching all user IDs&lt;/li&gt;
&lt;li&gt;For each user, querying comments table&lt;/li&gt;
&lt;li&gt;For each user, querying posts table
&lt;/li&gt;
&lt;li&gt;For each user, querying sessions table&lt;/li&gt;
&lt;li&gt;Loading everything into pandas DataFrames&lt;/li&gt;
&lt;li&gt;Running transformations&lt;/li&gt;
&lt;li&gt;Calculating weighted averages&lt;/li&gt;
&lt;li&gt;Storing results back to the database&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This ran every hour. It took 45 minutes to complete. On good days.&lt;/p&gt;

&lt;p&gt;The service had its own Docker container, its own logging pipeline, its own monitoring dashboard. It consumed more resources than our main API. For what? Math that belonged in the database from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  SQL Doesn't Need Your Permission to Be Powerful
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: Your application logic is probably just reimplementing SQL poorly. Modern databases are phenomenally capable. PostgreSQL has window functions, CTEs, advanced aggregations, and JSON operators that most developers never touch. &lt;/p&gt;

&lt;p&gt;The replacement query took me four hours to write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;user_comments&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;comment_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_comment_length&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; 
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'90 days'&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;post_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_views&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;like_count&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;engagement_rate&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; 
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'90 days'&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;session_patterns&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration_minutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_session_duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;peak_hour&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;user_sessions&lt;/span&gt; 
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="s1"&gt;'90 days'&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="n"&gt;peak_activity&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MODE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;peak_hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;most_active_hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_session_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_duration&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;session_patterns&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;user_engagement_scores&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LEAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GREATEST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;comment_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engagement_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;COALESCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;avg_duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; 
      &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;pa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;most_active_hour&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
      &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;pa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;most_active_hour&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;  
      &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;user_comments&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;user_posts&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;  
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;peak_activity&lt;/span&gt; &lt;span class="n"&gt;pa&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_engagement_scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs in 2.3 seconds. On our largest dataset. Every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Fear the Database
&lt;/h2&gt;

&lt;p&gt;I think the real issue is control. Application code feels safe because you can step through it with a debugger. You can add print statements. You can unit test each tiny piece in isolation.&lt;/p&gt;

&lt;p&gt;SQL feels opaque. Black magic happening inside the database. What if it's slow? What if it locks tables? What if the query planner picks the wrong index?&lt;/p&gt;

&lt;p&gt;But here's what that 2,000-line service taught me: Complex application logic is infinitely harder to debug than complex SQL. When the Python service broke (and it broke constantly), finding the bug meant tracing through layers of abstraction, debugging pandas operations, and hunting down edge cases in the transformation pipeline.&lt;/p&gt;

&lt;p&gt;When the SQL query has issues? You run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; and fix the obvious problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Wasn't Even Close
&lt;/h2&gt;

&lt;p&gt;The old Python service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;45 minutes runtime&lt;/li&gt;
&lt;li&gt;8GB memory usage at peak&lt;/li&gt;
&lt;li&gt;Failed silently when data was inconsistent&lt;/li&gt;
&lt;li&gt;Required constant babysitting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SQL replacement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2.3 seconds runtime
&lt;/li&gt;
&lt;li&gt;Zero memory overhead (database handles it)&lt;/li&gt;
&lt;li&gt;Fails fast with clear error messages&lt;/li&gt;
&lt;li&gt;Runs unattended for three years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We deleted 2,000 lines of code, removed a Docker container, eliminated three monitoring dashboards, and improved performance by 1,100x. &lt;/p&gt;

&lt;p&gt;Sometimes the best code is no code.&lt;/p&gt;

&lt;h2&gt;
  
  
  When SQL Is the Wrong Choice
&lt;/h2&gt;

&lt;p&gt;I'm not advocating for putting all business logic in the database. SQL is terrible for complex branching logic, external API calls, or anything requiring loops and conditionals.&lt;/p&gt;

&lt;p&gt;But for data transformation, aggregation, and analysis? Stop fighting your database. It's optimized for exactly this work.&lt;/p&gt;

&lt;p&gt;The sweet spot: Use SQL for what it's good at (data), use application code for what it's good at (business logic, external integrations, user interactions).&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Action Plan
&lt;/h2&gt;

&lt;p&gt;Look at your codebase right now. Find the services that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull large datasets into memory&lt;/li&gt;
&lt;li&gt;Run aggregations in application code
&lt;/li&gt;
&lt;li&gt;Take forever to execute&lt;/li&gt;
&lt;li&gt;Handle mostly data transformation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask yourself: Could this be a SQL query?&lt;/p&gt;

&lt;p&gt;Don't let fear of the database drive architectural decisions. Your PostgreSQL instance is probably more capable than the last three microservices you built combined.&lt;/p&gt;

&lt;p&gt;The 2,000-line service is gone. The SQL query remains, quietly doing its job every hour for three years. No maintenance, no failures, no drama.&lt;/p&gt;

&lt;p&gt;Sometimes the old ways are better.&lt;/p&gt;

</description>
      <category>sql</category>
      <category>backend</category>
      <category>programming</category>
      <category>database</category>
    </item>
    <item>
      <title>You Don't Need Kubernetes. You Need a Single $40/Month Server.</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Tue, 24 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/you-dont-need-kubernetes-you-need-a-single-40month-server-4bg</link>
      <guid>https://dev.to/kirill_tolmachev/you-dont-need-kubernetes-you-need-a-single-40month-server-4bg</guid>
      <description>&lt;p&gt;Your startup just burned three months and $50k implementing Kubernetes for an app that serves 200 concurrent users. The same workload runs flawlessly on a single DigitalOcean droplet that costs less than your daily coffee budget.&lt;/p&gt;

&lt;p&gt;Stop. Breathe. You've been fooled by the complexity industrial complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Kubernetes Cargo Cult
&lt;/h2&gt;

&lt;p&gt;I've watched brilliant engineers spend weeks debugging why their "hello world" microservice won't start in their 47-node cluster while their competitor ships features on a $5 VPS. We've created a generation of developers who think you need an orchestration platform before you need customers.&lt;/p&gt;

&lt;p&gt;Here's what actually happens when you choose Kubernetes for your 10-user MVP:&lt;/p&gt;

&lt;p&gt;Your simple three-tier app now requires 12 different YAML files. You need to understand ingress controllers, service meshes, persistent volume claims, and network policies. Your deployment pipeline breaks every Tuesday because someone updated a Helm chart. Your monitoring stack uses more resources than your actual application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $40 Server That Could
&lt;/h2&gt;

&lt;p&gt;A modern VPS with 4 CPU cores, 8GB RAM, and SSD storage handles more traffic than most startups will see in their first two years. I've run production systems serving millions of requests monthly on hardware that fits in your laptop bag.&lt;/p&gt;

&lt;p&gt;Here's what this looks like in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Web server&lt;/strong&gt;: Nginx reverse proxy (handles 10k concurrent connections easily)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application&lt;/strong&gt;: Your app in Docker containers, managed by systemd&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database&lt;/strong&gt;: PostgreSQL with proper tuning (yes, on the same box)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt;: Redis for sessions and hot data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt;: Prometheus + Grafana, lightweight setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups&lt;/strong&gt;: Daily snapshots, rsync to object storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total monthly cost: $40. Total management overhead: 2 hours per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  But My Scaling Requirements!
&lt;/h2&gt;

&lt;p&gt;No, you don't have scaling requirements. You have scaling fantasies.&lt;/p&gt;

&lt;p&gt;Your app that struggles to retain 100 daily active users doesn't need horizontal pod autoscaling. You need product-market fit. Your database that stores 50MB of user data doesn't need sharding strategies. You need customers who want to give you money.&lt;/p&gt;

&lt;p&gt;Most "scale problems" are actually performance problems. I've seen single-server setups handle 50,000+ concurrent users with proper caching, database indexing, and optimized queries. The bottleneck isn't your infrastructure. The bottleneck is your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Actually Need Kubernetes
&lt;/h2&gt;

&lt;p&gt;Real talk: Kubernetes solves real problems for real companies. But those companies look different than yours.&lt;/p&gt;

&lt;p&gt;You need Kubernetes when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple teams deploy independently dozens of times per day&lt;/li&gt;
&lt;li&gt;You're running 100+ services with complex interdependencies
&lt;/li&gt;
&lt;li&gt;Compliance requires strict resource isolation and audit trails&lt;/li&gt;
&lt;li&gt;You have dedicated platform engineers (plural) on staff&lt;/li&gt;
&lt;li&gt;Downtime costs more than infrastructure complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice what's missing from that list? "I might need to scale someday" isn't there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Single Server Playbook
&lt;/h2&gt;

&lt;p&gt;Ready to escape YAML hell? Here's your migration plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1&lt;/strong&gt;: Provision a VPS, install Docker, migrate your simplest service. Set up basic monitoring and backups. You'll be shocked how fast it is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2&lt;/strong&gt;: Migrate your database. Use connection pooling, enable query logging, set up automated backups to cloud storage. Benchmark everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3&lt;/strong&gt;: Move remaining services, set up CI/CD that actually works, implement blue-green deployments with simple bash scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 4&lt;/strong&gt;: Enjoy your newfound simplicity while your competitors debug their ingress controllers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Reality Check
&lt;/h2&gt;

&lt;p&gt;I ran load tests comparing identical applications on Kubernetes vs single-server deployments. The single server won every benchmark under 10,000 concurrent users. Lower latency, higher throughput, zero network overhead between containers.&lt;/p&gt;

&lt;p&gt;Your database queries run faster when they're not crossing container network boundaries. Your cache hits improve when Redis sits on localhost. Your debugging sessions shrink from hours to minutes when you can SSH into one box and see everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Escape Velocity
&lt;/h2&gt;

&lt;p&gt;The dirty secret of modern infrastructure: complexity compounds faster than benefits. Every abstraction layer adds failure modes. Every orchestration tool requires specialists. Every "cloud native" pattern increases your bus factor.&lt;/p&gt;

&lt;p&gt;Simple systems scale. Complex systems break in new and exciting ways at 2 AM.&lt;/p&gt;

&lt;p&gt;Your users don't care about your elegant microservice architecture. They care about fast page loads and features that work. A single optimized server delivers both better than a poorly managed cluster.&lt;/p&gt;

&lt;p&gt;Start simple. Stay simple as long as possible. Scale when you have problems that require scaling, not problems that require customers.&lt;/p&gt;

&lt;p&gt;The $40 server is waiting. Your future self will thank you.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloud</category>
      <category>programming</category>
    </item>
    <item>
      <title>Every REST API I've Seen in Production Violates REST. Nobody Cares.</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Mon, 23 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/every-rest-api-ive-seen-in-production-violates-rest-nobody-cares-13h7</link>
      <guid>https://dev.to/kirill_tolmachev/every-rest-api-ive-seen-in-production-violates-rest-nobody-cares-13h7</guid>
      <description>&lt;p&gt;Roy Fielding must be spinning in his still-very-much-alive chair. The man who coined REST in his 2000 dissertation watches as an entire industry slaps "REST API" labels on endpoints that would make him weep into his coffee. And you know what? We're all better for it.&lt;/p&gt;

&lt;p&gt;Every "RESTful" API I've audited in fifteen years of backend work commits the same sins. They return JSON instead of hypermedia. They stuff action verbs into URLs like &lt;code&gt;/api/users/123/activate&lt;/code&gt;. They ignore HATEOAS completely. They treat HTTP status codes like suggestions rather than contracts.&lt;/p&gt;

&lt;p&gt;But here's the uncomfortable truth: these "violations" make APIs faster to build, easier to consume, and simpler to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Academic REST vs The Real World
&lt;/h2&gt;

&lt;p&gt;True REST, as Fielding envisioned it, demands six constraints. Stateless communication, cacheable responses, uniform interface, layered system, code on demand, and the big one that trips everyone up: hypermedia as the engine of application state.&lt;/p&gt;

&lt;p&gt;HATEOAS means your API responses should contain links telling clients what actions they can take next. Like a choose-your-own-adventure book for computers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_links"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"self"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"href"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/123"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deactivate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"href"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/123/deactivate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"edit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"href"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/users/123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beautiful in theory. Absolute hell in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Everyone Ignores HATEOAS
&lt;/h2&gt;

&lt;p&gt;I worked on a project where the backend team spent three months implementing proper HATEOAS. The frontend developers revolted. Mobile developers threatened to quit. The API became a maze of link-following that turned simple operations into multi-round-trip nightmares.&lt;/p&gt;

&lt;p&gt;Frontend frameworks expect predictable endpoints. Mobile apps need offline capabilities that break when every action depends on server-provided links. GraphQL exists partly because REST's hypermedia constraints feel like handcuffs when you're trying to ship features.&lt;/p&gt;

&lt;p&gt;The industry collectively decided that having &lt;code&gt;/users/123/activate&lt;/code&gt; is more valuable than maintaining Roy Fielding's architectural purity. And we're right.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Status Code Theater
&lt;/h2&gt;

&lt;p&gt;HTTP status codes tell another story of pragmatic rebellion. Academic REST demands precise status codes for every scenario. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;201 for created resources&lt;/li&gt;
&lt;li&gt;202 for accepted but not processed&lt;/li&gt;
&lt;li&gt;204 for successful deletion&lt;/li&gt;
&lt;li&gt;422 for validation errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real APIs? Most return 200 for everything that doesn't explode, stuff error details in the response body, and call it a day.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USER_NOT_FOUND"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This drives REST purists crazy. It also prevents client-side libraries from breaking when status codes change between API versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource URLs That Aren't Actually Resources
&lt;/h2&gt;

&lt;p&gt;True REST treats everything as resources identified by URLs. Actions happen through HTTP verbs on those resources.&lt;/p&gt;

&lt;p&gt;But what happens when you need to send a password reset email? Or trigger a complex workflow? Or batch process a thousand records?&lt;/p&gt;

&lt;p&gt;You get endpoints like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /send-password-reset&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /workflows/inventory-sync/trigger&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /batch/process-orders&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These aren't resources. They're RPC calls wearing REST costumes. And that's perfectly fine because sometimes you need to tell a server to DO something, not just manipulate data.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON Killed the Hypermedia Star
&lt;/h2&gt;

&lt;p&gt;The final nail in REST's coffin was JSON becoming the default response format. XML supported rich linking structures through standards like XLink. JSON turned APIs into simple data dumps.&lt;/p&gt;

&lt;p&gt;Compare this XML response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;user&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt; &lt;span class="na"&gt;xmlns:xlink=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/1999/xlink"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;John Doe&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"edit"&lt;/span&gt; &lt;span class="na"&gt;xlink:href=&lt;/span&gt;&lt;span class="s"&gt;"/users/123"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"delete"&lt;/span&gt; &lt;span class="na"&gt;xlink:href=&lt;/span&gt;&lt;span class="s"&gt;"/users/123"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/user&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the JSON equivalent developers actually want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"John Doe"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON version loses hypermedia capabilities but gains simplicity, performance, and developer happiness. We chose convenience over architectural correctness.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Built Instead
&lt;/h2&gt;

&lt;p&gt;Modern "REST" APIs are really just HTTP-based RPC with some conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use HTTP verbs when they make sense&lt;/li&gt;
&lt;li&gt;Structure URLs in a hierarchy when possible
&lt;/li&gt;
&lt;li&gt;Return JSON because everyone expects it&lt;/li&gt;
&lt;li&gt;Use status codes for major categories (success/client error/server error)&lt;/li&gt;
&lt;li&gt;Ignore hypermedia unless you have infinite time and patience&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hybrid approach gives us 80% of REST's benefits with 20% of its complexity. That's an engineering win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Practical Takeaway
&lt;/h2&gt;

&lt;p&gt;Stop feeling guilty about your "impure" REST APIs. Focus on what actually matters:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consistency over compliance.&lt;/strong&gt; Pick conventions and stick to them across your entire API surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer experience over dogma.&lt;/strong&gt; Your API's job is to make other developers productive, not to satisfy academic requirements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pragmatic status codes.&lt;/strong&gt; Use 200/400/401/403/404/500 for 95% of scenarios. Add others only when clients need to handle them differently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embrace RPC when needed.&lt;/strong&gt; Some operations aren't resource manipulations. Don't force them into resource-based URLs just to satisfy REST purists.&lt;/p&gt;

&lt;p&gt;The internet runs on APIs that violate REST principles. It works because we optimized for reality instead of theory. Roy Fielding gave us a foundation, but we built something more practical on top of it.&lt;/p&gt;

&lt;p&gt;And that's exactly how software evolution should work.&lt;/p&gt;

</description>
      <category>api</category>
      <category>rest</category>
      <category>backend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Caching Is the Root of All Evil in Modern Backend Systems</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Sun, 22 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/caching-is-the-root-of-all-evil-in-modern-backend-systems-1ea7</link>
      <guid>https://dev.to/kirill_tolmachev/caching-is-the-root-of-all-evil-in-modern-backend-systems-1ea7</guid>
      <description>&lt;h1&gt;
  
  
  Caching Is the Root of All Evil in Modern Backend Systems
&lt;/h1&gt;

&lt;p&gt;You think caching is your friend. You're wrong. That Redis instance humming away in your infrastructure? It's not solving your problems. It's creating new ones, hiding the real issues, and slowly strangling your system's ability to scale gracefully.&lt;/p&gt;

&lt;p&gt;After building systems that handle millions of requests daily, I've watched teams chase cache hit ratios like they're chasing dragons. They optimize for metrics that don't matter while their actual problems fester underneath layers of complexity that caching introduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cache Invalidation Myth
&lt;/h2&gt;

&lt;p&gt;"There are only two hard things in Computer Science: cache invalidation and naming things." Phil Karlton's famous quote has become gospel, but here's the uncomfortable truth: if cache invalidation is hard, you shouldn't be caching in the first place.&lt;/p&gt;

&lt;p&gt;Cache invalidation isn't hard because it's inherently complex. It's hard because you're solving the wrong problem. When you cache data, you're essentially saying "this computation is too expensive, so let's store the result." But instead of making the computation cheaper, you've just added a distributed state management problem on top of your original expensive computation.&lt;/p&gt;

&lt;p&gt;I worked on a project where we spent three months debugging inconsistent search results. Users would search for products, add them to cart, then search again and find different results. The culprit? A elaborate caching system with seven different invalidation strategies across four cache layers. We had cache warming, cache aside patterns, write-through caches, and cache hierarchies.&lt;/p&gt;

&lt;p&gt;The fix wasn't better cache invalidation. We deleted 80% of the caching logic and optimized the underlying queries. Search response time dropped from 200ms to 50ms, consistency issues vanished, and our deployment complexity halved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caching Hides Performance Problems
&lt;/h2&gt;

&lt;p&gt;Caches are performance band-aids. They mask symptoms while the underlying disease spreads.&lt;/p&gt;

&lt;p&gt;Your API endpoint takes 2 seconds to respond, so you cache the response. Problem solved, right? Wrong. You've just hidden the fact that your API endpoint is fundamentally broken. That 2-second response time is telling you something important: your data model is wrong, your query is inefficient, or your architecture is flawed.&lt;/p&gt;

&lt;p&gt;Caching transforms observable problems into invisible ones. When your cache hit ratio is 99%, nobody investigates why the 1% of cache misses are so slow. But that 1% represents your real performance characteristics. Everything else is an illusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complexity Explosion
&lt;/h2&gt;

&lt;p&gt;Every cache layer doubles your system's complexity. You now have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cache warming strategies&lt;/li&gt;
&lt;li&gt;Invalidation patterns
&lt;/li&gt;
&lt;li&gt;TTL management&lt;/li&gt;
&lt;li&gt;Cache stampede protection&lt;/li&gt;
&lt;li&gt;Monitoring and alerting for cache health&lt;/li&gt;
&lt;li&gt;Backup plans for cache failures&lt;/li&gt;
&lt;li&gt;Data consistency guarantees (or lack thereof)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've seen teams with more cache-related code than business logic. Their systems became archaeology projects where nobody understood which cache invalidated which other cache, and when.&lt;/p&gt;

&lt;p&gt;One team I consulted for had a cache dependency graph that looked like a spider web designed by someone on hallucinogens. A single user update triggered invalidation cascades across seventeen different cache keys. Their deployment process included a 40-step cache warming procedure that took three hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cache Stampedes and Thundering Herds
&lt;/h2&gt;

&lt;p&gt;Caches create new failure modes that didn't exist before. Cache stampedes happen when a popular cache key expires and multiple threads simultaneously try to regenerate it. Your database gets hammered by the very traffic the cache was supposed to protect it from.&lt;/p&gt;

&lt;p&gt;The solutions? More complexity. Cache locking, probabilistic expiration, background refresh patterns. You're now running a distributed computing research project instead of a web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  False Economies
&lt;/h2&gt;

&lt;p&gt;Caching feels economical because it reduces database load. But you're trading predictable database costs for unpredictable cache complexity costs.&lt;/p&gt;

&lt;p&gt;Database scaling is a solved problem. You can buy more IOPS, add read replicas, or switch to a faster database. The cost is linear and predictable.&lt;/p&gt;

&lt;p&gt;Cache scaling is chaos. Redis clustering, data distribution, failover scenarios, cross-region replication. You're building a distributed system that's harder to operate than your original database.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternative: Fix Your Data Layer
&lt;/h2&gt;

&lt;p&gt;Instead of caching, fix the underlying problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize your queries.&lt;/strong&gt; Most slow queries can be fixed with better indexes, query restructuring, or data denormalization. A well-optimized PostgreSQL query can outperform a cache lookup when you factor in serialization overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design better data models.&lt;/strong&gt; If you're constantly joining across eight tables, your schema is wrong. Denormalize strategically. Store computed values. Use materialized views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use faster databases.&lt;/strong&gt; Modern databases are incredibly fast. A properly configured PostgreSQL instance can handle 100,000+ queries per second on commodity hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scale your database properly.&lt;/strong&gt; Read replicas, connection pooling, and vertical scaling solve most performance problems without introducing cache complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Caching Makes Sense
&lt;/h2&gt;

&lt;p&gt;I'm not completely anti-cache. Caching works when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're caching expensive computations, not database queries&lt;/li&gt;
&lt;li&gt;The cached data has clear, predictable invalidation patterns&lt;/li&gt;
&lt;li&gt;Cache misses are acceptable (the system works fine without the cache)&lt;/li&gt;
&lt;li&gt;You're caching at the edge (CDNs, browser caches)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But in-application caching? The kind where you sprinkle Redis throughout your backend? That's usually a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;Next time someone suggests adding caching to solve a performance problem, ask these questions instead:&lt;/p&gt;

&lt;p&gt;Can we optimize the underlying query? Can we denormalize this data? Can we use a faster database? Can we scale our existing database?&lt;/p&gt;

&lt;p&gt;Only after exhausting those options should you consider caching. And if you do cache, keep it simple. No cache hierarchies, no complex invalidation strategies, no distributed cache coordination.&lt;/p&gt;

&lt;p&gt;Your future self will thank you when you're not debugging cache consistency issues at 3 AM.&lt;/p&gt;

&lt;p&gt;The best cache is the one you don't need.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>caching</category>
      <category>programming</category>
      <category>performance</category>
    </item>
    <item>
      <title>Your Architecture Diagram Is a Lie and Everyone in the Meeting Knows It</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Sat, 21 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/your-architecture-diagram-is-a-lie-and-everyone-in-the-meeting-knows-it-15l9</link>
      <guid>https://dev.to/kirill_tolmachev/your-architecture-diagram-is-a-lie-and-everyone-in-the-meeting-knows-it-15l9</guid>
      <description>&lt;h1&gt;
  
  
  Your Architecture Diagram Is a Lie and Everyone in the Meeting Knows It
&lt;/h1&gt;

&lt;p&gt;That pristine diagram on your screen right now? The one with perfectly aligned boxes, clean arrows, and color-coded components that flow like a symphony? It's fiction. Pure, beautiful fiction that everyone in the meeting room is politely pretending represents reality.&lt;/p&gt;

&lt;p&gt;I've sat through hundreds of architecture reviews where teams present these masterpieces of wishful thinking. The database always has perfect uptime. The API responses are consistently under 200ms. The error handling is "robust." The security boundaries are clearly defined. Meanwhile, production is on fire, alerts are screaming, and nobody wants to admit that the actual system looks nothing like the art project on the wall.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diagram Delusion
&lt;/h2&gt;

&lt;p&gt;Architecture diagrams have become corporate mythology. We create them not to document reality, but to imagine what we wish reality looked like. They're Instagram filters for software systems.&lt;/p&gt;

&lt;p&gt;Consider the last system diagram you reviewed. Did it show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The legacy database that still runs on Oracle 11g because migration is "planned for next quarter"?&lt;/li&gt;
&lt;li&gt;The microservice that's actually three services duct-taped together because of that urgent deadline six months ago?&lt;/li&gt;
&lt;li&gt;The "temporary" Redis cache that's now critical to system function and nobody knows how to safely remove?&lt;/li&gt;
&lt;li&gt;The authentication service that works completely differently in staging than production?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course not. Those details are "implementation specifics."&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Architecture Lives in Production Logs
&lt;/h2&gt;

&lt;p&gt;Want to see your actual architecture? Don't look at Visio. Look at your error logs.&lt;/p&gt;

&lt;p&gt;Your system's true design emerges from the patterns of failure, the bottlenecks that actually matter, and the dependencies that break everything when they hiccup. The database connection pool that maxes out every Tuesday at 2 PM tells you more about your architecture than any diagram ever will.&lt;/p&gt;

&lt;p&gt;I worked on a project where our beautiful service mesh diagram showed clean, independent services. Reality? Service A couldn't function without Service B, which was secretly dependent on a shared file system that wasn't documented anywhere. When that file system filled up, half our "microservices" died together like a house of cards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Keep Drawing Lies
&lt;/h2&gt;

&lt;p&gt;It's not malicious. These diagrams serve a purpose, just not the one we claim.&lt;/p&gt;

&lt;p&gt;Architecture diagrams are aspiration documents. They represent the system we're trying to build, or the system we think we're building. They're useful for onboarding new team members who need a mental model to start with. They help us communicate with stakeholders who don't need to know about the Oracle 11g problem.&lt;/p&gt;

&lt;p&gt;But we've confused the map with the territory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Meeting Room Theater
&lt;/h2&gt;

&lt;p&gt;Here's the script that plays out in every architecture review:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presenter:&lt;/strong&gt; "So here's our high-availability setup with automatic failover..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everyone else:&lt;/strong&gt; &lt;em&gt;Thinking about that time the "automatic failover" required manual intervention for four hours&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Someone brave:&lt;/strong&gt; "What about that issue we had last month with the payment service?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Presenter:&lt;/strong&gt; "Oh, that's been addressed. Next slide."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everyone else:&lt;/strong&gt; &lt;em&gt;Knows it's still an open ticket&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This isn't productive. It's theater. We're all pretending the diagram is accurate because calling it out would mean admitting we don't have our act together.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Better Way Forward
&lt;/h2&gt;

&lt;p&gt;Stop treating architecture diagrams as documentation. Start treating them as design tools.&lt;/p&gt;

&lt;p&gt;Here's what actually works:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before building anything new:&lt;/strong&gt; Create a diagram that shows your ideal state. Use it to identify potential problems, discuss trade-offs, and align on goals. Then put it away.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For existing systems:&lt;/strong&gt; Create "as-is" diagrams that show the ugly truth. Include the technical debt, the workarounds, and the actual data flows. Update them when things change, not when things were supposed to change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For stakeholders:&lt;/strong&gt; Create simplified views that focus on business capabilities, not technical implementation. They don't need to know about your twelve microservices. They need to know that orders get processed and customers get charged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For new team members:&lt;/strong&gt; Give them the aspirational diagram first, then the real one after they've been there two weeks. Context matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Truth
&lt;/h2&gt;

&lt;p&gt;Your production system is messier, more complex, and more interdependent than any diagram can capture. That's not a failure—that's software development.&lt;/p&gt;

&lt;p&gt;The best architects I know don't hide from this complexity. They document it, measure it, and gradually improve it. They create diagrams that serve specific purposes for specific audiences, not universal truth documents that please nobody.&lt;/p&gt;

&lt;p&gt;Next time you're in an architecture review, try this: ask how the diagram would change if you included every actual dependency, every shared resource, and every piece of technical debt. Watch the room get uncomfortable.&lt;/p&gt;

&lt;p&gt;Then start a real conversation about what you're actually building.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Action Items
&lt;/h2&gt;

&lt;p&gt;Tomorrow, do this: take your current architecture diagram and mark every component that doesn't match production reality in red. Everything that's partially accurate gets yellow. Only the truly accurate pieces stay green.&lt;/p&gt;

&lt;p&gt;If your diagram is mostly red and yellow, congratulations. You're being honest for the first time in months.&lt;/p&gt;

&lt;p&gt;Now you can start building something better.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>programming</category>
      <category>softwareengineering</category>
      <category>career</category>
    </item>
    <item>
      <title>Senior Developers Don't Write Better Code. They Delete More of It.</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Fri, 20 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/senior-developers-dont-write-better-code-they-delete-more-of-it-1o88</link>
      <guid>https://dev.to/kirill_tolmachev/senior-developers-dont-write-better-code-they-delete-more-of-it-1o88</guid>
      <description>&lt;p&gt;You think senior developers got where they are by writing elegant algorithms and architecting beautiful systems? Wrong. They got there by becoming ruthless deleters of code. While junior developers are busy adding features and showing off their latest framework knowledge, senior developers are quietly removing thousands of lines that never should have existed in the first place.&lt;/p&gt;

&lt;p&gt;I've watched this pattern for over a decade across multiple teams and projects. The developer who gets promoted isn't the one who shipped the most features. It's the one who made the codebase smaller, faster, and more maintainable by systematically eliminating complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deletion Paradox
&lt;/h2&gt;

&lt;p&gt;Here's what nobody tells you about career progression: your value as a developer becomes inversely proportional to the amount of code you write.&lt;/p&gt;

&lt;p&gt;A junior developer joins the team and immediately starts contributing. They implement new APIs, add validation layers, create utility functions, and build comprehensive test suites. In their first year, they might add 50,000 lines of code to the project. Everyone's impressed.&lt;/p&gt;

&lt;p&gt;A senior developer joins the same team. In their first month, they delete 15,000 lines of code. They remove three entire microservices, eliminate two dependency libraries, and consolidate four similar functions into one. The junior developer's code contribution graph shows mountain peaks. The senior's looks like a valley.&lt;/p&gt;

&lt;p&gt;Guess which one the company values more?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Deletion Beats Creation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Less Code = Fewer Problems
&lt;/h3&gt;

&lt;p&gt;Every line of code is a liability. It can break, needs maintenance, creates dependencies, and must be understood by future developers. I once worked on a project where we reduced a 3,000-line configuration system down to 200 lines. Bug reports dropped by 80%. Performance improved by 40%. The junior developer who originally built it was offended. The users were ecstatic.&lt;/p&gt;

&lt;h3&gt;
  
  
  Maintenance Costs Are Real
&lt;/h3&gt;

&lt;p&gt;That clever abstraction you built? Someone will spend three days trying to understand it next year. Those eight similar-but-slightly-different functions? Each one is a potential bug waiting to happen when requirements change. The comprehensive error handling that covers every edge case? Most of those cases will never occur, but the code complexity they add will slow down every future developer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cognitive Load Is The Real Enemy
&lt;/h3&gt;

&lt;p&gt;Human brains can hold about seven pieces of information in working memory. Your codebase probably has 700,000 pieces of information. Every unnecessary function, every redundant parameter, every "just in case" feature adds cognitive load. Senior developers understand that reducing cognitive load is more valuable than adding functionality.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Art of Strategic Deletion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Delete Redundant Code First
&lt;/h3&gt;

&lt;p&gt;Look for patterns where the same logic appears multiple times with slight variations. I recently combined twelve different "send notification" functions into one configurable function. Deleted 400 lines, reduced bugs, simplified testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove Unused Features
&lt;/h3&gt;

&lt;p&gt;That analytics dashboard nobody looks at? Delete it. The API endpoint that only the QA team uses for testing? Delete it. The feature flag that's been enabled for two years? Delete the flag and make the behavior permanent. Unused code isn't just neutral - it's actively harmful because it creates false complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Eliminate Premature Optimizations
&lt;/h3&gt;

&lt;p&gt;Junior developers love to optimize for performance scenarios that never happen. I've seen caching layers for data that changes once per month, complex algorithms to handle loads that peak at 10 requests per minute, and elaborate failover systems for services with 99.99% uptime requirements. Delete the optimization and see if anyone notices. Spoiler: they won't.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kill Your Darlings (And Your Dependencies)
&lt;/h3&gt;

&lt;p&gt;That elegant helper library you wrote? If it's used in three places, maybe just copy the 20 lines of code instead of maintaining a whole module. That popular npm package that does exactly what you need? If you're only using 5% of its functionality, consider implementing those features directly and dropping the dependency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Developing Your Deletion Instincts
&lt;/h2&gt;

&lt;p&gt;Start measuring the right metrics. Instead of tracking lines of code written, track lines of code removed. Instead of celebrating new features shipped, celebrate features retired. Instead of asking "how can we add this functionality," ask "how can we avoid adding this functionality."&lt;/p&gt;

&lt;p&gt;Practice the 10x rule: every line of code you write will be read 10 times, modified 3 times, and debugged twice. Make sure it's worth it.&lt;/p&gt;

&lt;p&gt;Before writing anything new, spend 30 minutes looking for existing code you can delete instead. You'll be surprised how often you can solve the "new" problem by removing old problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Counter-Intuitive Truth
&lt;/h2&gt;

&lt;p&gt;The most productive thing you can do as a developer isn't writing more code. It's writing less code that does more. The fastest way to ship features isn't adding functionality. It's removing the barriers that slow down feature development.&lt;/p&gt;

&lt;p&gt;Senior developers understand this instinctively. They've been burned by their own clever code enough times to know that simplicity beats sophistication. They've maintained enough legacy systems to know that every line of code is a commitment to future maintenance.&lt;/p&gt;

&lt;p&gt;Next time you're about to implement a new feature, ask yourself: what can I delete instead? Your future self will thank you. Your teammates will thank you. Your users will thank you, even if they never realize why the application suddenly feels faster and more reliable.&lt;/p&gt;

&lt;p&gt;The best code is the code that doesn't exist. The best developers are the ones who keep it that way.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>career</category>
      <category>softwaredevelopment</category>
      <category>codequality</category>
    </item>
    <item>
      <title>99% of Backend Developers Can't Design a System That Handles 1,000 Concurrent Users</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Thu, 19 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/99-of-backend-developers-cant-design-a-system-that-handles-1000-concurrent-users-3pkh</link>
      <guid>https://dev.to/kirill_tolmachev/99-of-backend-developers-cant-design-a-system-that-handles-1000-concurrent-users-3pkh</guid>
      <description>&lt;p&gt;You think 1,000 concurrent users is easy? Most senior developers I've worked with would fail this challenge spectacularly. They'd reach for microservices, throw Redis at everything, and wonder why their beautifully architected system crumbles under load that a simple PHP script could handle.&lt;/p&gt;

&lt;p&gt;The problem isn't lack of knowledge about distributed systems or fancy tools. It's that developers optimize for the wrong metrics and misunderstand what "concurrent" actually means.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math They Don't Want You to Know
&lt;/h2&gt;

&lt;p&gt;Here's what kills me: developers obsess over theoretical throughput while ignoring the brutal reality of connection management. A single server can theoretically handle millions of requests per second, but most can't even maintain 1,000 persistent connections without choking.&lt;/p&gt;

&lt;p&gt;Why? Because they never learned the difference between concurrent requests and concurrent connections. A user hitting your API doesn't just send one request and disappear. They establish a connection, maybe keep it alive, send multiple requests, wait for responses, handle timeouts.&lt;/p&gt;

&lt;p&gt;Real concurrency means 1,000 users all doing this simultaneously. Your server needs to juggle 1,000+ file descriptors, maintain session state, handle network buffers, and still process business logic fast enough that nobody times out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Connection Pool Disaster
&lt;/h2&gt;

&lt;p&gt;I've seen developers spend weeks optimizing database queries that run in 2ms, then deploy to production where the connection pool has 10 connections for 1,000 users.&lt;/p&gt;

&lt;p&gt;Every user waits in line for database access like it's Black Friday at Best Buy.&lt;/p&gt;

&lt;p&gt;Connection pooling isn't just a database concern. Your HTTP client libraries, message queues, cache connections, external API calls - they all need pools sized for your actual concurrency, not your theoretical QPS.&lt;/p&gt;

&lt;p&gt;Here's the calculation nobody does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000 concurrent users&lt;/li&gt;
&lt;li&gt;Average request takes 200ms end-to-end&lt;/li&gt;
&lt;li&gt;Each user makes a request every 2 seconds&lt;/li&gt;
&lt;li&gt;You need at least 100 database connections, not 10&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Memory: The Silent Killer
&lt;/h2&gt;

&lt;p&gt;Python developers are the worst offenders here, but everyone's guilty. They load entire user objects into memory "for performance," then wonder why their 8GB server dies at 500 concurrent users.&lt;/p&gt;

&lt;p&gt;Each connection consumes memory. TCP buffers, application state, framework overhead, garbage collection pressure. A Rails app might use 50MB per worker process. That's 200 concurrent users max before you're swapping to disk.&lt;/p&gt;

&lt;p&gt;JavaScript developers aren't innocent either. Node.js handles concurrency beautifully until you start storing user sessions in memory objects. Suddenly your event loop is spending all its time in garbage collection instead of processing requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monitoring Blind Spot
&lt;/h2&gt;

&lt;p&gt;Most developers monitor the wrong metrics. They watch CPU usage and database query times while their users are timing out due to TCP queue saturation.&lt;/p&gt;

&lt;p&gt;The metrics that actually matter for 1,000 concurrent users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active connections (not just QPS)&lt;/li&gt;
&lt;li&gt;Connection establishment time&lt;/li&gt;
&lt;li&gt;Memory per connection&lt;/li&gt;
&lt;li&gt;File descriptor usage&lt;/li&gt;
&lt;li&gt;Network buffer utilization&lt;/li&gt;
&lt;li&gt;Time spent waiting for connections vs processing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I worked on a project where we had sub-10ms API response times but users were still complaining about slowness. The problem? Our load balancer was only opening 100 connections to each backend server. Users were waiting 5 seconds just to get connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Microservices Trap
&lt;/h2&gt;

&lt;p&gt;Junior developers think scaling means splitting everything into microservices. Senior developers should know better, but most don't.&lt;/p&gt;

&lt;p&gt;Microservices multiply your concurrent connection problems. Now instead of handling 1,000 user connections, each service needs to handle connections from other services plus users. Your user service talks to auth service, which talks to profile service, which queries the database. One user request becomes 4-6 internal service calls.&lt;/p&gt;

&lt;p&gt;Each hop introduces latency, connection overhead, and failure points. Your 1,000 concurrent users become 6,000 concurrent service-to-service connections. Good luck debugging that when it breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;Stop overthinking it. A well-configured monolith on a modern server can handle thousands of concurrent users without breaking a sweat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use connection pooling everywhere.&lt;/strong&gt; Not just databases - HTTP clients, Redis, message queues, everything that opens sockets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure memory per user, not per request.&lt;/strong&gt; If you can't support 1,000 users on a single server due to memory constraints, fix that before you scale out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize connection handling, not business logic.&lt;/strong&gt; Use async I/O frameworks, configure proper timeouts, tune your TCP stack. A slow algorithm running on 1,000 connections beats a fast algorithm that can only handle 100.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Load test with realistic user behavior.&lt;/strong&gt; Don't just hammer endpoints with curl. Simulate actual user sessions, connection lifecycle, typical request patterns.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reality Check
&lt;/h2&gt;

&lt;p&gt;Most "scalable" systems I've audited couldn't handle 100 real concurrent users, let alone 1,000. They're optimized for demo traffic, not production load.&lt;/p&gt;

&lt;p&gt;If you can't run &lt;code&gt;ab -n 10000 -c 1000&lt;/code&gt; against your system without errors, you're not ready for production. Fix the fundamentals before you architect for millions of users.&lt;/p&gt;

&lt;p&gt;The irony? Once you can handle 1,000 concurrent users properly, scaling to 10,000 or 100,000 becomes straightforward. You just add more servers behind a load balancer.&lt;/p&gt;

&lt;p&gt;But until you understand connection management, memory allocation, and realistic concurrency testing, you're just building distributed systems that fail faster.&lt;/p&gt;

</description>
      <category>backend</category>
      <category>architecture</category>
      <category>programming</category>
      <category>scalability</category>
    </item>
    <item>
      <title>Docker Is the New jQuery: Everyone Uses It, Nobody Understands It</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Wed, 18 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/docker-is-the-new-jquery-everyone-uses-it-nobody-understands-it-4g83</link>
      <guid>https://dev.to/kirill_tolmachev/docker-is-the-new-jquery-everyone-uses-it-nobody-understands-it-4g83</guid>
      <description>&lt;h1&gt;
  
  
  Docker Is the New jQuery: Everyone Uses It, Nobody Understands It
&lt;/h1&gt;

&lt;p&gt;Remember when jQuery was everywhere? Every website, every tutorial, every bootcamp graduate's first instinct was to &lt;code&gt;$()&lt;/code&gt; their way through problems. Half the developers using it couldn't explain what the DOM was, but they sure knew how to copy-paste that CDN link. Today, Docker has achieved the same dubious honor.&lt;/p&gt;

&lt;p&gt;I've watched teams Dockerize their Hello World applications while running monolithic databases on bare metal. I've seen developers spend three days debugging container networking for a project that could run perfectly fine as a systemd service. We've created a generation of developers who know more about Dockerfile syntax than they do about process management.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cargo Cult Problem
&lt;/h2&gt;

&lt;p&gt;Docker isn't inherently bad. Neither was jQuery. Both solved real problems and democratized complex concepts. jQuery made DOM manipulation accessible when browsers were inconsistent nightmares. Docker made deployment consistent when servers were snowflake disasters.&lt;/p&gt;

&lt;p&gt;But somewhere along the way, we stopped asking "why" and started assuming "must."&lt;/p&gt;

&lt;p&gt;I worked on a project where the team Dockerized a static file server. Not a complex application. Not even a dynamic site. Static HTML files served by nginx. When I asked why we needed containers for this, the response was genuinely puzzled: "How else would you deploy it?"&lt;/p&gt;

&lt;p&gt;This is cargo cult programming. Going through the motions without understanding the principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Docker Makes Sense (And When It Doesn't)
&lt;/h2&gt;

&lt;p&gt;Here's what Docker actually solves:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment consistency&lt;/strong&gt;: Your application runs the same way across development, testing, and production. This matters when you have complex dependencies or need specific system libraries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource isolation&lt;/strong&gt;: Multiple applications can share the same host without stepping on each other. Critical for multi-tenant environments or when you're running untrusted code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling orchestration&lt;/strong&gt;: Kubernetes and similar platforms need standardized deployment units. Containers provide that abstraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dependency management&lt;/strong&gt;: Complex applications with conflicting system requirements become manageable.&lt;/p&gt;

&lt;p&gt;Now here's when Docker is overkill:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-purpose VMs where you control the entire stack&lt;/li&gt;
&lt;li&gt;Applications with simple, standard dependencies&lt;/li&gt;
&lt;li&gt;Development environments where you're the only user&lt;/li&gt;
&lt;li&gt;Stateful services that need persistent storage and don't benefit from horizontal scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Hidden Costs Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Every abstraction has overhead. Docker's costs aren't just computational (though that extra layer does impact performance). The real costs are cognitive and operational.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging becomes harder.&lt;/strong&gt; Your application is now wrapped in multiple layers. Network issues become container networking issues. File system problems become volume mounting problems. Memory leaks become container memory limit problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Local development gets complex.&lt;/strong&gt; Simple port conflicts become Docker port mapping configuration. Environment variables become docker-compose.yml files. Quick code changes become image rebuilds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security surface area expands.&lt;/strong&gt; Now you're responsible for base image vulnerabilities, container runtime security, and registry access controls on top of your application security.&lt;/p&gt;

&lt;p&gt;I've seen teams spend more time managing their Docker setup than building features.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Good Docker Usage Looks Like
&lt;/h2&gt;

&lt;p&gt;The teams that use Docker well share common patterns:&lt;/p&gt;

&lt;p&gt;They &lt;strong&gt;understand the alternatives&lt;/strong&gt;. Before containerizing, they can articulate why traditional deployment won't work. They've tried systemd services, configuration management, or platform-specific deployment tools.&lt;/p&gt;

&lt;p&gt;They &lt;strong&gt;optimize for their actual problems&lt;/strong&gt;. If they need environment consistency, they focus on that. If they need scaling, they design for horizontal scaling from day one.&lt;/p&gt;

&lt;p&gt;They &lt;strong&gt;keep it simple&lt;/strong&gt;. Multi-stage builds are common. Alpine base images unless they need specific libraries. Minimal layers. No installing debugging tools in production images.&lt;/p&gt;

&lt;p&gt;They &lt;strong&gt;automate ruthlessly&lt;/strong&gt;. If you're manually running docker commands in production, you're doing it wrong. Everything goes through CI/CD pipelines with proper testing at each stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Path Forward
&lt;/h2&gt;

&lt;p&gt;Here's how to escape the Docker cargo cult:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with the problem, not the solution.&lt;/strong&gt; Before adding Docker to any project, write down the specific problem you're solving. "Everyone uses Docker" isn't a problem statement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learn the fundamentals.&lt;/strong&gt; Understand what containers actually are. Learn about namespaces, cgroups, and union filesystems. Spend time with &lt;code&gt;docker inspect&lt;/code&gt; and &lt;code&gt;docker exec&lt;/code&gt; to see what's really happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practice alternatives.&lt;/strong&gt; Try deploying the same application multiple ways. Use systemd services, configuration management tools, or platform-specific deployment. Compare the complexity and maintenance overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure everything.&lt;/strong&gt; Track build times, deployment times, resource usage, and debugging time. Make decisions based on data, not assumptions.&lt;/p&gt;

&lt;p&gt;Docker isn't going anywhere. But neither is the need for developers who understand when and why to use it. The industry needs fewer Docker users and more Docker understanders.&lt;/p&gt;

&lt;p&gt;Stop copying configuration from Stack Overflow and start understanding what those configurations actually do. Your future self (and your production environment) will thank you.&lt;/p&gt;

&lt;p&gt;The next time someone asks why you're using Docker, you should have a better answer than "that's how everyone does it."&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The Worst Technical Decision I've Seen Cost $2M. It Started with 'Let's Rewrite It in...'</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Tue, 17 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/the-worst-technical-decision-ive-seen-cost-2m-it-started-with-lets-rewrite-it-in-3e32</link>
      <guid>https://dev.to/kirill_tolmachev/the-worst-technical-decision-ive-seen-cost-2m-it-started-with-lets-rewrite-it-in-3e32</guid>
      <description>&lt;p&gt;You know that feeling when someone in a meeting says "What if we just rewrote the whole thing?" and everyone nods like it's brilliant? I watched that exact moment destroy a perfectly good business. The system was making money. The customers were happy. Revenue was growing 40% year over year.&lt;/p&gt;

&lt;p&gt;Then someone said the magic words: "Let's rewrite it in Go."&lt;/p&gt;

&lt;p&gt;Two years and $2.3 million later, they had nothing to show for it except a half-built system, three burned-out teams, and a competitor who ate their lunch while they were busy solving problems that didn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rewrite Disease
&lt;/h2&gt;

&lt;p&gt;Here's what nobody tells you about rewrites: they're almost never about technology. They're about ego.&lt;/p&gt;

&lt;p&gt;I worked on a project where the existing Python API handled 50,000 requests per second without breaking a sweat. Sure, the code wasn't pretty. Yes, there were some gnarly bits that made new developers cry. But it worked. It made money. It had been battle-tested by real users doing real things.&lt;/p&gt;

&lt;p&gt;The engineering team had different ideas. They wanted to use the hot new language. They wanted clean architecture. They wanted to feel smart again.&lt;/p&gt;

&lt;h2&gt;
  
  
  How $2M Disappeared
&lt;/h2&gt;

&lt;p&gt;The numbers are brutal when you break them down:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 1-3:&lt;/strong&gt; Planning and "research" (3 senior engineers × $150k salary = $112k)&lt;br&gt;
&lt;strong&gt;Month 4-12:&lt;/strong&gt; Development sprint one, building the "core" (6 engineers × $130k average = $585k)&lt;br&gt;
&lt;strong&gt;Month 13-18:&lt;/strong&gt; Development sprint two, "fixing architecture issues" (8 engineers × $140k average = $630k)&lt;br&gt;
&lt;strong&gt;Month 19-24:&lt;/strong&gt; Panic mode, bringing in consultants (4 consultants × $200/hour × 40 hours/week × 24 weeks = $768k)&lt;br&gt;
&lt;strong&gt;Infrastructure costs:&lt;/strong&gt; New staging environments, monitoring, deployment pipelines ($180k)&lt;br&gt;
&lt;strong&gt;Opportunity cost:&lt;/strong&gt; Features not built on the working system (immeasurable)&lt;/p&gt;

&lt;p&gt;The kicker? They never shipped. The rewrite got cancelled when the company ran out of runway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Wasn't Technical
&lt;/h2&gt;

&lt;p&gt;Every rewrite I've seen fail had the same pattern. Engineers convinced themselves they were solving technical problems, but they were really solving emotional ones.&lt;/p&gt;

&lt;p&gt;"The old codebase is messy." Translation: I didn't write it, so I don't understand it.&lt;/p&gt;

&lt;p&gt;"We need better performance." Translation: I want to work with shiny tools.&lt;/p&gt;

&lt;p&gt;"The architecture is all wrong." Translation: I have opinions about how things should be done.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: your users don't care about your code quality. They care about your features working. They care about bugs getting fixed. They care about new functionality that solves their problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Rewrites Actually Make Sense
&lt;/h2&gt;

&lt;p&gt;I'm not anti-rewrite. I've seen them work. But they work under very specific conditions:&lt;/p&gt;

&lt;p&gt;The existing system is genuinely blocking business growth. Not "it's hard to add features" but "we literally cannot serve more customers."&lt;/p&gt;

&lt;p&gt;You have a clear migration path with measurable milestones. If you can't break your rewrite into deliverable chunks that provide value, you're building a death star.&lt;/p&gt;

&lt;p&gt;The business can afford to get zero new features for 12-18 months minimum. And yes, it will take longer than you think.&lt;/p&gt;

&lt;p&gt;You have senior engineers who've done large rewrites before and lived to tell about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What To Do Instead
&lt;/h2&gt;

&lt;p&gt;Stop thinking about rewrites. Start thinking about incremental improvements.&lt;/p&gt;

&lt;p&gt;I worked with a team that had a "legacy" PHP application serving 100 million page views per month. Instead of rewriting it, they slowly extracted services. They replaced the nastiest parts first. They added proper monitoring. They wrote tests for the scary functions.&lt;/p&gt;

&lt;p&gt;Three years later, 60% of the system was new code running on modern infrastructure. The other 40% was the old stuff that just worked and didn't need fixing. Total cost: $400k spread over three years. Zero downtime. Zero missed deadlines. Zero customer complaints.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Playbook That Actually Works
&lt;/h2&gt;

&lt;p&gt;Start with monitoring. You can't improve what you can't measure.&lt;/p&gt;

&lt;p&gt;Identify the true bottlenecks. Not the code that makes you uncomfortable, but the actual problems users experience.&lt;/p&gt;

&lt;p&gt;Extract the easiest wins first. Build confidence with small successes.&lt;/p&gt;

&lt;p&gt;Celebrate boring improvements. A 50% reduction in database queries beats a complete architecture overhaul.&lt;/p&gt;

&lt;p&gt;Keep shipping features on the old system while you improve it. Your competitors won't wait for your rewrite to finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Reality
&lt;/h2&gt;

&lt;p&gt;Most rewrite decisions happen because engineers are bored. They want to work on greenfield projects. They want to use new technologies. They want to build something they can put on their resume.&lt;/p&gt;

&lt;p&gt;These are valid human needs. But they're not business needs.&lt;/p&gt;

&lt;p&gt;If you want to experiment with new technologies, build side projects. Create internal tools. Contribute to open source. Don't bet the company's future on your learning journey.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Action Plan
&lt;/h2&gt;

&lt;p&gt;Next time someone suggests a rewrite, ask these questions:&lt;/p&gt;

&lt;p&gt;What specific business problem are we solving? "Code quality" is not a business problem.&lt;/p&gt;

&lt;p&gt;How will we measure success? If you can't quantify the benefit, you shouldn't do the work.&lt;/p&gt;

&lt;p&gt;What's our rollback plan? If things go sideways, how do we get back to serving customers?&lt;/p&gt;

&lt;p&gt;Can we solve this problem incrementally? Usually the answer is yes, but it requires more discipline than starting over.&lt;/p&gt;

&lt;p&gt;Who's going to maintain the old system while we build the new one? Spoiler alert: the same people building the new system.&lt;/p&gt;

&lt;p&gt;The most expensive technical decision isn't the wrong technology choice. It's the decision to throw away working code and start over. Save your company the $2 million lesson. Fix what you have. Your future self (and your bank account) will thank you.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>career</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AI Already Won. Programmers Still Have Jobs. Nobody Can Explain Why.</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Mon, 16 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/ai-already-won-programmers-still-have-jobs-nobody-can-explain-why-2ap6</link>
      <guid>https://dev.to/kirill_tolmachev/ai-already-won-programmers-still-have-jobs-nobody-can-explain-why-2ap6</guid>
      <description>&lt;p&gt;The tech world has made up its mind: AI will replace programmers. And you know what? They're mostly right. The traditional programmer — the person whose job is to translate business requirements into code — is genuinely at risk.&lt;/p&gt;

&lt;p&gt;But here's the plot twist nobody's talking about: &lt;strong&gt;the software industry itself has never been safer.&lt;/strong&gt; Not because AI can't build software. But because the people who need software are too lazy to use AI to build it themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Yes, Programmers Are in Danger. Let's Not Pretend Otherwise.
&lt;/h2&gt;

&lt;p&gt;Let's not sugarcoat this: AI is coming for a significant chunk of programming jobs. The junior developer who writes CRUD endpoints? AI does that faster and cheaper. The mid-level dev who implements well-defined features from Jira tickets? AI is getting dangerously close.&lt;/p&gt;

&lt;p&gt;The writing is on the wall, and the developers who refuse to read it will be the first casualties.&lt;/p&gt;

&lt;p&gt;But there's a critical distinction the doomsayers miss: &lt;strong&gt;AI replacing programmers is not the same as AI replacing the need for software services.&lt;/strong&gt; These are two entirely different claims, and conflating them is where the narrative goes off the rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $10 Problem
&lt;/h2&gt;

&lt;p&gt;Here's a thought experiment. You're a small business owner. You need a landing page. Nothing fancy — a hero section, a signup form, maybe some testimonials. In 2026, you have two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A:&lt;/strong&gt; Open Claude/GPT/Cursor, describe what you want, iterate on the output, validate the HTML, check responsiveness, fix the three things it got wrong, figure out hosting, deploy it yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B:&lt;/strong&gt; Go to Fiverr, pay someone $10-50, get it back tomorrow, done.&lt;/p&gt;

&lt;p&gt;Most people pick Option B. Not because they're stupid. Not because they can't use AI. But because &lt;strong&gt;Option B requires zero cognitive effort after the initial click.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the uncomfortable truth the "AI democratizes everything" crowd doesn't want to hear. The bottleneck was never access to tools. It was always willingness to use them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cognitive Tax of "Free"
&lt;/h2&gt;

&lt;p&gt;There's a hidden assumption in every "AI will let everyone build software" argument: that people &lt;em&gt;want&lt;/em&gt; to build software themselves. They don't. They never did.&lt;/p&gt;

&lt;p&gt;WordPress "democratized" web development in 2004. Twenty-two years later, there are more web developers than ever, and most small businesses still pay someone to set up their WordPress site. Squarespace, Wix, and every no-code tool in existence proved the same thing: &lt;strong&gt;lowering the barrier to entry doesn't eliminate demand for professionals. It creates a new category of people who know the barrier is low and still don't want to cross it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;AI is the same story with better marketing.&lt;/p&gt;

&lt;p&gt;Using AI effectively requires:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Knowing what to ask for&lt;/strong&gt; — You need a mental model of the solution before you can prompt for it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluating the output&lt;/strong&gt; — Can you tell if the generated code has a security vulnerability? A performance bottleneck? A subtle logic error?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Iterating intelligently&lt;/strong&gt; — When the first result isn't right, you need to know &lt;em&gt;what's&lt;/em&gt; wrong and &lt;em&gt;how&lt;/em&gt; to fix it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration and deployment&lt;/strong&gt; — The AI gives you code. You still need to put it somewhere, connect it to your systems, and maintain it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these steps is a tax on your attention. And attention is the scarcest resource in 2026 — not compute, not API credits, not talent. &lt;strong&gt;Attention.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paradox of Capability
&lt;/h2&gt;

&lt;p&gt;As AI tools become more capable, the gap between "possible" and "actually happens" widens, not narrows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2020:&lt;/strong&gt; "Can AI write code?" → Barely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2023:&lt;/strong&gt; "Can AI write code?" → Yes, simple things&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2025:&lt;/strong&gt; "Can AI write code?" → Yes, even complex things&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2026:&lt;/strong&gt; "Does everyone build their own software now?" → No. Absolutely not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Because capability was never the bottleneck. &lt;strong&gt;Willingness was.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about cooking. YouTube has millions of free tutorials. Gordon Ramsay will teach you beef Wellington for free. The ingredients are at your local grocery store. Yet the restaurant industry is worth $1 trillion. UberEats, DoorDash, and Glovo aren't thriving because people &lt;em&gt;can't&lt;/em&gt; cook. They're thriving because people &lt;em&gt;won't&lt;/em&gt; cook.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cooking&lt;/th&gt;
&lt;th&gt;AI-Assisted Dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Watch tutorial&lt;/td&gt;
&lt;td&gt;Write prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buy ingredients&lt;/td&gt;
&lt;td&gt;Set up environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Follow recipe&lt;/td&gt;
&lt;td&gt;Iterate on output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Taste and adjust&lt;/td&gt;
&lt;td&gt;Test and debug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean up&lt;/td&gt;
&lt;td&gt;Deploy and maintain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each step is individually easy. Together, they represent a commitment most people would rather outsource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two Things Can Be True at Once
&lt;/h2&gt;

&lt;p&gt;Most takes on this topic pick a side. Either "AI will replace all programmers" or "programmers are safe." The reality is messier and more interesting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. AI will absolutely displace many programmers.&lt;/strong&gt; The ones writing boilerplate, implementing standard patterns, doing predictable work. This is already happening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The demand for software products and services will not decrease.&lt;/strong&gt; If anything, it will increase. As AI makes development cheaper, more people and businesses will want custom software. But they still won't want to build it themselves.&lt;/p&gt;

&lt;p&gt;The result? &lt;strong&gt;The industry transforms, but doesn't shrink.&lt;/strong&gt; The nature of the work changes. The people doing it might look different. But someone is still getting paid to turn "I need a thing" into a working product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Laziness Economy
&lt;/h2&gt;

&lt;p&gt;"Laziness" isn't a moral judgment here. It's an economic force. Perhaps the most powerful one.&lt;/p&gt;

&lt;p&gt;Every successful consumer product in history has been a laziness product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cars:&lt;/strong&gt; Walking is free, but...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Washing machines:&lt;/strong&gt; You could wash by hand, but...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calculators:&lt;/strong&gt; You could do math on paper, but...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Uber:&lt;/strong&gt; You could drive yourself, but...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DoorDash:&lt;/strong&gt; You could cook, but...&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI coding tools:&lt;/strong&gt; You could build it yourself, but...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each technology &lt;em&gt;did&lt;/em&gt; displace workers. Cars displaced carriage drivers. Washing machines displaced washerwomen. But they also created entirely new service economies. Mechanics. Delivery drivers.&lt;/p&gt;

&lt;p&gt;AI will do the same. The question isn't "will programmers survive?" — many won't, at least not in their current form. The question is: &lt;strong&gt;"What new service economy will emerge around the people who are too lazy to use AI themselves?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And the answer is already forming: a massive market of people who operate AI on your behalf, validate the results for you, handle the deployment, and charge you a reasonable fee for the privilege of not having to think about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Actually Wins?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Winner #1: Senior developers who become AI-amplified.&lt;/strong&gt; A senior dev with Cursor isn't being replaced — they're doing 5x the work. They handle the architecture, the edge cases, the things AI still gets wrong. They're more valuable than ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner #2: "AI-native" service providers.&lt;/strong&gt; A new class of professional: people who are excellent at prompting, validating, and deploying AI-generated solutions. Not traditional developers, not "prompt engineers" — translators between human intent and machine output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner #3: Platform builders.&lt;/strong&gt; The real money isn't in generating code — it's in wrapping AI in products that eliminate cognitive load. Vercel's v0, Bolt, Lovable — these aren't AI tools. They're convenience products. The user never writes a prompt. They click buttons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The losers?&lt;/strong&gt; Junior and mid-level developers who don't adapt. And, ironically, the "everyone can code now" narrative — which promised democratization but delivered a new layer of abstraction most people are happy to pay someone else to navigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Means for Developers
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't be complacent.&lt;/strong&gt; The "they can't replace me" attitude is dangerous. They can replace parts of what you do, and that's enough to restructure the industry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Move up the stack.&lt;/strong&gt; The surviving roles are the ones AI can't easily replicate: system design, complex debugging, understanding business context, managing ambiguity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Learn to wield AI, not compete with it.&lt;/strong&gt; The developers who thrive will use AI for the boring parts while they focus on the genuinely hard problems.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consider the service layer.&lt;/strong&gt; There's a growing market for "I'll handle the AI part for you." It's not glamorous, but it's recession-proof.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;AI will transform software development. It already has. Many programming jobs will disappear. This is real, and pretending otherwise is irresponsible.&lt;/p&gt;

&lt;p&gt;But the demand for software? The need for someone to turn vague ideas into working products? The willingness of people to pay $10, $100, or $10,000 rather than figure it out themselves?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's eternal.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Because the most human-proof force in existence isn't artificial intelligence. It's the deeply human tendency to pay for convenience even when the DIY option is trivially easy.&lt;/p&gt;

&lt;p&gt;Programmers might be in danger. The software industry isn't. And the gap between those two statements is where the next trillion-dollar economy lives.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Database Indexes Explained Like You're 5</title>
      <dc:creator>Kirill Tolmachev</dc:creator>
      <pubDate>Sat, 14 Feb 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/kirill_tolmachev/database-indexes-explained-like-youre-5-1d3b</link>
      <guid>https://dev.to/kirill_tolmachev/database-indexes-explained-like-youre-5-1d3b</guid>
      <description>&lt;p&gt;Imagine you walk into a library with ten thousand books, and you need to find one specific title. You have two options: start at the first shelf and check every single spine until you find it, or walk over to the catalog system, look up the title, and get the exact shelf and position in seconds.&lt;/p&gt;

&lt;p&gt;A database index works exactly like that catalog. It's a separate data structure that helps your database find rows without scanning the entire table. And once you understand how indexes work, you'll know why some queries take 2 milliseconds and others take 20 seconds — even on the same table.&lt;/p&gt;

&lt;p&gt;The SQL examples in this article use PostgreSQL, but the concepts apply equally to MySQL, SQL Server, SQLite, and pretty much any relational database you'll encounter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The phone book you never asked for
&lt;/h2&gt;

&lt;p&gt;Here's another way to think about it. A phone book is sorted alphabetically by last name. If you need to find "Martinez," you can flip to the M section and get there quickly. That's an index at work — the data is organized in a way that makes lookup fast.&lt;/p&gt;

&lt;p&gt;But what if someone asks you to find everyone in the phone book with the phone number 555-0142? Now you're stuck reading every single entry, because the book isn't sorted by phone number. There's no shortcut.&lt;/p&gt;

&lt;p&gt;That's what happens inside your database when you run a query against a column that has no index.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens without an index: the sequential scan
&lt;/h2&gt;

&lt;p&gt;Let's say you have a &lt;code&gt;users&lt;/code&gt; table with a million rows, and you run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ada@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without an index on the &lt;code&gt;email&lt;/code&gt; column, PostgreSQL has to perform a &lt;strong&gt;sequential scan&lt;/strong&gt; — it reads every row in the table, checks whether &lt;code&gt;email&lt;/code&gt; matches, and moves on. For a million rows, that's a million comparisons. You can see this happening with &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ada@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Seq Scan on users  (cost=0.00..20834.00 rows=1 width=72)
  Filter: (email = 'ada@example.com'::text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;Seq Scan&lt;/code&gt; label is telling you the database is walking through the entire table. For small tables this is fine — maybe even faster than using an index. But once your table grows, sequential scans become a real bottleneck.&lt;/p&gt;

&lt;h2&gt;
  
  
  B-tree indexes: the workhorse
&lt;/h2&gt;

&lt;p&gt;When you create an index in most databases, you're getting a &lt;strong&gt;B-tree index&lt;/strong&gt; by default. B-tree stands for "balanced tree," and you can picture it like a decision tree that narrows down where your data lives.&lt;/p&gt;

&lt;p&gt;Think of it this way: imagine a dictionary. You don't start at page one and read every word. You know that "M" words are roughly in the middle, so you open there. Then you narrow it down — "Ma" words, then "Mar," then "Martinez." Each step cuts out a huge chunk of the remaining pages.&lt;/p&gt;

&lt;p&gt;A B-tree works similarly. It organizes values in a sorted, tree-like structure where each level of the tree narrows the search space dramatically. Finding a value in a million-row table might only require checking 20 or so nodes in the tree, instead of scanning all million rows.&lt;/p&gt;

&lt;p&gt;Creating one is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run that same query again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ada@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Scan using idx_users_email on users  (cost=0.42..8.44 rows=1 width=72)
  Index Cond: (email = 'ada@example.com'::text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost dropped from ~20,834 to ~8. Instead of scanning the whole table, PostgreSQL jumped straight to the right spot via the index. That's the kind of difference that turns a 3-second API response into a 10-millisecond one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composite indexes: column order matters more than you think
&lt;/h2&gt;

&lt;p&gt;Sometimes your queries filter on more than one column. Say you're looking up orders by customer and status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You could create two separate indexes — one on &lt;code&gt;customer_id&lt;/code&gt; and one on &lt;code&gt;status&lt;/code&gt; — but a &lt;strong&gt;composite index&lt;/strong&gt; covering both columns is usually more efficient:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_orders_customer_status&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the thing that trips people up: the order of columns in a composite index matters a lot. Think of it like a phone book sorted by last name, then first name. You can quickly look up everyone named "Martinez," and within those, you can find "Sofia Martinez" easily. But you can't efficiently search for everyone named "Sofia" regardless of last name — the book isn't organized that way.&lt;/p&gt;

&lt;p&gt;The same logic applies to composite indexes. The index above works great for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WHERE customer_id = 42&lt;/code&gt; (uses the index — it's the leading column)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WHERE customer_id = 42 AND status = 'shipped'&lt;/code&gt; (uses both columns)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it won't help much with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;WHERE status = 'shipped'&lt;/code&gt; (the leading column is &lt;code&gt;customer_id&lt;/code&gt;, so the index can't narrow things down)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb is: &lt;strong&gt;put the column you filter on most often (or the most selective one) first&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When indexes hurt instead of help
&lt;/h2&gt;

&lt;p&gt;Indexes aren't free. Every index you add comes with trade-offs, and understanding them keeps you from making your database slower while trying to make it faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write overhead&lt;/strong&gt; is the big one. Every time you &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, or &lt;code&gt;DELETE&lt;/code&gt; a row, the database has to update not just the table but every index on that table too. If you have ten indexes on a table that gets thousands of writes per second, those index updates add up fast. Your reads get quicker, but your writes get slower.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage&lt;/strong&gt; is the other cost. An index is a real data structure sitting on disk, and for large tables, indexes can take up significant space — sometimes as much as the table itself.&lt;/p&gt;

&lt;p&gt;So indexes are a trade-off: faster reads in exchange for slower writes and more disk usage. For a table that gets read a thousand times for every write, indexes are almost always worth it. For a high-write logging table that rarely gets queried, you might want to keep indexes minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Covering indexes: skipping the table entirely
&lt;/h2&gt;

&lt;p&gt;Normally, when PostgreSQL uses an index to find matching rows, it still has to go back to the actual table to fetch the full row data. The index told it &lt;em&gt;where&lt;/em&gt; the row is, but it still needs to read the row itself. This is sometimes called a "table lookup" or "heap fetch."&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;covering index&lt;/strong&gt; is one that contains all the columns your query needs, so the database never has to visit the table at all. It gets everything it needs directly from the index.&lt;/p&gt;

&lt;p&gt;For example, suppose you frequently run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ada@example.com'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you create an index that includes both columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_users_email_created&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL can answer the query entirely from the index — it finds the email in the B-tree and the &lt;code&gt;created_at&lt;/code&gt; value is right there alongside it. You'll see this show up as an "Index Only Scan" in &lt;code&gt;EXPLAIN&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Index Only Scan using idx_users_email_created on users  (cost=0.42..4.44 rows=1 width=40)
  Index Cond: (email = 'ada@example.com'::text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That "Index Only Scan" means zero table lookups, which is about as fast as it gets. This technique is particularly useful for queries that run thousands of times per minute, where shaving off even a small amount of I/O adds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes (and how to avoid them)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Indexing every column.&lt;/strong&gt; More indexes doesn't mean faster. Each one costs write performance and storage. Be intentional — index columns that actually appear in your &lt;code&gt;WHERE&lt;/code&gt; clauses, &lt;code&gt;JOIN&lt;/code&gt; conditions, and &lt;code&gt;ORDER BY&lt;/code&gt; clauses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting to index columns you filter and join on.&lt;/strong&gt; This is the flip side. If you have a query that joins &lt;code&gt;orders&lt;/code&gt; to &lt;code&gt;customers&lt;/code&gt; on &lt;code&gt;customer_id&lt;/code&gt;, and there's no index on &lt;code&gt;orders.customer_id&lt;/code&gt;, that join is doing a sequential scan every time. Check your slow queries and see what columns they're filtering on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong column order in composite indexes.&lt;/strong&gt; As we covered above, an index on &lt;code&gt;(status, customer_id)&lt;/code&gt; won't help a query that only filters on &lt;code&gt;customer_id&lt;/code&gt;. Think about which queries will actually use the index, and put the most commonly filtered or most selective column first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never looking at EXPLAIN.&lt;/strong&gt; You don't have to guess whether an index is being used — the database will tell you. Get comfortable running &lt;code&gt;EXPLAIN&lt;/code&gt; (or &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; for actual execution times) on your slow queries. It takes ten seconds and saves hours of guessing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;ANALYZE&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'shipped'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you the query plan &lt;em&gt;and&lt;/em&gt; the actual execution time, so you can see exactly what changed after adding an index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules of thumb
&lt;/h2&gt;

&lt;p&gt;When you're deciding whether to add an index, these guidelines will serve you well in the vast majority of cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always index foreign keys.&lt;/strong&gt; If a column is used in &lt;code&gt;JOIN&lt;/code&gt; conditions, it should probably have an index. Some databases do this automatically; PostgreSQL does not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Index columns in your &lt;code&gt;WHERE&lt;/code&gt; clause&lt;/strong&gt; — especially on large tables where you're selecting a small percentage of rows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Think about selectivity.&lt;/strong&gt; An index on a &lt;code&gt;boolean&lt;/code&gt; column with two possible values rarely helps, because the database still has to read half the table. An index on &lt;code&gt;email&lt;/code&gt; (where every value is unique) is extremely effective.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fewer, smarter indexes beat more indexes.&lt;/strong&gt; One well-designed composite index can serve multiple queries. Three separate single-column indexes often can't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor before and after.&lt;/strong&gt; Use &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; to verify that your new index actually improves the query. Sometimes the optimizer ignores it, and you've added write overhead for nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't forget partial indexes.&lt;/strong&gt; In PostgreSQL, you can index only a subset of rows: &lt;code&gt;CREATE INDEX idx_active_users ON users (email) WHERE active = true;&lt;/code&gt; — smaller index, faster lookups, lower overhead.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;If you've been writing queries without thinking about indexes, now's a great time to open up your slowest endpoint, run &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; on its main query, and see what the database is actually doing. You might find a sequential scan on a table with millions of rows, and a single &lt;code&gt;CREATE INDEX&lt;/code&gt; statement could cut your response time by 99%.&lt;/p&gt;

&lt;p&gt;Indexes are one of those concepts where a little knowledge goes a long way. You don't need to be a DBA to get this right — you just need to understand the trade-offs and pay attention to your query plans. Start with your slowest queries, add indexes where they make sense, and measure the difference.&lt;/p&gt;

&lt;p&gt;Got a query that's been bugging you? Drop it in the comments — let's figure out if an index can fix it.&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>postgres</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
