<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged mysql</title>
<link>https://coderlegion.com/tag/mysql</link>
<description>Powered by Question2Answer</description>
<item>
<title>Flyway Migration: From Legacy Database to CI/CD</title>
<link>https://coderlegion.com/32961/flyway-migration-from-legacy-database-to-ci-cd</link>
<description>&lt;p&gt;Most teams think of Flyway only when they need to manage ongoing database schema changes. But what if your project already has a complete, stable schema and you’re only now introducing version control? Or what if you want to incorporate your schema into a CI/CD pipeline for reliable, reproducible deployments?&lt;/p&gt;
&lt;p&gt;In this guide, I’ll walk you through a real-world scenario where we used Flyway to baseline an existing MySQL database and then extended it to support version-controlled schema migrations in CI/CD, without disrupting production.&lt;/p&gt;
&lt;h3 id=&quot;what-is-flyway-&quot;&gt;What is Flyway?&lt;/h3&gt;
&lt;p&gt;Flyway is a simple but powerful tool for managing database migrations. It applies versioned SQL scripts to your database and tracks them in a special table called &lt;code&gt;flyway_schema_history&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Typical script names:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;V1__initial_schema.sql
V2__add_orders_table.sql
V3__rename_column.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Flyway ensures these scripts run only once and in the correct order. It also integrates well with CI/CD tools, making it ideal for automated environments.&lt;/p&gt;
&lt;h3 id=&quot;use-case-existing-schema-with-no-migrations-yet-&quot;&gt;Use Case: Existing Schema with No Migrations (Yet)&lt;/h3&gt;
&lt;p&gt;In our project, we had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A production MySQL database with dozens of tables&lt;/li&gt;
&lt;li&gt;No migration history&lt;/li&gt;
&lt;li&gt;No current need for structural changes&lt;/li&gt;
&lt;li&gt;A desire to version control the database and enable future migrations via CI/CD&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;step-1-baselining-an-existing-schema&quot;&gt;Step 1: Baselining an Existing Schema&lt;/h3&gt;
&lt;h4 id=&quot;dump-the-schema&quot;&gt;Dump the schema&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqldump -u root -p --no-data your_database &amp;gt; V1__baseline_schema.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean the output: remove auto-increments, user references, and environment-specific SQL.&lt;/p&gt;
&lt;h4 id=&quot;create-directory-structure&quot;&gt;Create directory structure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir -p db/sql
mv V1__baseline_schema.sql db/sql/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;configure-flyway-conf&quot;&gt;Configure flyway.conf&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;flyway.url=jdbc:mysql://your-db-host:3306/your_database
flyway.user=db_user
flyway.password=secret_password
flyway.locations=filesystem:db/sql
flyway.baselineOnMigrate=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;run-baseline&quot;&gt;Run baseline&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flyway baseline
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates the &lt;code&gt;flyway_schema_history&lt;/code&gt; table and marks version 1 as the current state without running any script.&lt;/p&gt;
&lt;h3 id=&quot;step-2-add-a-real-migration&quot;&gt;Step 2: Add a Real Migration&lt;/h3&gt;
&lt;p&gt;After baselining, we added a real change in a new script&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- db/sql/V2__add_status_to_orders.sql
ALTER TABLE orders ADD COLUMN status VARCHAR(20) DEFAULT 'pending';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we ran:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;flyway migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Flyway applied version 2, recorded it in history, and our database was now safely versioned.&lt;/p&gt;
&lt;h3 id=&quot;-step-3-integrate-flyway-into-ci-cd&quot;&gt;⚙️ Step 3: Integrate Flyway into CI/CD&lt;/h3&gt;
&lt;p&gt;You can integrate Flyway with GitHub Actions, GitLab CI, Bitbucket Pipelines, or Jenkins.&lt;/p&gt;
&lt;h3 id=&quot;-example-github-actions-workflow&quot;&gt;✅ Example GitHub Actions Workflow&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;name: Flyway Migration
on:
  push:
    branches: [ main ]

jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Code
        uses: actions/checkout@v3
      - name: Set up Java (Flyway needs it)
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '17'
      - name: Install Flyway
        run: |
          curl -L https://repo1.maven.org/maven2/org/flywaydb/flyway-commandline/10.8.1/flyway-commandline-10.8.1-linux-x64.tar.gz | tar xvz
          sudo ln -s $PWD/flyway-10.8.1/flyway /usr/local/bin/flyway
      - name: Run Flyway Migrations
        env:
          FLYWAY_URL: jdbc:mysql://your-host:3306/your_database
          FLYWAY_USER: ${{ secrets.DB_USER }}
          FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          flyway -url=$FLYWAY_URL \
                 -user=$FLYWAY_USER \
                 -password=$FLYWAY_PASSWORD \
                 -locations=filesystem:./db/sql \
                 migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;benefits-of-this-approach&quot;&gt;Benefits of This Approach&lt;/h3&gt;
&lt;p&gt;✅ You gain &lt;strong&gt;auditability&lt;/strong&gt; for all database changes&lt;/p&gt;
&lt;p&gt;✅ Developers can build &lt;strong&gt;local environments&lt;/strong&gt; using the same scripts&lt;/p&gt;
&lt;p&gt;✅ CI/CD ensures &lt;strong&gt;consistency&lt;/strong&gt; across all environments&lt;/p&gt;
&lt;p&gt;✅ Flyway’s &lt;code&gt;validate&lt;/code&gt; command catches missing or changed scripts&lt;/p&gt;
&lt;p&gt;✅ Schema management becomes part of your &lt;strong&gt;software delivery lifecycle&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;potential-challenges&quot;&gt;Potential Challenges&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Problem → Fix&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flyway re-applies the baseline script&lt;br&gt;
→ Use baselineOnMigrate=true and never modify a baseline&lt;/li&gt;
&lt;li&gt;Passwords in config files&lt;br&gt;
→ Use secrets and environment variables in CI/CD&lt;/li&gt;
&lt;li&gt;Manual schema drift&lt;br&gt;
→ Disable direct DB access in prod or run flyway validate periodically&lt;/li&gt;
&lt;li&gt;Need rollback support&lt;br&gt;
→ Write manual undo scripts or use point-in-time backups&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;alternatives-and-why-flyway-wins-&quot;&gt;Alternatives (and Why Flyway Wins)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt; Liquibase&lt;/strong&gt; &lt;br&gt;
✅ Pros: Advanced features, rollback support &lt;br&gt;
❌ Cons: More complex, steeper learning curve&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Django ORM / Alembic&lt;/strong&gt; &lt;br&gt;
✅ Pros: Built-in for app devs &lt;br&gt;
❌ Cons: Not cross-language or DB-agnostic&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Manual SQL or DB dumps&lt;/strong&gt; &lt;br&gt;
✅ Pros: Simple &lt;br&gt;
❌ Cons: No versioning, no tracking, no automation&lt;/p&gt;
&lt;p&gt;Flyway is a great middle-ground: powerful enough for teams, simple enough for solo devs, and perfect for CI/CD.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Flyway isn’t just for teams writing migrations every day. It’s also ideal for bringing an existing database under version control, giving your project a foundation for safe schema evolution.&lt;/p&gt;
&lt;p&gt;We started with a baseline, added our first real migration, and then built it into our CI/CD pipeline — all in a day’s work.&lt;/p&gt;
&lt;p&gt;If you’re managing an existing DB without schema tracking, now’s the time to fix that. Start with a baseline. Your future self will thank you.&lt;/p&gt;
&lt;h3 id=&quot;let-s-connect-&quot;&gt;Let’s Connect!&lt;/h3&gt;
&lt;p&gt;Have you tried Flyway for baseline-only use cases or integrated it into your CI/CD pipeline? I’d love to hear your experience in the comments.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you found this helpful, follow me here on DEV for more DevOps and database engineering content.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32961/flyway-migration-from-legacy-database-to-ci-cd</guid>
<pubDate>Thu, 29 Oct 2026 12:05:30 +0000</pubDate>
</item>
<item>
<title>Understanding Database Replication: How to Keep Your Apps Fast and Reliable</title>
<link>https://coderlegion.com/32656/understanding-database-replication-how-to-keep-your-apps-fast-and-reliable</link>
<description>&lt;h3 id=&quot;what-is-database-replication-&quot;&gt;What is Database Replication?&lt;/h3&gt;
&lt;p&gt;Database replication is the automated process of keeping multiple exact copies of a database synchronized across different server instances. When data is added, updated, or removed in the primary database, those changes are copied to one or more replica databases in near real-time. This setup ensures that if one database fails or experiences high traffic, the overall application remains operational, responsive, and secure against data loss.&lt;/p&gt;
&lt;h3 id=&quot;a-real-world-analogy-the-restaurant-kitchen-whiteboard&quot;&gt;A Real-World Analogy: The Restaurant Kitchen Whiteboard&lt;/h3&gt;
&lt;p&gt;Imagine a high-volume restaurant kitchen with a single Head Chef and five Line Cooks. Every time a new order arrives or a special menu item changes, the Head Chef writes it down on a master whiteboard in the center of the kitchen.&lt;/p&gt;
&lt;p&gt;If all five Line Cooks had to walk up to that single whiteboard every few seconds to read the latest order, a massive bottleneck would form around the Head Chef. Cooking would grind to a halt because everyone is waiting in line just to read information.&lt;/p&gt;
&lt;p&gt;To solve this, the kitchen hires a runner whose sole job is to copy whatever the Head Chef writes on the master whiteboard onto five individual clipboards stationed at each Line Cook's workstation. The Head Chef remains the only person writing new orders (the Primary database), while the Line Cooks read from their dedicated station clipboards (the Read Replicas). Orders flow instantly, cooks stay productive, and the head chef is never overwhelmed.&lt;/p&gt;
&lt;h3 id=&quot;why-database-replication-matters-in-modern-engineering&quot;&gt;Why Database Replication Matters in Modern Engineering&lt;/h3&gt;
&lt;p&gt;In real-world web applications, user activity is overwhelmingly biased toward reading data rather than writing it. For example, millions of people scroll through social media posts or view e-commerce products for every one person who creates a new post or completes a purchase. If every read request hit the exact same database server handling write operations, that single database would quickly exhaust its memory and processing power.&lt;/p&gt;
&lt;p&gt;Engineers use database replication to solve several critical challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;High Availability and Fault Tolerance&lt;/strong&gt;: A hardware failure or crash on the primary database server does not bring down the entire website. System administrators can quickly promote a replica to become the new primary writer with minimal downtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance Scaling&lt;/strong&gt;: Read traffic is distributed across multiple read-only replica servers. This practice, known as load balancing, prevents any single server from becoming a CPU bottleneck.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Heavy Reporting Offloading&lt;/strong&gt;: Running complex analytical queries (like generating monthly revenue reports) takes immense computing resources. Performing these queries on a replica ensures transactional write operations for live customers remain fast and unaffected.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;implementing-read-write-splitting-in-node-js&quot;&gt;Implementing Read/Write Splitting in Node.js&lt;/h3&gt;
&lt;p&gt;To take advantage of database replication, backend application code must route write commands (&lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;) to the primary database, while directing read queries (&lt;code&gt;SELECT&lt;/code&gt;) to replica nodes. A common database driver used in Node.js applications is &lt;code&gt;mysql2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is how you can set up dual connections in a Node.js module to execute read/write splitting:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// db.js - Database connection module for Primary and Replica instances
const mysql = require('mysql2/promise');

// Primary connection reserved strictly for write operations
const primaryPool = mysql.createPool({
  host: process.env.DB_PRIMARY_HOST || 'primary.db.example.com',
  user: 'admin',
  password: 'securepassword',
  database: 'store_db',
  connectionLimit: 10
});

// Replica connection pool used for read-only queries
const replicaPool = mysql.createPool({
  host: process.env.DB_REPLICA_HOST || 'replica-1.db.example.com',
  user: 'read_only_user',
  password: 'readpassword',
  database: 'store_db',
  connectionLimit: 20
});

// Function to execute write operations on the primary DB
async function executeWrite(sqlQuery, params) {
  const [result] = await primaryPool.execute(sqlQuery, params);
  return result;
}

// Function to execute read operations on the replica DB
async function executeRead(sqlQuery, params) {
  const [rows] = await replicaPool.execute(sqlQuery, params);
  return rows;
}

module.exports = {
  executeWrite,
  executeRead
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, calling &lt;code&gt;executeWrite()&lt;/code&gt; executes the SQL query on the primary database node, ensuring that structural data modifications land safely on the source authority. Calling &lt;code&gt;executeRead()&lt;/code&gt; queries the replica node, taking performance stress off the writer server.&lt;/p&gt;
&lt;h3 id=&quot;key-takeaway&quot;&gt;Key Takeaway&lt;/h3&gt;
&lt;p&gt;Database replication shifts application architecture from a fragile single point of failure into a redundant, horizontally scalable infrastructure. By separating write transactions from high-volume read requests, development teams guarantee that their software stays online and lightning-fast, even during massive user traffic surges.&lt;/p&gt;
&lt;hr&gt;
&lt;p align=&quot;center&quot;&gt;
  &lt;a href=&quot;https://www.buymeacoffee.com/saurav_tb_pandey&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer nofollow&quot;&gt;
    &lt;img src=&quot;https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&amp;amp;logo=buy-me-a-coffee&amp;amp;logoColor=black&quot; alt=&quot;Buy Me A Coffee&quot;&gt;
&lt;p&gt;  &lt;/p&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Enjoyed this breakdown? Support my open-source work on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.buymeacoffee.com/saurav_tb_pandey&quot;&gt;Buy Me a Coffee&lt;/a&gt; or explore &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/react-hook-lab&quot;&gt;react-hook-lab&lt;/a&gt;!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/10/scaling-applications-with-database.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32656/understanding-database-replication-how-to-keep-your-apps-fast-and-reliable</guid>
<pubDate>Tue, 27 Oct 2026 09:21:52 +0000</pubDate>
</item>
<item>
<title>Demystifying ACID Transactions: How to Keep Your Database from Making a Mess</title>
<link>https://coderlegion.com/29382/demystifying-acid-transactions-how-to-keep-your-database-from-making-a-mess</link>
<description>&lt;p&gt;An ACID transaction is a set of four database safety rules—Atomicity, Consistency, Isolation, and Durability—that ensure database operations are executed reliably. It guarantees that even if your application crashes, loses power, or experiences thousands of concurrent requests, your data remains accurate and uncorrupted. Simply put, it ensures your database never ends up in a half-finished, broken state.&lt;/p&gt;
&lt;h4 id=&quot;the-ticket-booth-analogy&quot;&gt;The Ticket Booth Analogy&lt;/h4&gt;
&lt;p&gt;Imagine buying a physical ticket at a busy movie theater box office. You hand the cashier a $15 bill, and they hand you the ticket. This exchange must happen as a single, indivisible unit. If you hand over your cash, but a sudden power outage occurs before the cashier can hand you the ticket, you would be furious if they kept your money. Conversely, the theater would lose out if you grabbed the ticket and walked away without paying.&lt;/p&gt;
&lt;p&gt;The transaction is &quot;all-or-nothing&quot;: either the cash changes hands &lt;em&gt;and&lt;/em&gt; you get the ticket, or the entire deal is canceled, and you keep your money. ACID acts as this strict box-office coordinator for your software's database, ensuring that multi-step operations never stop halfway.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-real-world-disasters&quot;&gt;Why It Matters: Real-World Disasters&lt;/h4&gt;
&lt;p&gt;In daily software engineering, we rely on ACID transactions to prevent catastrophic data errors, especially when building systems like financial ledgers, e-commerce checkouts, or reservation platforms.&lt;/p&gt;
&lt;p&gt;Without ACID, imagine a bank transfer system built on Node.js. When a user transfers $100, the system must perform two separate actions: deduct $100 from Account A, and add $100 to Account B. If the database server loses connection exactly halfway through this process, Account A loses money, but Account B never receives it. The $100 vanishes into the digital void.&lt;/p&gt;
&lt;p&gt;Similarly, in a high-traffic retail store, if two customers try to buy the very last item in stock at the exact same millisecond, a non-ACID database might let both transactions complete. This leaves you with an oversold item and an angry customer. By grouping these operations inside an ACID transaction, developers ensure that if any step fails, the database automatically rolls back all changes, restoring everything to its original state.&lt;/p&gt;
&lt;h4 id=&quot;implementing-acid-in-node-js-and-mysql&quot;&gt;Implementing ACID in Node.js and MySQL&lt;/h4&gt;
&lt;p&gt;Here is how we can implement a secure money transfer using Node.js and the standard &lt;code&gt;mysql2/promise&lt;/code&gt; library. By wrapping our queries in a transaction block, we guarantee data safety.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const mysql = require('mysql2/promise');

async function transferFunds(fromAccountId, toAccountId, amount) {
  const connection = await mysql.createConnection({
    host: 'localhost',
    user: 'root',
    database: 'bank_db'
  });

  try {
    // Start the ACID transaction
    await connection.beginTransaction();

    // Step 1: Deduct from the sender's account
    const [deductResult] = await connection.execute(
      'UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance &amp;gt;= ?',
      [amount, fromAccountId, amount]
    );

    if (deductResult.affectedRows === 0) {
      throw new Error('Insufficient funds or invalid sender account');
    }

    // Step 2: Add to the receiver's account
    const [creditResult] = await connection.execute(
      'UPDATE accounts SET balance = balance + ? WHERE id = ?',
      [amount, toAccountId]
    );

    if (creditResult.affectedRows === 0) {
      throw new Error('Invalid receiver account');
    }

    // If both steps succeed, permanently commit the changes to disk
    await connection.commit();
    console.log('Transaction completed successfully!');
  } catch (error) {
    // If anything fails, revert all changes immediately
    await connection.rollback();
    console.error('Transaction failed. Rolled back changes:', error.message);
  } finally {
    await connection.end();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;ACID transactions are the ultimate insurance policy for your application's state. While they introduce a minor performance trade-off due to locking rows during updates, they completely eliminate the need to write complex, error-prone fallback code. Shifting the responsibility of data integrity to your database engine lets you sleep easy knowing your system will never tell a lie.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/10/data-integrity-101-why-acid.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29382/demystifying-acid-transactions-how-to-keep-your-database-from-making-a-mess</guid>
<pubDate>Thu, 01 Oct 2026 10:28:33 +0000</pubDate>
</item>
<item>
<title>Database Concurrency 101: Optimistic vs. Pessimistic Locking</title>
<link>https://coderlegion.com/28791/database-concurrency-101-optimistic-vs-pessimistic-locking</link>
<description>&lt;p&gt;In modern software development, we often have to handle multiple users interacting with our application at the exact same time. Optimistic and pessimistic locking are two distinct strategies used to manage concurrency, which is the technical term for when multiple users or systems try to update the exact same database record simultaneously. While optimistic locking assumes conflicts are rare and verifies changes only at the very end of an operation, pessimistic locking assumes conflicts are highly likely and preemptively blocks others from accessing the data until the current operation is finished.&lt;/p&gt;
&lt;h4 id=&quot;a-relatable-everyday-analogy&quot;&gt;A Relatable Everyday Analogy&lt;/h4&gt;
&lt;p&gt;To understand the difference, imagine a collaborative digital workspace where you and your colleagues edit text files.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pessimistic locking&lt;/strong&gt; is like an old-school file server where only one person can open a document for editing at a time. The moment you open the file, the server locks it. If your colleague tries to open it, they receive a &quot;Read-Only&quot; warning and are completely blocked from making edits until you save, close, and release your lock on the file.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimistic locking&lt;/strong&gt;, on the other hand, is like a modern version control system like Git. Both you and your colleague can open, read, and edit the exact same document simultaneously. However, when you click &quot;Save,&quot; the system checks if anyone else has saved a new version of the document since you opened it. If no one has, your save succeeds. If your colleague managed to save their changes first, the system blocks your save, alerts you to a &quot;conflict,&quot; and asks you to review and merge the changes before trying again.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-in-the-tech-industry&quot;&gt;Why It Matters in the Tech Industry&lt;/h4&gt;
&lt;p&gt;In the tech industry, failing to implement a proper locking strategy leads to a highly destructive bug known as a &quot;lost update&quot; or a &quot;race condition&quot;—where the outcome of a process depends on the unpredictable sequence or timing of other events. &lt;/p&gt;
&lt;p&gt;Imagine a financial application where two family members try to withdraw $50 from a shared $100 bank account at the exact same millisecond. Without locking, both transactions will read the balance as $100 simultaneously. Both transactions will approve the $50 withdrawal, and both will attempt to write the new balance of $50 back to the database. The bank is now down $100, but the database incorrectly shows a remaining balance of $50 because the second write silently overwrote the first one. By using database locking, software engineers prevent this double-spending, protect system integrity, and ensure transactions are processed safely.&lt;/p&gt;
&lt;h4 id=&quot;implementation-in-node-js-and-mysql&quot;&gt;Implementation in Node.js and MySQL&lt;/h4&gt;
&lt;p&gt;Here is a simple example of how to implement &lt;strong&gt;optimistic locking&lt;/strong&gt; in a Node.js application using a MySQL database client. We do this by adding a &lt;code&gt;version&lt;/code&gt; column to our database table and verifying it during our update query.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// A helper function to update a user's bank balance using Optimistic Locking
async function updateAccountBalance(connection, accountId, withdrawAmount, currentVersion) {
  // We attempt to update the balance and increment the version number,
  // but ONLY if the version in the database matches the version we originally read.
  const query = `
    UPDATE bank_accounts 
    SET balance = balance - ?, version = version + 1 
    WHERE id = ? AND version = ?
  `;

  const [result] = await connection.execute(query, [withdrawAmount, accountId, currentVersion]);

  // If no rows were affected, it means the version changed in the background
  // because another transaction updated it first. We must roll back and retry.
  if (result.affectedRows === 0) {
    throw new Error(&quot;Concurrency Conflict: The account was updated by another process. Please retry.&quot;);
  }

  return &quot;Balance updated successfully!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-strategic-takeaway&quot;&gt;The Strategic Takeaway&lt;/h4&gt;
&lt;p&gt;Choosing between these two approaches is always a trade-off between speed and strict prevention. You should default to optimistic locking for applications with low-to-medium write conflicts because it keeps your application fast, highly available, and free of database bottlenecks. However, when dealing with highly critical operations where conflicts are frequent and data corruption is unacceptable—such as reservation systems or payment gateways—pessimistic locking is the superior choice, despite the performance hit of making users wait in a digital queue.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/how-to-handle-concurrent-writes.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28791/database-concurrency-101-optimistic-vs-pessimistic-locking</guid>
<pubDate>Sun, 27 Sep 2026 09:32:31 +0000</pubDate>
</item>
<item>
<title>Data Engineering Beyond &quot;Pretty Charts&quot;: How I Built an Aviation Telemetry Leakage Engine</title>
<link>https://coderlegion.com/28338/data-engineering-beyond-pretty-charts-how-i-built-an-aviation-telemetry-leakage-engine</link>
<description>&lt;h2 id=&quot;stop-tracking-on-time-flights-track-tarmac-fuel-destruction-instead-&quot;&gt;Stop Tracking &quot;On-Time&quot; Flights. Track Tarmac Fuel Destruction Instead.&lt;/h2&gt;
&lt;p&gt;Most aviation dashboards track gate-to-gate &quot;On-Time&quot; arrivals. The problem? &lt;strong&gt;It's a lagging indicator that completely masks why airlines silently hemorrhage millions.&lt;/strong&gt; Padded flight schedules disguise a massive bottleneck: chronic tarmac fuel destruction occurring right before takeoff.&lt;/p&gt;
&lt;p&gt;I built &lt;strong&gt;AeroOps&lt;/strong&gt;, an end-to-end telemetry and multi-domain operations leakage engine, to expose exactly where systems drop value between the hangar and the runway.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt; Repo &amp;amp; Technical Report:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hafsamotiwala/aero-ops-forensic-audit&quot;&gt;[GitHub Link]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Stack:&lt;/strong&gt; MySQL (Star Schema &amp;amp; CTEs) · Telemetry Data Modeling · Executive Dashboard Architecture · Power BI (DAX)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-architectural-amp-engineering-challenge&quot;&gt;The Architectural &amp;amp; Engineering Challenge&lt;/h4&gt;
&lt;p&gt;Handling flight telemetry means dealing with high-volume, disjointed datasets. To make this actionable, the engineering required:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Star Schema Data Warehousing:&lt;/strong&gt; Joined &lt;strong&gt;513,000+ flight legs&lt;/strong&gt; with precise technical burn parameters pulled directly from the &lt;em&gt;ICAO Aircraft Emissions Databank&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bypassing Relational Boundaries:&lt;/strong&gt; Formulated deduplicated DAX lookup models to handle wide-body propulsion penalties under sub-second memory constraints.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Algorithmic Baseline Filters:&lt;/strong&gt; Enforced a strict &lt;strong&gt;4-minute engine warm-up buffer&lt;/strong&gt; directly into the SQL logic. This stripped away safe, expected operational windows to isolate true, excess ground latency.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-bottom-line-impact-metrics&quot;&gt;The Bottom-Line Impact Metrics&lt;/h4&gt;
&lt;p&gt;By querying raw transaction logs and user event data instead of building passive, &quot;pretty&quot; charts, the engine surfaced massive operational friction:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt; $42.69M in Tarmac Cash Destruction:&lt;/strong&gt; Isolated and quantified the exact cost of ground traffic gridlocks at major hubs like Atlanta (ATL).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; 54% to 57% Short-Haul Menu Spoilage:&lt;/strong&gt; Exposed a massive waste loop across major carriers (Delta/American) due to over-boarding. Transitioning the system to a 100% shelf-stable inventory model &lt;strong&gt;reclaimed $2.58M in simulated margin leaks&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; $725K Legal Fine Exposure:&lt;/strong&gt; Isolated a hidden &lt;strong&gt;9% telemetry sync drop rate&lt;/strong&gt; specifically among regional commuter airframes caused by corrupted logs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt; Prevention of $1.50M Asset Groundings (AOG):&lt;/strong&gt; Coded an automated Reorder Point (ROP) trigger tool to insulate warehouses against catastrophic out-of-stock asset scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-takeaway-1&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Data shouldn't just look nice in a presentation deck. If your queries aren't actively protecting business margins or exposing systemic infrastructure leaks, you're looking at the wrong metrics. &lt;/p&gt;
&lt;p&gt;If your team is scaling fast and dealing with messy relational pipelines or unoptimized telemetry data, let's connect and fix your backend infrastructure loops. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28338/data-engineering-beyond-pretty-charts-how-i-built-an-aviation-telemetry-leakage-engine</guid>
<pubDate>Wed, 23 Sep 2026 12:10:54 +0000</pubDate>
</item>
<item>
<title>The 200-byte trap: why WordPress core updates break Arabic URLs</title>
<link>https://coderlegion.com/28135/the-200-byte-trap-why-wordpress-core-updates-break-arabic-urls</link>
<description>&lt;p&gt;You update WordPress on a quiet afternoon, a routine release, the kind you've installed a hundred times. The dashboard says everything went fine. Then the 404s start: not a handful, but every long-headlined article in your archive, all at once, all in Arabic.&lt;/p&gt;
&lt;p&gt;Nothing in the update log mentions it. No plugin changed. And the cruel part: the data that made those URLs work is already gone, shaved off inside a database-upgrade routine that ran for a few milliseconds and reported success. This isn't a freak accident or a broken plugin. It's three separate assumptions baked into WordPress core, each hard-coding the same number, &lt;strong&gt;200&lt;/strong&gt;, and Arabic sites are almost uniquely exposed to all three. We hit this running WordPress for Arabic newsrooms, the same high-traffic publishing we've written about &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/insights/breaking-news-wordpress-aws-architecture&quot;&gt;surviving breaking-news spikes&lt;/a&gt;. We traced it to the exact lines in core and built a fix that survives every future update. Here's the whole story.&lt;/p&gt;
&lt;h3 id=&quot;why-arabic-urls-hit-a-wall-english-never-does&quot;&gt;Why Arabic URLs hit a wall English never does&lt;/h3&gt;
&lt;p&gt;WordPress stores a post's slug in the &lt;code&gt;post_name&lt;/code&gt; column of &lt;code&gt;wp_posts&lt;/code&gt;, and a category or tag slug in the &lt;code&gt;slug&lt;/code&gt; column of &lt;code&gt;wp_terms&lt;/code&gt;. Both are &lt;code&gt;VARCHAR(200)&lt;/code&gt; by default: room for 200 characters, which for an English headline is generous. &lt;em&gt;&quot;Everything you need to know about our new pricing&quot;&lt;/em&gt; is barely fifty. You'd have to write a paragraph to run out.&lt;/p&gt;
&lt;p&gt;Arabic is a different arithmetic, because of what WordPress actually stores in that column. It doesn't keep the raw Arabic text; it stores the &lt;strong&gt;percent-encoded&lt;/strong&gt; form, the same &lt;code&gt;%XX&lt;/code&gt; sequence that travels in the URL. Take a single word:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;الذكاء  →  %d8%a7%d9%84%d8%b0%d9%83%d8%a7%d8%a1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every Arabic letter is two bytes in UTF-8, and every byte becomes a three-character &lt;code&gt;%XX&lt;/code&gt; token. So &lt;strong&gt;one Arabic character costs about six characters of column space.&lt;/strong&gt; Do the division: &lt;code&gt;VARCHAR(200)&lt;/code&gt; holds roughly &lt;strong&gt;33 Arabic characters.&lt;/strong&gt; A normal news headline, &lt;em&gt;&quot;القبض على المتهمين في قضية الاحتيال الإلكتروني&quot;&lt;/em&gt;, blows past that before it's halfway done.&lt;/p&gt;
&lt;p&gt;So Arabic publishers learn early to widen the columns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE wp_posts MODIFY post_name VARCHAR(1024) NOT NULL DEFAULT '';
ALTER TABLE wp_terms MODIFY slug      VARCHAR(1024) NOT NULL DEFAULT '';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;VARCHAR(1024)&lt;/code&gt; holds about 170 Arabic characters, comfortable. Permalinks resolve, the newsroom publishes, and the problem looks solved.&lt;/p&gt;
&lt;p&gt;It isn't. It's about &lt;strong&gt;one-third&lt;/strong&gt; solved, and the other two-thirds are waiting for the next core update. (This isn't only an Arabic problem, either: any non-Latin script that percent-encodes (Persian, Urdu, Hebrew, Greek) hits the same wall. Arabic publishers just hit it first; scripts that take three bytes a character, such as Hindi, Thai or Chinese, hit it harder still, at about 22 characters to Arabic's 33.)&lt;/p&gt;
&lt;h3 id=&quot;the-number-200-lives-in-three-places-not-one&quot;&gt;The number 200 lives in three places, not one&lt;/h3&gt;
&lt;p&gt;Here's the insight that turns this from a recurring mystery into something you can actually close out. WordPress hard-codes &quot;200&quot; in &lt;strong&gt;three independent places&lt;/strong&gt;, and widening the column only touches the first:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Layer &lt;/th&gt;&lt;th&gt;What it caps at 200 &lt;/th&gt;&lt;th&gt;Does widening the column fix it?&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;1, Storage&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;the column that physically stores the slug &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;, but a core update silently undoes it&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;2, Generation&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;every new slug, the moment it is created &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;, it's PHP; needs a code override&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;3, De-duplication&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;slugs that collide and need a numeric suffix &lt;/td&gt;&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;, but this only fires on collisions&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Your &lt;code&gt;ALTER&lt;/code&gt; fixes Layer 1's &lt;em&gt;storage&lt;/em&gt;. But Layer 2 means that even with a 1024-wide column, every new slug is still cut to 200 bytes &lt;em&gt;before it's ever saved&lt;/em&gt;, unless something on your site already overrode it. (If long Arabic slugs save correctly for you today, a plugin, your theme, or a past developer already patched Layer 2, which means you have an undocumented dependency you didn't know you were relying on.) And Layer 1, the storage you so carefully widened, is exactly the one a core update quietly reverts. Let's take each in turn.&lt;/p&gt;
&lt;h3 id=&quot;layer-1-how-a-core-update-erases-your-column-width&quot;&gt;Layer 1: how a core update erases your column width&lt;/h3&gt;
&lt;p&gt;WordPress keeps a canonical description of its own database schema, and on every major update it reconciles the live database against that description. The chain is short and deterministic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A new release bumps &lt;code&gt;$wp_db_version&lt;/code&gt; in &lt;code&gt;version.php&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On the next admin request, &lt;code&gt;wp_upgrade()&lt;/code&gt; notices the bump and runs the database upgrade.&lt;/li&gt;
&lt;li&gt;That calls &lt;code&gt;make_db_current_silent()&lt;/code&gt;, which calls &lt;code&gt;dbDelta( wp_get_db_schema() )&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;wp_get_db_schema()&lt;/code&gt; is the canonical schema, and it says, verbatim:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;post_name varchar(200) NOT NULL default '',
...
slug varchar(200) NOT NULL default '',
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dbDelta()&lt;/code&gt; then runs a &lt;code&gt;DESCRIBE&lt;/code&gt; on every live table, compares each column to the canonical definition, and for any mismatch emits an &lt;code&gt;ALTER TABLE … CHANGE COLUMN&lt;/code&gt; to &quot;correct&quot; it. Your &lt;code&gt;post_name&lt;/code&gt; is &lt;code&gt;VARCHAR(1024)&lt;/code&gt;; the canonical says &lt;code&gt;VARCHAR(200)&lt;/code&gt;; they don't match, so dbDelta dutifully issues a &lt;code&gt;CHANGE COLUMN&lt;/code&gt; back to 200, MySQL rebuilds the table, and every byte past the 200th is discarded.&lt;/p&gt;
&lt;p&gt;Here's the detail that makes it unrecoverable. Years ago, core added &lt;em&gt;downsize protection&lt;/em&gt; to dbDelta, but &lt;strong&gt;only for &lt;code&gt;TEXT&lt;/code&gt; and &lt;code&gt;BLOB&lt;/code&gt; columns&lt;/strong&gt; (it was &lt;a rel=&quot;nofollow&quot; href=&quot;https://core.trac.wordpress.org/ticket/36748&quot;&gt;Trac #36748&lt;/a&gt;, born from exactly this kind of data loss). &lt;code&gt;VARCHAR&lt;/code&gt; got no such guard. So a &lt;code&gt;TEXT&lt;/code&gt; column is safe from accidental shrinking; your &lt;code&gt;VARCHAR(1024)&lt;/code&gt; slug column is not. dbDelta shrinks it without hesitation, and restoring the column to 1024 afterwards brings back the width but &lt;strong&gt;not the data&lt;/strong&gt;; those bytes were handed to MySQL and dropped.&lt;/p&gt;
&lt;p&gt;That's why your fix keeps coming undone: always right after an update, always silently. It isn't a bug aimed at you; it's core doing exactly what it was designed to do, to a column you were never expected to change.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;For accuracy: a separate routine, &lt;code&gt;pre_schema_upgrade()&lt;/code&gt;, also runs hand-written&lt;br&gt;
&lt;code&gt;ALTER&lt;/code&gt;s, but those are version-gated to historic migrations and won't touch a&lt;br&gt;
modern site's &lt;code&gt;post_name&lt;/code&gt;. The reversion you're seeing is dbDelta.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;the-fix-don-t-repair-the-damage-prevent-the-shrink&quot;&gt;The fix: don't repair the damage, prevent the shrink&lt;/h3&gt;
&lt;p&gt;The instinct is to write something that detects the reverted column and re-widens it after each update. Resist it. By the time you can detect the shrink, the data is already gone; you'd just be putting an empty wider column back over truncated slugs. The only fix that actually &lt;em&gt;saves the data&lt;/em&gt; is one that stops the shrink from ever happening.&lt;/p&gt;
&lt;p&gt;And core hands you the seam to do it. dbDelta exposes a filter, &lt;code&gt;dbdelta_create_queries&lt;/code&gt;, that receives the canonical &lt;code&gt;CREATE TABLE&lt;/code&gt; statements &lt;em&gt;before&lt;/em&gt; they're compared to the live database. Rewrite the canonical &lt;code&gt;post_name varchar(200)&lt;/code&gt; to &lt;code&gt;varchar(1024)&lt;/code&gt; in that filter, and dbDelta's idea of &quot;correct&quot; now matches what's actually on disk. It sees no mismatch. It emits no &lt;code&gt;CHANGE COLUMN&lt;/code&gt;. There is nothing to truncate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Plugin Name: Arabic Slug Schema Guard (must-use)
 * Description: Keeps wp_posts.post_name and wp_terms.slug at VARCHAR(1024) across
 *              core DB upgrades, so long Arabic slugs are never truncated.
 *              Prevention-first: dbDelta never even tries to shrink them.
 */
defined( 'ABSPATH' ) || exit;

const SLUG_COLUMN_LEN = 1024;

// Rewrite the canonical schema dbDelta diffs against, so &quot;desired&quot; already
// equals the live 1024-wide column, so no CHANGE COLUMN is ever emitted.
add_filter( 'dbdelta_create_queries', function ( $queries ) {
    global $wpdb;

    foreach ( array( $wpdb-&amp;gt;posts =&amp;gt; 'post_name', $wpdb-&amp;gt;terms =&amp;gt; 'slug' ) as $table =&amp;gt; $column ) {
        if ( isset( $queries[ $table ] ) ) {
            $queries[ $table ] = preg_replace(
                '/(\b' . $column . '\s+varchar\()\s*200\s*(\))/i',
                '${1}' . SLUG_COLUMN_LEN . '${2}',
                $queries[ $table ]
            );
        }
    }
    return $queries;
} );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That single filter is the whole prevention story for Layer 1, and it's robust in a way an after-the-fact repair can never be: it runs &lt;em&gt;inside&lt;/em&gt; dbDelta, so it covers &lt;strong&gt;every&lt;/strong&gt; path that triggers a schema reconcile: the admin &quot;database update required&quot; screen, background auto-updates, and &lt;code&gt;wp core update-db&lt;/code&gt; on the CLI alike.&lt;/p&gt;
&lt;p&gt;Where this code lives matters as much as what it does. Put it in a &lt;strong&gt;must-use plugin&lt;/strong&gt; (a single &lt;code&gt;.php&lt;/code&gt; file in &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt;), not a regular plugin. MU-plugins always load, can't be deactivated from the dashboard, and load &lt;em&gt;before&lt;/em&gt; the upgrade routine runs. And because a core update only ever replaces &lt;code&gt;wp-admin&lt;/code&gt;, &lt;code&gt;wp-includes&lt;/code&gt; and the root PHP files (it never touches &lt;code&gt;wp-content&lt;/code&gt;), your guard is guaranteed to still be in place at the exact moment the upgrade fires. A regular plugin could be deactivated the one time it mattered; don't bet your archive on a toggle.&lt;/p&gt;
&lt;h3 id=&quot;layer-2-stop-new-slugs-truncating-at-birth&quot;&gt;Layer 2: stop new slugs truncating at birth&lt;/h3&gt;
&lt;p&gt;Prevention on the column keeps your &lt;em&gt;existing&lt;/em&gt; slugs safe. But Layer 2 is still cutting every &lt;em&gt;new&lt;/em&gt; slug to 200 bytes at the moment of creation, inside &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;. The relevant line, unchanged in current core, is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;$title = utf8_uri_encode( $title, 200 );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That function is attached to the &lt;code&gt;sanitize_title&lt;/code&gt; filter, so you can cleanly swap it for an identical copy that uses a larger byte budget:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Layer 2: replace core's slug generator with a byte-for-byte copy whose only
// change is the encoding cap. The copy self-tests against core (see below), so a
// future core rewrite of this function is caught instead of drifting silently.
remove_filter( 'sanitize_title', 'sanitize_title_with_dashes' );
add_filter( 'sanitize_title', 'asg_sanitize_title_with_dashes', 10, 3 );

function asg_sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) {
    // … the body is line-for-line core, with one change:
    //     utf8_uri_encode( $title, 200 )  →  utf8_uri_encode( $title, 1000 )
    // 1000 leaves headroom under the 1024 column for a &quot;-2&quot; collision suffix.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pick a sane budget: around 1000 bytes (≈ 160 Arabic characters) leaves room under the column for the numeric suffix WordPress adds to resolve duplicates. You don't want &lt;em&gt;unlimited&lt;/em&gt; slugs; you want &lt;em&gt;enough.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Layer 3 (&lt;code&gt;_truncate_post_slug()&lt;/code&gt;) caps at 200 too, but it only runs when a slug collides and needs a &lt;code&gt;-2&lt;/code&gt; suffix. For unique news headlines that almost never fires, and it isn't filterable, so most sites can leave it alone. If you genuinely need more than 200 bytes &lt;em&gt;even on collisions&lt;/em&gt;, you take over uniqueness through the &lt;code&gt;pre_wp_unique_post_slug&lt;/code&gt; filter, an advanced case worth a footnote, not a paragraph.&lt;/p&gt;
&lt;h4 id=&quot;keeping-the-fork-honest&quot;&gt;Keeping the fork honest&lt;/h4&gt;
&lt;p&gt;Copying a core function buys a maintenance debt: the day WordPress rewrites &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;, your copy diverges, &lt;em&gt;silently&lt;/em&gt;, which is the exact failure mode we're trying to kill. Pinning the copy to a core version and re-diffing by hand works right up until the release someone forgets to check, which on a long-lived site is &lt;em&gt;when&lt;/em&gt;, not &lt;em&gt;if&lt;/em&gt;. So don't rely on remembering. Make the plugin re-diff itself.&lt;/p&gt;
&lt;p&gt;The lever: &lt;code&gt;remove_filter()&lt;/code&gt; only unhooks the function; &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt; stays &lt;strong&gt;callable&lt;/strong&gt;. So you can use core's own current code as a live oracle and assert your fork still agrees with it on &lt;strong&gt;short&lt;/strong&gt; inputs, where neither byte cap fires and the only thing left that could differ is the cleanup logic:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Run once per core version. Short fixtures =&amp;gt; neither length cap engages,
// so any difference is pure logic drift, not the byte budget.
foreach ( array( 'الذكاء الاصطناعي', 'عاجل: «مهم» اليوم', 'foo/bar — baz' ) as $f ) {
    if ( sanitize_title_with_dashes( $f, $f, 'save' )
         !== asg_sanitize_title_with_dashes( $f, $f, 'save' ) ) {
        // core changed the cleanup logic: log + email ops, and (optionally) fall
        // back to core's generator so slugs cap at 200 again: degraded, but safe
        // and known, never a malformed slug from a stale copy.
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This beats a source-hash check on two counts. It's &lt;strong&gt;semantic, not textual&lt;/strong&gt;: it stays quiet when core only touches the length handling (which you override anyway) and fires only when a change actually breaks &lt;em&gt;your&lt;/em&gt; output, so the alerts that do fire get acted on. And it &lt;strong&gt;degrades safely&lt;/strong&gt;: on drift you fall back to core's exact behaviour: worst case, new slugs cap at 200 until you re-sync, never a half-encoded slug from a stale copy. Loud-and-safe beats silent-and-wrong. The full guard (with the per-core-version cache and the optional fail-safe switch) is in the complete plugin below.&lt;/p&gt;
&lt;h3 id=&quot;a-tripwire-because-silent-failure-is-the-real-enemy&quot;&gt;A tripwire, because silent failure is the real enemy&lt;/h3&gt;
&lt;p&gt;The filter prevents the known incident. But the failure mode that actually hurt you was that it happened &lt;em&gt;silently&lt;/em&gt;, so the last piece is making sure you'd never again learn about it from a drop in search traffic. The same MU-plugin can verify the column widths after every update and shout if anything is wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hook &lt;code&gt;upgrader_process_complete&lt;/code&gt; to flag that a core update just ran.&lt;/li&gt;
&lt;li&gt;On the next admin request, check the real column widths in &lt;code&gt;information_schema&lt;/code&gt; and, if either has shrunk, write to the error log and email ops.&lt;/li&gt;
&lt;li&gt;Expose a WP-CLI command for cron-based monitoring:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Nightly: verify the slug columns, email ops if either has reverted.
0 3 * * *  cd /var/www/site &amp;amp;&amp;amp; wp asg verify | grep -q REVERTED \
           &amp;amp;&amp;amp; wp asg verify | mail -s &quot;WP slug schema reverted on $(hostname)&quot; *Emails are not allowed*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One discipline to internalise: this tripwire restores &lt;em&gt;confidence&lt;/em&gt;, not &lt;em&gt;data&lt;/em&gt;. If it ever reports a reverted column, treat it as an incident, not a self-healing event; the slugs longer than the new width are already gone and must come back from a backup. Prevention (the filter) and backups are your safety net; the tripwire just tells you when to reach for them.&lt;/p&gt;
&lt;h3 id=&quot;-isn-t-varchar-1024-dangerous-no-and-the-reason-is-reassuring&quot;&gt;&quot;Isn't VARCHAR(1024) dangerous?&quot;: no, and the reason is reassuring&lt;/h3&gt;
&lt;p&gt;Widening these columns &lt;em&gt;sounds&lt;/em&gt; risky (indexes, utf8mb4, InnoDB limits), and it's the part people most often get scared off by. It's almost entirely a non-issue, and one fact explains why:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The index on these columns is a fixed 191-character &lt;em&gt;prefix&lt;/em&gt;, independent of the column's declared length.&lt;/strong&gt; Core defines &lt;code&gt;KEY post_name (post_name(191))&lt;/code&gt;; it indexes the first 191 characters whether the column is 200 wide or 1024. Widening the column doesn't enlarge the index by a single byte.&lt;/p&gt;
&lt;p&gt;That dissolves the usual worries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Index / InnoDB prefix limits.&lt;/strong&gt; The old InnoDB prefix ceiling is 767 bytes; WordPress chose 191 because 191 × 4 (utf8mb4's worst case) = 764, just under it. Because the prefix stays at 191 no matter how wide the column grows, you never move &lt;em&gt;towards&lt;/em&gt; that limit. (A quiet irony: slugs are stored as percent-encoded ASCII, so they were never going to hit four bytes per character anyway.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Row size.&lt;/strong&gt; &lt;code&gt;VARCHAR&lt;/code&gt; stores the &lt;em&gt;actual&lt;/em&gt; length of each value, not its declared maximum; a 30-character slug takes ~30 bytes in a &lt;code&gt;VARCHAR(1024)&lt;/code&gt; exactly as it would in a &lt;code&gt;VARCHAR(200)&lt;/code&gt;. Rows don't get bigger because the ceiling moved up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query plans.&lt;/strong&gt; Lookups use the same 191-prefix index to narrow, then verify the full value. Identical behaviour at 200 or 1024.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plugins.&lt;/strong&gt; SEO plugins, caches and CDNs read &lt;code&gt;post_name&lt;/code&gt; and emit longer URLs without complaint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The genuine risks aren't in steady state; they're tools that &lt;strong&gt;recreate the schema&lt;/strong&gt; from an old definition: a migration plugin that runs its own &lt;code&gt;CREATE TABLE&lt;/code&gt;, or, most commonly, restoring a SQL dump taken &lt;em&gt;before&lt;/em&gt; you widened the columns. A dump of the &lt;em&gt;current&lt;/em&gt; database preserves &lt;code&gt;VARCHAR(1024)&lt;/code&gt; correctly; importing an old structure silently puts you back at 200. Write that one down in your runbook.&lt;/p&gt;
&lt;h3 id=&quot;doing-it-safely-on-a-site-with-millions-of-posts&quot;&gt;Doing it safely on a site with millions of posts&lt;/h3&gt;
&lt;p&gt;There's one operational gotcha, and it's about the migration itself, not the result. Going from &lt;code&gt;VARCHAR(200)&lt;/code&gt; to &lt;code&gt;VARCHAR(1024)&lt;/code&gt; &lt;strong&gt;cannot&lt;/strong&gt; be an instant, in-place change. InnoDB only keeps a &lt;code&gt;VARCHAR&lt;/code&gt; length change &quot;in place&quot; while both lengths stay in the same size class: values under 256 use one length byte, values 256 and over use two. Crossing 255 forces &lt;code&gt;ALGORITHM=COPY&lt;/code&gt;: a full table rebuild. On a &lt;code&gt;wp_posts&lt;/code&gt; with millions of rows, a raw &lt;code&gt;ALTER&lt;/code&gt; means a long operation and a problematic lock.&lt;/p&gt;
&lt;p&gt;So on a large live site, do the initial widening with an online schema-change tool, not a bare &lt;code&gt;ALTER&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pt-online-schema-change \
  --alter &quot;MODIFY post_name VARCHAR(1024) NOT NULL DEFAULT ''&quot; \
  --chunk-time=0.5 --max-load Threads_running=50 \
  --execute D=wordpress,t=wp_posts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;gh-ost&lt;/code&gt; is an equally good triggerless alternative.) &lt;code&gt;wp_terms&lt;/code&gt; is tiny and takes a plain &lt;code&gt;ALTER&lt;/code&gt; without ceremony; &lt;code&gt;wp_posts&lt;/code&gt; is the one that needs the online tool. The rollout that's bitten no one:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Back up&lt;/strong&gt;, and confirm the dump shows &lt;code&gt;VARCHAR(1024)&lt;/code&gt; afterwards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clone to staging&lt;/strong&gt;, deploy the MU-plugin, and run a &lt;em&gt;real&lt;/em&gt; major core update there. Confirm no &lt;code&gt;CHANGE COLUMN&lt;/code&gt; appears and the verify command reports &lt;code&gt;OK&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy the MU-plugin to production &lt;em&gt;before&lt;/em&gt; the next core update&lt;/strong&gt;: prevention has to be in place ahead of the event it prevents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Widen the production columns off-peak&lt;/strong&gt; with pt-osc / gh-ost.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a nightly verify cron and a 404 monitor&lt;/strong&gt;, so anything unexpected surfaces in hours, not in a month of lost rankings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the MU-plugin in version control&lt;/strong&gt;, and never import a pre-widening structure dump.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;the-deeper-fix-stop-betting-your-urls-on-a-column-width&quot;&gt;The deeper fix: stop betting your URLs on a column width&lt;/h3&gt;
&lt;p&gt;Everything above makes Arabic slugs safe. But there's a more permanent way to think about it, and for a large news archive it's worth the shift: &lt;strong&gt;don't let the URL depend on the slug surviving at all.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anchor your permalinks on the numeric post ID, and keep the Arabic slug as a descriptive (but cosmetic) suffix:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;https://example.com/123456/الذكاء-الاصطناعي
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resolve the post by its ID, and the slug becomes decoration. If it's ever truncated, malformed, or edited, the URL &lt;strong&gt;still resolves&lt;/strong&gt; to the right article instead of 404-ing, and WordPress can &lt;code&gt;301&lt;/code&gt; to the canonical form. You keep the Arabic keywords in the URL for search relevance &lt;em&gt;and&lt;/em&gt; you become structurally immune to the entire class of problem. (If you want to stop touching core tables altogether, you can go one step further and store the long slug in your own indexed routing table, which dbDelta has no opinion about, but for most teams, ID-anchored permalinks are the sweet spot.)&lt;/p&gt;
&lt;p&gt;That's the difference between patching a bug and designing it out.&lt;/p&gt;
&lt;h3 id=&quot;the-complete-plugin&quot;&gt;The complete plugin&lt;/h3&gt;
&lt;p&gt;Everything above, assembled into one must-use plugin. Drop it in &lt;code&gt;wp-content/mu-plugins/wp-arabic-slug-schema-guard.php&lt;/code&gt;, widen the columns once with an online schema-change tool, and all three layers, plus the tripwire, are handled in a single place. The slug generator is a copy of core's &lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;; only the byte cap differs.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;📦 &lt;strong&gt;Prefer it ready-made?&lt;/strong&gt; The plugin is open-source on GitHub as &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mantekio/wp-arabic-slug-schema-guard&quot;&gt;&lt;code&gt;mantekio/wp-arabic-slug-schema-guard&lt;/code&gt;&lt;/a&gt;, or &lt;code&gt;composer require mantekio/wp-arabic-slug-schema-guard&lt;/code&gt;. Star it, file an issue, or lift it whole.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
/**
 * Plugin Name: Arabic Slug Schema Guard (must-use)
 * Description: Keeps wp_posts.post_name and wp_terms.slug at VARCHAR(1024) across
 *              core DB upgrades, lets core generate long URL-encoded Arabic slugs,
 *              and verifies + alerts after every update. Prevention-first:
 *              dbDelta never truncates, because the canonical schema it diffs
 *              against already says 1024. The slug fork self-tests against core
 *              on each update and can fail safe.
 */
defined( 'ABSPATH' ) || exit;

const ASG_COLUMN_LEN = 1024;  // physical column width (bytes)
const ASG_SLUG_BYTES = 1000;  // max generated slug length, under the column width
const ASG_SCHEMA_SIG = 'post_name:1024;slug:1024';
// define( 'ASG_ALERT_EMAIL', '*Emails are not allowed*' );  // optional, set in wp-config.php
// define( 'ASG_L2_FAILSAFE', true );               // optional: on Layer-2 drift, fall back to core (slugs cap at 200) until you re-sync

/* LAYER 1: PREVENTION
 * Rewrite the canonical CREATE TABLE dbDelta diffs against, so &quot;desired&quot; already
 * equals the live 1024-wide column and no destructive CHANGE COLUMN is emitted. */
add_filter( 'dbdelta_create_queries', function ( $queries ) {
    global $wpdb;
    foreach ( array( $wpdb-&amp;gt;posts =&amp;gt; 'post_name', $wpdb-&amp;gt;terms =&amp;gt; 'slug' ) as $table =&amp;gt; $column ) {
        if ( isset( $queries[ $table ] ) ) {
            $queries[ $table ] = preg_replace(
                '/(\b' . $column . '\s+varchar\()\s*200\s*(\))/i',
                '${1}' . ASG_COLUMN_LEN . '${2}',
                $queries[ $table ]
            );
        }
    }
    return $queries;
} );

/* LAYER 2: GENERATION
 * Core caps new slugs at 200 bytes via utf8_uri_encode($title, 200). Swap in a
 * copy of sanitize_title_with_dashes() whose only change is the byte budget.
 * Installed conditionally: if the drift guard (below) flagged THIS core version
 * and ASG_L2_FAILSAFE is on, stay on core's default (caps at 200, degraded but
 * safe and known) rather than run a fork we know has drifted. */
if ( get_option( 'asg_l2_status' ) === 'drift:' . $GLOBALS['wp_version']
     &amp;amp;&amp;amp; defined( 'ASG_L2_FAILSAFE' ) &amp;amp;&amp;amp; ASG_L2_FAILSAFE ) {
    // Known-drifted on this core version → leave core's generator in place.
} else {
    remove_filter( 'sanitize_title', 'sanitize_title_with_dashes' );
    add_filter( 'sanitize_title', 'asg_sanitize_title_with_dashes', 10, 3 );
}

function asg_sanitize_title_with_dashes( $title, $raw_title = '', $context = 'display' ) {
    $title = strip_tags( $title );
    // Preserve already-encoded octets through the cleanup below.
    $title = preg_replace( '|%([a-fA-F0-9][a-fA-F0-9])|', '---$1---', $title );
    $title = str_replace( '%', '', $title );
    $title = preg_replace( '|---([a-fA-F0-9][a-fA-F0-9])---|', '%$1', $title );

    if ( seems_utf8( $title ) ) {
        if ( function_exists( 'mb_strtolower' ) ) {
            $title = mb_strtolower( $title, 'UTF-8' );
        }
        $title = utf8_uri_encode( $title, ASG_SLUG_BYTES );  // core hard-codes 200 here
    }

    $title = strtolower( $title );

    if ( 'save' === $context ) {
        // EVERYTHING here is copied verbatim from core. The ONLY sanctioned difference
        // between this fork and core is the byte budget above. Drop a line and you have
        // drifted, see the note under the drift guard below.

        // Turn non-breaking spaces, en/em dashes and slashes into hyphens.
        $title = str_replace( array( '%c2%a0', '%e2%80%93', '%e2%80%94' ), '-', $title );
        $title = str_replace( array( ' ', ' ', '–', '–', '—', '—' ), '-', $title );
        $title = str_replace( '/', '-', $title );

        // Strip these characters entirely. Note %c2%ab / %c2%bb: the guillemets Arabic
        // uses as quotation marks. And %e2%80%8e / %e2%80%8f: the LRM and RTL marks.
        $title = str_replace(
            array(
                '%c2%ad',                                            // soft hyphen
                '%c2%a1', '%c2%bf',                                  // iexcl, iquest
                '%c2%ab', '%c2%bb', '%e2%80%b9', '%e2%80%ba',        // angle quotes
                '%e2%80%98', '%e2%80%99', '%e2%80%9c', '%e2%80%9d',  // curly quotes
                '%e2%80%9a', '%e2%80%9b', '%e2%80%9e', '%e2%80%9f',
                '%e2%80%a2',                                         // bullet
                '%c2%a9', '%c2%ae', '%c2%b0', '%e2%80%a6', '%e2%84%a2',
                '%c2%b4', '%cb%8a', '%cc%81', '%cd%81',              // acute accents
                '%cc%80', '%cc%84', '%cc%8c',                        // grave, macron, caron
                '%e2%80%8b', '%e2%80%8c', '%e2%80%8d',               // zero-width chars
                '%e2%80%8e', '%e2%80%8f',                            // LRM, RTL mark
                '%e2%80%aa', '%e2%80%ab', '%e2%80%ac',
                '%e2%80%ad', '%e2%80%ae',
                '%ef%bb%bf',                                         // byte order mark
                '%ef%bf%bc',                                         // object replacement
            ),
            '',
            $title
        );

        // Convert × to 'x'.
        $title = str_replace( '%c3%97', 'x', $title );
    }

    $title = preg_replace( '/&amp;amp;.+?;/', '', $title );          // strip remaining entities
    $title = str_replace( '.', '-', $title );
    $title = preg_replace( '/[^%a-z0-9 _-]/', '', $title );  // keep only slug-safe chars
    $title = preg_replace( '/\s+/', '-', $title );
    $title = preg_replace( '|-+|', '-', $title );
    return trim( $title, '-' );
}

/* LAYER 2 DRIFT GUARD: self-test the fork against core's live implementation.
 * remove_filter() only unhooks sanitize_title_with_dashes(); the function stays
 * callable, so we use core's CURRENT code as a live oracle and assert our fork
 * still agrees on SHORT inputs (where neither byte cap engages, the only thing
 * the fork changed). Any divergence == core altered the cleanup logic and our
 * copy has drifted. Runs once per core version; shouts, and can fail safe. */
add_action( 'admin_init', 'asg_guard_layer2_drift' );

function asg_guard_layer2_drift() {
    $ok      = 'ok:' . $GLOBALS['wp_version'];
    $drifted = 'drift:' . $GLOBALS['wp_version'];
    if ( in_array( get_option( 'asg_l2_status' ), array( $ok, $drifted ), true ) ) {
        return;  // already settled for this core version
    }
    if ( ! function_exists( 'sanitize_title_with_dashes' ) ) {
        return;  // core refactored it away: no oracle to diff against; the fork
                 // still runs as a standalone copy (it needs no removed function).
    }

    // Deliberately SHORT fixtures so neither length cap fires; any difference is
    // pure cleanup-logic drift, not the byte budget.
    $fixtures = array(
        'الذكاء الاصطناعي',
        'عاجل: تطورات «مهمة» اليوم',
        'Mixed عربي + English — dash',
        'foo/bar — baz',
        'UPPER   multiple   spaces',
        '%d8%a7 pre-encoded octet',
    );

    $drift = array();
    foreach ( $fixtures as $f ) {
        $core = sanitize_title_with_dashes( $f, $f, 'save' );
        $ours = asg_sanitize_title_with_dashes( $f, $f, 'save' );
        if ( $core !== $ours ) {
            $drift[] = &quot;in=[{$f}] core=[{$core}] ours=[{$ours}]&quot;;
        }
    }

    if ( $drift ) {
        $msg = &quot;[Arabic Slug Schema Guard] Layer-2 DRIFT on WP {$GLOBALS['wp_version']}: core &quot;
             . &quot;sanitize_title_with_dashes() no longer matches our fork; re-sync the copy.\n&quot;
             . implode( &quot;\n&quot;, $drift );
        error_log( $msg );
        if ( defined( 'ASG_ALERT_EMAIL' ) &amp;amp;&amp;amp; function_exists( 'wp_mail' ) ) {
            wp_mail( ASG_ALERT_EMAIL, 'WP slug Layer-2 drift: ' . wp_parse_url( home_url(), PHP_URL_HOST ), $msg );
        }
        update_option( 'asg_l2_status', $drifted, true );  // autoload: the fail-safe reads it every request
        return;  // do NOT cache &quot;ok&quot; while drifted
    }
    update_option( 'asg_l2_status', $ok, true );
}

/* LAYER 3: DE-DUPLICATION (optional)
 * _truncate_post_slug() also caps at 200, but only when a slug COLLIDES and needs
 * a &quot;-2&quot; suffix, rare for unique headlines, and it isn't filterable. If you need
 * &amp;gt;200 bytes even on collisions, take over uniqueness here. Most sites skip this.
 *
 * add_filter( 'pre_wp_unique_post_slug', 'asg_unique_post_slug', 10, 6 );
 */

/* TRIPWIRE: verify + alert after every core update
 * This only detects and alerts: it restores the COLUMN, never bytes already
 * truncated. Treat any revert as an incident: restore from backup. */
add_action( 'upgrader_process_complete', function ( $upgrader, $extra ) {
    if ( isset( $extra['type'] ) &amp;amp;&amp;amp; 'core' === $extra['type'] ) {
        delete_option( 'asg_schema_sig' );  // force a re-check on the next admin_init
    }
}, 10, 2 );

add_action( 'admin_init', function () {
    if ( get_option( 'asg_schema_sig' ) === ASG_SCHEMA_SIG ) {
        return;  // fast path: already verified since the last update
    }
    $reverted = asg_reverted_columns();
    if ( $reverted ) {
        $msg = '[Arabic Slug Schema Guard] Column(s) reverted: ' . implode( ', ', $reverted )
             . '. Long slugs were truncated: restore from backup and check 404 logs.';
        error_log( $msg );
        if ( defined( 'ASG_ALERT_EMAIL' ) ) {
            wp_mail( ASG_ALERT_EMAIL, 'WP slug schema reverted: ' . wp_parse_url( home_url(), PHP_URL_HOST ), $msg );
        }
        return;  // do NOT cache &quot;ok&quot; while broken
    }
    update_option( 'asg_schema_sig', ASG_SCHEMA_SIG, false );
} );

function asg_reverted_columns() {
    global $wpdb;
    $reverted = array();
    foreach ( array( array( $wpdb-&amp;gt;posts, 'post_name' ), array( $wpdb-&amp;gt;terms, 'slug' ) ) as list( $table, $column ) ) {
        $len = (int) $wpdb-&amp;gt;get_var( $wpdb-&amp;gt;prepare(
            &quot;SELECT CHARACTER_MAXIMUM_LENGTH FROM information_schema.COLUMNS
              WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND COLUMN_NAME = %s&quot;,
            DB_NAME, $table, $column
        ) );
        if ( $len &amp;amp;&amp;amp; $len &amp;lt; ASG_COLUMN_LEN ) {
            $reverted[] = &quot;{$table}.{$column} (now {$len})&quot;;
        }
    }
    return $reverted;
}

/* WP-CLI: `wp asg verify` for cron-based monitoring */
if ( defined( 'WP_CLI' ) &amp;amp;&amp;amp; WP_CLI ) {
    WP_CLI::add_command( 'asg verify', function () {
        foreach ( asg_reverted_columns() ?: array( 'ok' ) as $row ) {
            WP_CLI::log( 'ok' === $row ? 'Schema OK: both columns at VARCHAR(1024).' : &quot;REVERTED: {$row}&quot; );
        }
    } );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;a-postscript-the-drift-guard-caught-us&quot;&gt;A postscript: the drift guard caught us&lt;/h4&gt;
&lt;p&gt;The argument above is that a fork of a core function drifts silently, so the guard matters more than the copy. That turned out to be true in the least comfortable way.&lt;/p&gt;
&lt;p&gt;Versions 1.0.0 through 1.2.0 of the released plugin shipped a fork that was &lt;strong&gt;missing core's entire &quot;strip these characters entirely&quot; block&lt;/strong&gt;, along with the &lt;code&gt;×&lt;/code&gt; to &lt;code&gt;x&lt;/code&gt; mapping. The consequence lands exactly on the readers this post is written for: Arabic guillemets (&lt;code&gt;«&lt;/code&gt; &lt;code&gt;»&lt;/code&gt;) and the LRM/RTL direction marks survived into slugs as &lt;code&gt;%c2%ab&lt;/code&gt;, &lt;code&gt;%c2%bb&lt;/code&gt;, &lt;code&gt;%e2%80%8f&lt;/code&gt;. The character filter at the end cannot remove them, because &lt;code&gt;%c2%ab&lt;/code&gt; is built entirely from permitted characters.&lt;/p&gt;
&lt;p&gt;The drift guard printed above would have caught it on the first &lt;code&gt;admin_init&lt;/code&gt;. Its second fixture is &lt;code&gt;'عاجل: تطورات «مهمة» اليوم'&lt;/code&gt;. It never fired, because the guard is bound to &lt;code&gt;admin_init&lt;/code&gt; and nobody had opened &lt;code&gt;wp-admin&lt;/code&gt; on an install running that fork. A guard on a hook that never fires is a comment.&lt;/p&gt;
&lt;p&gt;Both are fixed in &lt;strong&gt;v1.3.0&lt;/strong&gt;: the fork is re-synced (the block above is now complete and verified against core by an offline oracle on every build), and &lt;code&gt;wp asg verify&lt;/code&gt; evaluates the drift oracle itself rather than waiting for an admin page load. If you lifted this code before July 2026, take the &lt;code&gt;str_replace&lt;/code&gt; block above.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;This is the kind of problem that never shows up in a tutorial. It lives where WordPress internals, MySQL's storage rules, Arabic character encoding and SEO all overlap, and it only reveals itself at the worst possible moment. It's also the territory we work in every day: keeping Arabic publishing fast, multilingual and unbreakable on WordPress, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/wordpress-aws&quot;&gt;right through to AWS at scale&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you run a WordPress publication in Arabic and want it to survive its own updates, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/contact&quot;&gt;let's talk&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;further-reading&quot;&gt;Further reading&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/dbdelta/&quot;&gt;&lt;code&gt;dbDelta()&lt;/code&gt;&lt;/a&gt; and the &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/hooks/dbdelta_create_queries/&quot;&gt;&lt;code&gt;dbdelta_create_queries&lt;/code&gt; hook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://core.trac.wordpress.org/ticket/36748&quot;&gt;Trac #36748&lt;/a&gt;: the TEXT/BLOB downsize protection that VARCHAR never got&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/sanitize_title_with_dashes/&quot;&gt;&lt;code&gt;sanitize_title_with_dashes()&lt;/code&gt;&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/functions/_truncate_post_slug/&quot;&gt;&lt;code&gt;_truncate_post_slug()&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.wordpress.org/reference/hooks/upgrader_process_complete/&quot;&gt;&lt;code&gt;upgrader_process_complete&lt;/code&gt; hook&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28135/the-200-byte-trap-why-wordpress-core-updates-break-arabic-urls</guid>
<pubDate>Tue, 22 Sep 2026 09:22:50 +0000</pubDate>
</item>
<item>
<title>Stop Killing Your Database: The Power of Connection Pooling</title>
<link>https://coderlegion.com/28133/stop-killing-your-database-the-power-of-connection-pooling</link>
<description>&lt;p&gt;Connection pooling is a software development technique used to maintain a cache of database connections that can be reused for future requests. Instead of opening and closing a brand-new connection to the database every time a user wants to read or write data, your application keeps a &quot;pool&quot; of active connections ready to go. This drastically reduces the overhead of establishing new database handshakes over and over again.&lt;/p&gt;
&lt;h4 id=&quot;the-pizza-delivery-analogy&quot;&gt;The Pizza Delivery Analogy&lt;/h4&gt;
&lt;p&gt;Imagine you run a busy pizza shop. Every time a delivery order comes in, instead of sending a driver who is already waiting at the shop, you post a job listing online, interview a candidate, hire them, buy them a car, have them deliver the pizza, and then fire them and sell the car immediately after. &lt;/p&gt;
&lt;p&gt;It sounds completely insane, right? Yet, that is exactly what your server does when it creates a brand-new database connection for every single HTTP request, only to destroy it a millisecond later. &lt;/p&gt;
&lt;p&gt;Connection pooling is like hiring a dedicated team of five delivery drivers who hang out at the shop. When an order is ready, one driver grabs it, delivers it, and immediately returns to the shop to wait for the next order.&lt;/p&gt;
&lt;h4 id=&quot;why-connection-pooling-matters-daily&quot;&gt;Why Connection Pooling Matters Daily&lt;/h4&gt;
&lt;p&gt;In production environments, creating a database connection is an expensive operation that involves cryptographic handshakes, network latency, and authentication checks. If your Node.js application receives 100 concurrent API requests and tries to spin up 100 separate database connections simultaneously, your MySQL database will quickly run out of memory or reject connections altogether, throwing the dreaded &quot;Too many connections&quot; error.&lt;/p&gt;
&lt;p&gt;By implementing a connection pool, you cap the maximum number of active database connections, protect your database from crashing under sudden traffic spikes, and shave off up to 90% of your API response latency because the connection is already warmed up and ready to run queries.&lt;/p&gt;
&lt;h4 id=&quot;implementation-in-node-js-amp-mysql&quot;&gt;Implementation in Node.js &amp;amp; MySQL&lt;/h4&gt;
&lt;p&gt;Here is how simple it is to set up a connection pool in an Express.js backend using the popular &lt;code&gt;mysql2&lt;/code&gt; driver:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const mysql = require('mysql2/promise');

const app = express();

// Create a connection pool instead of a single connection
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  database: 'store_db',
  waitForConnections: true,
  connectionLimit: 10, // Max 10 active connections kept in the pool
  queueLimit: 0
});

app.get('/products', async (req, res) =&amp;gt; {
  try {
    // The pool automatically rents out a connection, runs the query,
    // and returns the connection back to the pool when finished.
    const [rows] = await pool.query('SELECT * FROM products LIMIT 10');
    res.json(rows);
  } catch (error) {
    console.error('Database query error:', error);
    res.status(500).send('Database error');
  }
});

app.listen(3000, () =&amp;gt; {
  console.log('Server running on port 3000');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-2&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Optimizing your backend performance isn't always about writing faster algorithms; often, it's about managing your external resources wisely. By treating database connections as a reusable, limited public utility rather than disposable single-use items, you build highly resilient APIs capable of weathering massive traffic spikes without sweating.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/how-connection-pooling-can-prevent-your.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28133/stop-killing-your-database-the-power-of-connection-pooling</guid>
<pubDate>Tue, 22 Sep 2026 08:51:06 +0000</pubDate>
</item>
<item>
<title>Why Your Node.js App Crashes Under Traffic: The Magic of Connection Pooling</title>
<link>https://coderlegion.com/27815/why-your-node-js-app-crashes-under-traffic-the-magic-of-connection-pooling</link>
<description>&lt;p&gt;Imagine launching your shiny new web application. During development, everything runs smoothly. But the moment you launch on social media and a few hundred users hit your API at the exact same time, your server grinds to a halt and starts throwing nasty database connection timeout errors. This common disaster is almost always caused by a lack of database connection pooling.&lt;/p&gt;
&lt;h4 id=&quot;what-is-connection-pooling-&quot;&gt;What is Connection Pooling?&lt;/h4&gt;
&lt;p&gt;Database connection pooling is a performance optimization technique where a set of active database connections is kept open and shared among multiple requests. Instead of creating and destroying a new database connection every single time an application needs to run a query, the application borrows an existing connection from a pre-allocated pool, executes the query, and immediately returns the connection back to the pool. This prevents the costly overhead of starting new communication channels from scratch.&lt;/p&gt;
&lt;h4 id=&quot;the-rental-car-analogy&quot;&gt;The Rental Car Analogy&lt;/h4&gt;
&lt;p&gt;To understand this concept, think of a rental car agency at an airport. Imagine if every time a traveler landed at the airport, the rental agency had to buy a brand-new car from the factory, register it with the DMV, customize it for the driver, and then completely crush and destroy the car at the end of the traveler's weekend trip. That would be an absurdly slow and expensive process.&lt;/p&gt;
&lt;p&gt;Instead, the agency maintains a &quot;pool&quot; of 50 pre-registered, fully fueled, ready-to-drive cars parked in a lot. When a traveler arrives, they borrow a car from the lot, use it for their trip, and then return it to the lot. The car is washed and immediately made available for the next traveler. This is connection pooling: instead of building and destroying connections on the fly, you manage a recycled fleet of them.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-daily-in-the-tech-industry&quot;&gt;Why It Matters Daily in the Tech Industry&lt;/h4&gt;
&lt;p&gt;Opening a database connection is an incredibly resource-intensive operation. To establish a new connection, your backend application and database server must complete a network handshake, negotiate security protocols (like TLS/SSL), authenticate user credentials, and allocate dedicated RAM and CPU threads to handle that specific session.&lt;/p&gt;
&lt;p&gt;If you build an application where every user request opens a brand-new connection, a sudden spike in web traffic will force your Node.js server to attempt to open hundreds of database connections simultaneously. Your database server (like MySQL) has a built-in safety limit of maximum allowable concurrent connections. Once that limit is hit, the database will aggressively reject all incoming connection requests, throwing &quot;Too many connections&quot; errors, causing API endpoints to return 500 errors, and crashing your platform. Connection pooling solves this by capping the total number of open database connections and keeping extra queries safely queued up in memory until a connection becomes available.&lt;/p&gt;
&lt;h4 id=&quot;connection-pooling-in-action-node-js-amp-mysql-&quot;&gt;Connection Pooling in Action (Node.js &amp;amp; MySQL)&lt;/h4&gt;
&lt;p&gt;Here is how you implement connection pooling using Express and the standard &lt;code&gt;mysql2/promise&lt;/code&gt; library in a Node.js environment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const mysql = require('mysql2/promise');
const app = express();

// 1. Create a global connection pool with configuration limits
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'secure_password',
  database: 'ecommerce_db',
  waitForConnections: true,    // Queue queries if no connections are free
  connectionLimit: 10,         // Maximum number of open connections
  queueLimit: 0                // No limit on the number of queued queries
});

app.get('/api/products', async (req, res) =&amp;gt; {
  try {
    // 2. pool.query automatically borrows an idle connection, 
    // executes the query, and returns it to the pool.
    const [products] = await pool.query('SELECT id, name, price FROM products LIMIT 20');
    res.json(products);
  } catch (error) {
    console.error('Database failure:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(3000, () =&amp;gt; {
  console.log('Server is running on port 3000');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-3&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Software scalability is not just about writing clean logic or optimizing loops; it is about protecting and sharing finite hardware resources under heavy load. By implementing connection pooling, you protect your database from crashing, reduce API response latency, and build backend systems capable of handling traffic spikes gracefully without costing a fortune in extra server infrastructure.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/stop-rebuilding-bridge-how-connection.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27815/why-your-node-js-app-crashes-under-traffic-the-magic-of-connection-pooling</guid>
<pubDate>Sun, 20 Sep 2026 08:56:50 +0000</pubDate>
</item>
<item>
<title>Supercharge Your Node.js App: Why You Need Database Connection Pooling</title>
<link>https://coderlegion.com/27716/supercharge-your-node-js-app-why-you-need-database-connection-pooling</link>
<description>&lt;h2 id=&quot;what-is-connection-pooling--1&quot;&gt;What is Connection Pooling?&lt;/h2&gt;
&lt;p&gt;Database connection pooling is a technique where a cache of database connections is kept open and shared among multiple queries, rather than opening and closing a brand-new connection for every single query. When your application needs to talk to the database, it borrows an existing connection from the pool, runs the query, and immediately returns it. This avoids the heavy performance penalty of creating and destroying connections constantly.&lt;/p&gt;
&lt;h3 id=&quot;the-pizza-scooter-analogy&quot;&gt;The Pizza Scooter Analogy&lt;/h3&gt;
&lt;p&gt;Imagine a busy local pizza shop. Every time a customer orders a pizza, if the shop had to buy a brand-new delivery scooter from the dealership, register it, insure it, deliver the pizza, and then scrap or sell the scooter immediately afterward, the business would go bankrupt in an hour.&lt;/p&gt;
&lt;p&gt;Instead, the shop maintains a stable fleet of five delivery scooters parked in the back. When a delivery is ready, a driver grabs an available scooter from the fleet, completes the delivery, and parks it back in the lot for the next driver to use. Connection pooling is that stable fleet of scooters.&lt;/p&gt;
&lt;h3 id=&quot;why-connection-pooling-matters-daily-1&quot;&gt;Why Connection Pooling Matters Daily&lt;/h3&gt;
&lt;p&gt;In backend development, establishing a connection to a database like MySQL is an incredibly heavy operation. It requires several steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A TCP network handshake.&lt;/li&gt;
&lt;li&gt;SSL/TLS security negotiation.&lt;/li&gt;
&lt;li&gt;User authentication and privilege verification.&lt;/li&gt;
&lt;li&gt;Memory allocation on the database server.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If your Express.js server handles 1,000 requests per second and you open a new connection for each one, your database server's CPU will spike to 100% just managing handshakes. Eventually, the database will run out of resources and throw a dreaded &quot;Too many connections&quot; error, crashing your app. By using a connection pool, you bypass this startup overhead. Your queries run almost instantly because the connection is already active and authenticated.&lt;/p&gt;
&lt;h3 id=&quot;simple-code-example&quot;&gt;Simple Code Example&lt;/h3&gt;
&lt;p&gt;Let's see the difference in a MERN stack application using Node.js, Express, and the &lt;code&gt;mysql2&lt;/code&gt; package.&lt;/p&gt;
&lt;h4 id=&quot;the-bad-way-new-connection-per-request-&quot;&gt;The Bad Way (New Connection per Request)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const mysql = require('mysql2/promise');
const app = express();

const dbConfig = { host: 'localhost', user: 'root', database: 'store' };

app.get('/products', async (req, res) =&amp;gt; {
  try {
    // Slow and resource-intensive: creating a connection on every request
    const connection = await mysql.createConnection(dbConfig);
    const [rows] = await connection.execute('SELECT * FROM products LIMIT 10');

    // Closing it manually
    await connection.end();
    res.json(rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-good-way-using-a-connection-pool-&quot;&gt;The Good Way (Using a Connection Pool)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const express = require('express');
const mysql = require('mysql2/promise');
const app = express();

const dbConfig = { host: 'localhost', user: 'root', database: 'store' };

// Create a pool of up to 10 reusable connections
const pool = mysql.createPool({
  ...dbConfig,
  connectionLimit: 10,
  waitForConnections: true,
  queueLimit: 0
});

app.get('/products', async (req, res) =&amp;gt; {
  try {
    // Automatically borrows an active connection and returns it when done
    const [rows] = await pool.execute('SELECT * FROM products LIMIT 10');
    res.json(rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-takeaway-1&quot;&gt;Key Takeaway&lt;/h3&gt;
&lt;p&gt;Database connection pooling is not just a performance optimization; it is a fundamental survival mechanism for production databases. It turns chaotic database traffic into a predictable, queued stream, ensuring your server remains responsive and your database stays healthy under heavy load.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/how-to-prevent-database-crashes-under.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27716/supercharge-your-node-js-app-why-you-need-database-connection-pooling</guid>
<pubDate>Sat, 19 Sep 2026 08:17:43 +0000</pubDate>
</item>
<item>
<title>The Silent Database Killer: Understanding and Fixing the N+1 Query Problem</title>
<link>https://coderlegion.com/27488/the-silent-database-killer-understanding-and-fixing-the-n-1-query-problem</link>
<description>&lt;p&gt;The N+1 query problem is a performance bottleneck that occurs when an application makes database queries inside a loop. Instead of fetching all the required data in a single, efficient query, the system executes one initial query to fetch a list of parent records, followed by &quot;N&quot; separate queries to fetch related child records for each item in that list. This creates an exponential number of database requests as your data grows.&lt;/p&gt;
&lt;h4 id=&quot;the-grocery-store-analogy&quot;&gt;The Grocery Store Analogy&lt;/h4&gt;
&lt;p&gt;Imagine you are baking a cake and need five different ingredients from your local grocery store. Instead of writing all five items on a single list and making one trip, you drive to the store, buy flour, and drive home. Then, you realize you need sugar, so you drive back to the store, buy sugar, and drive home. You repeat this entire round-trip for every single ingredient. This is highly inefficient, wasting time, fuel, and energy. In software, each trip to the database is like one of those painful, redundant car rides.&lt;/p&gt;
&lt;h4 id=&quot;why-it-matters-in-the-tech-industry-1&quot;&gt;Why It Matters in the Tech Industry&lt;/h4&gt;
&lt;p&gt;On a daily basis, software engineers actively fight N+1 queries to prevent application servers and databases from crashing under heavy traffic. Each extra query introduces network latency and forces the database to parse, plan, and execute a new command. This wastes valuable connection pools, spikes CPU usage, and causes page load times to balloon. Fixing this issue is often the difference between a snappy, responsive web app and one that times out during peak business hours.&lt;/p&gt;
&lt;h4 id=&quot;spotting-and-fixing-the-problem-in-node-js&quot;&gt;Spotting and Fixing the Problem in Node.js&lt;/h4&gt;
&lt;p&gt;Let's look at how this happens in a Node.js and Express application querying a MySQL database, and how to fix it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// THE PROBLEM: The N+1 Query Way
app.get('/users-with-posts-bad', async (req, res) =&amp;gt; {
  // 1. First query to get all users (The &quot;1&quot; in N+1)
  const [users] = await db.query('SELECT id, username FROM users');

  const usersWithPosts = [];

  // 2. Loop through each user and query their posts (The &quot;N&quot; queries)
  for (const user of users) {
    const [posts] = await db.query('SELECT id, title FROM posts WHERE user_id = ?', [user.id]);
    usersWithPosts.push({
      ...user,
      posts
    });
  }

  res.json(usersWithPosts);
});

// THE SOLUTION: The Efficient Way (Using a SQL JOIN)
app.get('/users-with-posts-good', async (req, res) =&amp;gt; {
  // A single query fetches all users and their posts at once
  const query = `
    SELECT u.id AS userId, u.username, p.id AS postId, p.title
    FROM users u
    LEFT JOIN posts p ON u.id = p.user_id
  `;
  const [rows] = await db.query(query);

  // Format the flat SQL result into structured JSON
  const usersMap = {};
  for (const row of rows) {
    if (!usersMap[row.userId]) {
      usersMap[row.userId] = {
        id: row.userId,
        username: row.username,
        posts: []
      };
    }
    if (row.postId) {
      usersMap[row.userId].posts.push({
        id: row.postId,
        title: row.title
      });
    }
  }

  res.json(Object.values(usersMap));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-4&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Database interactions are highly expensive operations. Always aim to retrieve data in bulk sets rather than looping over individual records. By shifting your mindset from iterative programming to relational database operations, you can transform a slow, unstable backend into a highly optimized, production-ready system that scales effortlessly.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/is-your-code-making-too-many-database.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27488/the-silent-database-killer-understanding-and-fixing-the-n-1-query-problem</guid>
<pubDate>Thu, 17 Sep 2026 08:54:44 +0000</pubDate>
</item>
<item>
<title>Stop Crashing Your Database: A Developer's Guide to Connection Pooling</title>
<link>https://coderlegion.com/27378/stop-crashing-your-database-a-developers-guide-to-connection-pooling</link>
<description>&lt;p&gt;Database connections are the lifeblood of any web application, but they are also surprisingly expensive to create and destroy. When your application struggles under sudden traffic spikes, the database is often the first thing to break. This is where connection pooling comes in.&lt;/p&gt;
&lt;p&gt;Connection pooling is a software development technique that creates and maintains a cache of active database connections, keeping them ready for immediate reuse. Instead of initiating and closing a brand-new connection every time a user requests data, your application borrows an already-established connection from this pre-allocated &quot;pool.&quot; Once the query is complete, the connection is returned to the pool for the next request to use, rather than being discarded.&lt;/p&gt;
&lt;h4 id=&quot;the-taxi-stand-analogy&quot;&gt;The Taxi Stand Analogy&lt;/h4&gt;
&lt;p&gt;To understand this clearly, think of a busy taxi stand outside a major hotel. If every guest who wanted to travel into town had to call a car dealership, buy a brand-new car, register it with the city, drive to their destination, and then crush the vehicle at a scrap yard when they arrived, transportation would be impossibly slow, expensive, and wasteful. &lt;/p&gt;
&lt;p&gt;Instead, the hotel relies on a taxi stand. A small fleet of vehicles sits idling in the driveway. When a guest steps outside, they instantly hop into an available taxi, take their trip, and the driver returns to the stand to wait for the next passenger. The taxi is never destroyed; it is constantly reused. In this analogy, the guests are your incoming API requests, the taxis are your database connections, and the stand is your connection pool.&lt;/p&gt;
&lt;h4 id=&quot;why-connection-pooling-matters-daily-2&quot;&gt;Why Connection Pooling Matters Daily&lt;/h4&gt;
&lt;p&gt;In real-world software engineering, establishing a database connection is not a simple task. It requires a multi-step process: initiating a TCP handshake over the network, negotiating security protocols (TLS/SSL), authenticating the database user's credentials, allocating memory on the database server, and setting up session variables. Doing this on every single HTTP request adds massive latency to your API responses.&lt;/p&gt;
&lt;p&gt;Furthermore, databases have strict physical limits. Every active connection consumes a slice of the database server's RAM and CPU. If your Express server experiences a sudden burst of 500 concurrent users and tries to open 500 individual connections simultaneously, your MySQL database will quickly run out of memory. This leads to the dreaded &quot;Too many connections&quot; error, bringing your entire application to a grinding halt. Connection pooling caps the maximum number of connections, forcing extra requests to wait in a fast queue rather than crashing your database server.&lt;/p&gt;
&lt;h4 id=&quot;putting-it-into-code&quot;&gt;Putting It into Code&lt;/h4&gt;
&lt;p&gt;Here is how you implement connection pooling in a Node.js application using the popular &lt;code&gt;mysql2&lt;/code&gt; driver. Instead of using &lt;code&gt;createConnection&lt;/code&gt;, we use &lt;code&gt;createPool&lt;/code&gt; to manage our resources efficiently.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const mysql = require('mysql2/promise');

// Configure and create the connection pool
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'your_secure_password',
  database: 'production_db',
  waitForConnections: true,    // Queue requests when pool is full
  connectionLimit: 10,         // Max number of simultaneous active connections
  queueLimit: 0                // No limit on waiting requests (0 means infinite)
});

// Helper function to fetch a user profile
async function getUserProfile(userId) {
  try {
    // The pool automatically checks out a connection, runs the query, and returns it
    const [rows] = await pool.execute(
      'SELECT id, username, email FROM users WHERE id = ?',
      [userId]
    );
    return rows[0];
  } catch (error) {
    console.error('Database query error:', error);
    throw error;
  }
}

// Example execution
getUserProfile(42).then(user =&amp;gt; console.log('User retrieved:', user));
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-takeaway-5&quot;&gt;The Takeaway&lt;/h4&gt;
&lt;p&gt;Connection pooling is a quiet champion of system architecture, transforming database communication from a slow bottleneck into a reliable, high-performance system utility. By setting up a robust pool inside your application, you prevent database crashes, lower response times, and ensure that your backend can handle high-concurrency traffic without breaking a sweat.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Originally published on my blog. You can &lt;a rel=&quot;nofollow&quot; href=&quot;https://sauravtbpandey.blogspot.com/2026/09/supercharge-your-api-performance-with.html&quot;&gt;read the alternative breakdown here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27378/stop-crashing-your-database-a-developers-guide-to-connection-pooling</guid>
<pubDate>Wed, 16 Sep 2026 08:51:01 +0000</pubDate>
</item>
<item>
<title>A Complete Guide to Install XAMPP, Configure Apache &amp; MySQL Services, and Deploy WordPress Using Pow</title>
<link>https://coderlegion.com/26591/complete-guide-install-xampp-configure-apache-mysql-services-and-deploy-wordpress-using</link>
<description>&lt;p&gt;Setting up a reliable local web development environment is one of the first steps for anyone working with WordPress, PHP, MySQL, or Apache. While XAMPP makes this process easier by bundling the required components into a single package, configuring the environment properly, especially through the command line, can still be challenging.&lt;/p&gt;
&lt;p&gt;Many online tutorials focus only on the graphical interface and often skip essential configuration steps, troubleshooting techniques, or service management. As a result, beginners frequently encounter issues with Apache, MySQL, PHP compatibility, phpMyAdmin, or WordPress installation.&lt;/p&gt;
&lt;p&gt;To address these challenges, I created a comprehensive guide that demonstrates how to install, configure, and manage an entire XAMPP and WordPress development environment using Windows PowerShell.&lt;/p&gt;
&lt;p&gt;Whether you're a student learning web technologies, an educator preparing laboratory sessions, or a developer looking for a repeatable deployment process, this guide provides a structured approach from start to finish.&lt;/p&gt;
&lt;h3 id=&quot;why-this-guide-&quot;&gt;Why This Guide?&lt;/h3&gt;
&lt;p&gt;A properly configured local development environment saves hours of troubleshooting and creates a consistent workflow for development, testing, and learning.&lt;/p&gt;
&lt;p&gt;Instead of relying solely on mouse clicks, this guide introduces a command-driven approach that helps users understand what happens behind the scenes while also making the installation process easier to reproduce across multiple machines.&lt;/p&gt;
&lt;p&gt;The guide follows a complete workflow covering installation, service management, WordPress deployment, version upgrades, and troubleshooting.&lt;/p&gt;
&lt;h3 id=&quot;what-you-ll-learn&quot;&gt;What You'll Learn&lt;/h3&gt;
&lt;p&gt;The guide covers every major step required to build a local WordPress development environment.&lt;/p&gt;
&lt;h4 id=&quot;1-xampp-installation&quot;&gt;1. XAMPP Installation&lt;/h4&gt;
&lt;p&gt;Learn how to install XAMPP using PowerShell commands, verify the installation, and safely uninstall and reinstall it when necessary.&lt;/p&gt;
&lt;p&gt;Topics include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downloading XAMPP&lt;/li&gt;
&lt;li&gt;Silent installation&lt;/li&gt;
&lt;li&gt;Installation verification&lt;/li&gt;
&lt;li&gt;Troubleshooting incomplete installations&lt;/li&gt;
&lt;li&gt;Safe uninstallation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-apache-service-configuration&quot;&gt;2. Apache Service Configuration&lt;/h4&gt;
&lt;p&gt;Apache is configured as a Windows Service, allowing it to be managed directly through PowerShell.&lt;/p&gt;
&lt;p&gt;You'll learn how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install Apache as a service&lt;/li&gt;
&lt;li&gt;Verify service status&lt;/li&gt;
&lt;li&gt;Start and stop Apache&lt;/li&gt;
&lt;li&gt;Manage services efficiently&lt;/li&gt;
&lt;li&gt;Troubleshoot service-related issues&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-mysql-service-installation&quot;&gt;3. MySQL Service Installation&lt;/h4&gt;
&lt;p&gt;Database configuration is an essential part of every WordPress deployment.&lt;/p&gt;
&lt;p&gt;This section explains how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install MySQL as a Windows Service&lt;/li&gt;
&lt;li&gt;Verify successful installation&lt;/li&gt;
&lt;li&gt;Start and stop the service&lt;/li&gt;
&lt;li&gt;Prepare the database environment for WordPress&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-wordpress-deployment-on-xampp&quot;&gt;4. WordPress Deployment on XAMPP&lt;/h4&gt;
&lt;p&gt;One of the largest sections of the guide walks through deploying WordPress on a local server.&lt;/p&gt;
&lt;p&gt;Topics include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating WordPress databases&lt;/li&gt;
&lt;li&gt;Configuring users and permissions&lt;/li&gt;
&lt;li&gt;Downloading WordPress&lt;/li&gt;
&lt;li&gt;Extracting and organizing files&lt;/li&gt;
&lt;li&gt;Running the installation&lt;/li&gt;
&lt;li&gt;Configuring the initial website&lt;/li&gt;
&lt;li&gt;Accessing the WordPress dashboard&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;5-upgrading-php&quot;&gt;5. Upgrading PHP&lt;/h4&gt;
&lt;p&gt;Modern WordPress versions require newer PHP releases.&lt;/p&gt;
&lt;p&gt;The guide explains how to safely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace outdated PHP versions&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;php.ini&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Enable required PHP extensions&lt;/li&gt;
&lt;li&gt;Update Apache configuration&lt;/li&gt;
&lt;li&gt;Verify the installation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;6-upgrading-phpmyadmin&quot;&gt;6. Upgrading phpMyAdmin&lt;/h4&gt;
&lt;p&gt;Many bundled XAMPP installations include older versions of phpMyAdmin.&lt;/p&gt;
&lt;p&gt;The guide demonstrates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installing the latest release&lt;/li&gt;
&lt;li&gt;Preserving existing configurations&lt;/li&gt;
&lt;li&gt;Migrating settings safely&lt;/li&gt;
&lt;li&gt;Verifying successful upgrades&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;7-daily-service-management&quot;&gt;7. Daily Service Management&lt;/h4&gt;
&lt;p&gt;Instead of opening the XAMPP Control Panel every time, the guide shows how to manage services directly from PowerShell.&lt;/p&gt;
&lt;p&gt;This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Starting Apache&lt;/li&gt;
&lt;li&gt;Starting MySQL&lt;/li&gt;
&lt;li&gt;Opening WordPress&lt;/li&gt;
&lt;li&gt;Accessing phpMyAdmin&lt;/li&gt;
&lt;li&gt;Stopping services after development&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-use-powershell-instead-of-only-the-xampp-control-panel-&quot;&gt;Why Use PowerShell Instead of Only the XAMPP Control Panel?&lt;/h3&gt;
&lt;p&gt;Learning to manage services through PowerShell provides several long-term advantages.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Faster setup&lt;/li&gt;
&lt;li&gt;Easier automation&lt;/li&gt;
&lt;li&gt;Better troubleshooting&lt;/li&gt;
&lt;li&gt;Repeatable deployments&lt;/li&gt;
&lt;li&gt;Improved understanding of Windows service management&lt;/li&gt;
&lt;li&gt;Practical DevOps and system administration skills&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These skills become increasingly valuable when working with larger development environments or automating deployments.&lt;/p&gt;
&lt;h3 id=&quot;who-should-read-this-guide-&quot;&gt;Who Should Read This Guide?&lt;/h3&gt;
&lt;p&gt;This guide is suitable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Web Developers&lt;/li&gt;
&lt;li&gt;WordPress Developers&lt;/li&gt;
&lt;li&gt;PHP Developers&lt;/li&gt;
&lt;li&gt;Computer Science Students&lt;/li&gt;
&lt;li&gt;Information Technology Students&lt;/li&gt;
&lt;li&gt;Software Engineering Undergraduates&lt;/li&gt;
&lt;li&gt;Lecturers and Instructors&lt;/li&gt;
&lt;li&gt;System Administrators&lt;/li&gt;
&lt;li&gt;Beginners learning local web development&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is especially useful in academic environments where students need to deploy multiple local development environments using consistent procedures.&lt;/p&gt;
&lt;h3 id=&quot;github-repository&quot;&gt;GitHub Repository&lt;/h3&gt;
&lt;p&gt;The complete guide and supporting resources are available on GitHub.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/NithilaLD/XAMPP-Wordpress-Powershell-Guide&quot;&gt;XAMPP-WordPress-PowerShell-Guide&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you find this resource useful, consider giving the repository a star. It helps increase visibility and encourages further improvements.&lt;/p&gt;
&lt;h3 id=&quot;also-published-on&quot;&gt;Also Published On&lt;/h3&gt;
&lt;p&gt;This article is also available on the following platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/pulse/complete-guide-installing-xampp-configuring-apache-liyanarachchi-6fqzc/&quot;&gt;Read on LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dulannithilaliyanarachchi.medium.com/a-complete-guide-to-install-xampp-configure-apache-mysql-services-and-deploy-wordpress-b6076092f948&quot;&gt;Read on Medium&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://builder.aws.com/content/3ImJ4y1l6v3vxDM6zoSjnTskHRi/a-complete-guide-to-install-xampp-configure-apache-and-mysql-services-and-deploy-wordpress-using-powershell-commands&quot;&gt;Read on AWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/dulannithilaliyanarachchi/a-complete-guide-to-install-xampp-configure-apache-mysql-services-and-deploy-wordpress-using-3f0h&quot;&gt;Read on DEV Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;acknowledgements&quot;&gt;Acknowledgements&lt;/h3&gt;
&lt;p&gt;I would like to express my sincere gratitude to &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/madusanka-mithrananda/&quot;&gt;&lt;strong&gt;Mr. Madusanka Mithrananda&lt;/strong&gt;&lt;/a&gt; for his invaluable guidance, encouragement, and continuous support throughout the development of this guide and the article. His expertise, constructive feedback, and mentorship were instrumental in shaping this work and deepening my understanding of XAMPP, PowerShell, and WordPress deployment.&lt;/p&gt;
&lt;p&gt;I truly appreciate his dedication to teaching, willingness to share knowledge, and the encouragement provided throughout this journey. His guidance has been an invaluable part of the successful completion of this guide.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;Documentation is one of the most valuable yet overlooked contributions to the developer community. A clear, repeatable guide can save countless hours for students, developers, and educators trying to build reliable development environments.&lt;/p&gt;
&lt;p&gt;This project was created with the goal of simplifying the setup process while encouraging developers to understand the underlying configuration instead of simply following GUI-based installation steps.&lt;/p&gt;
&lt;p&gt;I hope it serves as a practical reference for anyone working with XAMPP, Apache, MySQL, PHP, PowerShell, and WordPress.&lt;/p&gt;
&lt;p&gt;If you have suggestions, discover improvements, or would like to contribute, feel free to open an issue or submit a pull request on GitHub.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26591/complete-guide-install-xampp-configure-apache-mysql-services-and-deploy-wordpress-using</guid>
<pubDate>Wed, 09 Sep 2026 10:21:11 +0000</pubDate>
</item>
<item>
<title>Building a Real-Time Payment Gateway Integration with PHP, PayHere, ngrok, and MySQL</title>
<link>https://coderlegion.com/26585/building-a-real-time-payment-gateway-integration-with-php-payhere-ngrok-and-mysql</link>
<description>&lt;p&gt;Recently, I built a complete &lt;strong&gt;real-time payment gateway integration&lt;/strong&gt; using &lt;strong&gt;PHP, PayHere Sandbox, ngrok, and MySQL&lt;/strong&gt; to simulate and test online payments in a local development environment.&lt;/p&gt;
&lt;p&gt;This project covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Secure PayHere checkout integration&lt;/li&gt;
&lt;li&gt;Real-time payment callback handling through ngrok&lt;/li&gt;
&lt;li&gt;Transaction logging and MySQL database storage&lt;/li&gt;
&lt;li&gt;Email notifications using PHPMailer&lt;/li&gt;
&lt;li&gt;PDF receipt generation using mPDF&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One of the most interesting parts of the project was using &lt;strong&gt;ngrok to expose localhost securely&lt;/strong&gt;, allowing PayHere to communicate with local callback URLs such as &lt;code&gt;notify_url&lt;/code&gt;, &lt;code&gt;return_url&lt;/code&gt;, and &lt;code&gt;cancel_url&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The project structure was designed to keep the workflow clean and modular, with separate files for checkout processing, notifications, verification, configuration, and database handling.&lt;/p&gt;
&lt;p&gt;This implementation was a great hands-on experience in understanding how real-world payment systems work behind the scenes, from &lt;strong&gt;hash generation and callback verification&lt;/strong&gt; to &lt;strong&gt;preventing duplicate transactions and securely handling payment confirmations&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;repository&quot;&gt;Repository&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/NithilaLD/Building-a-Real-Time-Payment-Gateway-Integration-with-PHP-PayHere-ngrok-and-MySQL/&quot;&gt;https://github.com/NithilaLD/Building-a-Real-Time-Payment-Gateway-Integration-with-PHP-PayHere-ngrok-and-MySQL/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The repository includes the complete project resources, including the full implementation guide in PDF format, source code files for the payment gateway integration, and database setup scripts required to configure and run the application locally.&lt;/p&gt;
&lt;h3 id=&quot;tech-stack&quot;&gt;Tech Stack&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;PHP | MySQL | PayHere Sandbox | ngrok | Composer | PHPMailer | mPDF&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;also-published-on-1&quot;&gt;Also Published On&lt;/h3&gt;
&lt;p&gt;This article is also available on the following platforms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/pulse/building-real-time-payment-gateway-integration-php-liyanarachchi-yujgc/&quot;&gt;&lt;strong&gt;Read on LinkedIn&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dulannithilaliyanarachchi.medium.com/building-a-real-time-payment-gateway-integration-with-php-payhere-ngrok-and-mysql-533c5e4a5720&quot;&gt;&lt;strong&gt;Read on Medium&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://builder.aws.com/content/3ImAUjqYN0gmihHAxXEIlVJoHFn/building-a-real-time-payment-gateway-integration-with-php-payhere-ngrok-and-mysql&quot;&gt;&lt;strong&gt;Read on AWS&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DEV Community:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/dulannithilaliyanarachchi/building-a-real-time-payment-gateway-integration-with-php-payhere-ngrok-and-mysql-dnf&quot;&gt;&lt;strong&gt;Read on DEV Community&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26585/building-a-real-time-payment-gateway-integration-with-php-payhere-ngrok-and-mysql</guid>
<pubDate>Wed, 09 Sep 2026 09:49:02 +0000</pubDate>
</item>
<item>
<title>Building database synchronization the boring way</title>
<link>https://coderlegion.com/26522/building-database-synchronization-the-boring-way</link>
<description>&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://iamroot.tech/&quot;&gt;iamroot.tech&lt;/a&gt; uses a custom-built, asynchronous MySQL database synchronization system handling ~10 million daily changes across several distributed nodes without consensus algorithms or distributed coordinators.&lt;br&gt;
Key Architecture &amp;amp; Trade-offs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Primary Keys: UUIDs to allow conflict-free row generation on any node. &lt;/li&gt;
&lt;li&gt;Change Tracking: Database triggers log INSERT, UPDATE, and DELETE operations into a simplified change log. &lt;/li&gt;
&lt;li&gt;Replication Mechanism: Asynchronous background workers process log entries.&lt;/li&gt;
&lt;li&gt;Conflict Avoidance: Application-level single-writer authority per dataset &lt;/li&gt;
&lt;li&gt;Fault Tolerance: Nodes catch up automatically after offline periods by replaying the simple change log backlog.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-setup---servers-will-disappear&quot;&gt;The setup - servers will disappear&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://iamroot.tech/&quot;&gt;iamroot.tech&lt;/a&gt; runs across numerous servers in different geographical locations. They are deliberately inexpensive machines, and an important assumption behind the entire architecture is that individual servers will - at some point in time - disappear, underperform or otherwise misbehave.&lt;/p&gt;
&lt;p&gt;The goal has never been to make every server perfectly reliable. The goal is to make each server failure completely uninteresting.&lt;/p&gt;
&lt;p&gt;That creates an interesting problem for the databases. A server needs access to shared data, but I don't want a write on server A to depend on server B being available. In fact, I don't really want server A to care about server B at all while serving a request.&lt;/p&gt;
&lt;p&gt;So I ended up building my own synchronization system. It currently keeps roughly 50–60 tables synchronized across numerous servers and handles around 10 million changes on a normal day.&lt;/p&gt;
&lt;p&gt;It isn't particularly clever. In fact, it's about as simple as it gets.&lt;/p&gt;
&lt;p&gt;And that is rather the point.&lt;/p&gt;
&lt;h3 id=&quot;a-failing-server-should-create-a-backlog-not-an-outage&quot;&gt;A failing server should create a backlog, not an outage&lt;/h3&gt;
&lt;p&gt;The most important principle is that database writes are local operations. If the application running on server A needs to update something, it writes to the local MySQL database and continues.&lt;/p&gt;
&lt;p&gt;Synchronization happens afterwards.&lt;/p&gt;
&lt;p&gt;If server B happens to be slow, offline or has been gone for three days, that doesn't change what happens on A. It simply means B has more catching up to do when it eventually returns.&lt;/p&gt;
&lt;p&gt;In practice, this gives iamroot.tech a distributed data architecture without trying to turn MySQL itself into a distributed database. Each server owns its local database and can operate independently, while relevant data is eventually synchronized between them.&lt;/p&gt;
&lt;p&gt;Temporary differences between nodes are therefore expected. Eventual consistency isn't something the architecture reluctantly tolerates; it is part of the design.&lt;/p&gt;
&lt;p&gt;That trade-off works because the application itself understands how the data is used.&lt;/p&gt;
&lt;h3 id=&quot;the-implementation-is-almost-embarrassingly-simple&quot;&gt;The implementation is almost embarrassingly simple&lt;/h3&gt;
&lt;p&gt;Tables that need to be shared use UUIDs as their primary keys. That allows records to be created independently on different servers without coordinating ID ranges or worrying about primary key collisions later.&lt;/p&gt;
&lt;p&gt;Each server then maintains its own local change log. Synchronized tables have triggers for INSERT, UPDATE and DELETE, and whenever something changes, a small entry containing the table, record ID, operation and timestamp is written to the log.&lt;/p&gt;
&lt;p&gt;A background service processes those entries and sends the changes to whichever nodes should receive them. There is no central replication queue and no synchronization master.&lt;/p&gt;
&lt;p&gt; For inserts and updates, I use MySQL's INSERT ... ON DUPLICATE KEY UPDATE. If a server somehow missed the original INSERT but later receives an UPDATE, the record simply gets created anyway.&lt;/p&gt;
&lt;p&gt;Replicated writes bypass the change-log triggers for that database connection, while normal application writes still trigger them. Otherwise A would update B, B would send the same change back to A, and my wonderfully simple synchronization system would become a wonderfully effective infinite loop.&lt;/p&gt;
&lt;p&gt;DELETEs work through the same mechanism. The DELETE entry stays in the change log until the relevant nodes have processed it, effectively making it a temporary replication tombstone. A server can therefore disappear for days and still learn which records were deleted while it was gone.&lt;/p&gt;
&lt;p&gt;That's basically it.&lt;/p&gt;
&lt;h3 id=&quot;avoiding-problems-instead-of-solving-them&quot;&gt;Avoiding problems instead of solving them&lt;/h3&gt;
&lt;p&gt;This is not conflict-safe multi-master replication.&lt;/p&gt;
&lt;p&gt;If two servers independently modify the same record at exactly the wrong moment, there is no clever distributed algorithm deciding which version represents universal truth.&lt;/p&gt;
&lt;p&gt;Instead, I largely avoid the problem at the application level.&lt;/p&gt;
&lt;p&gt;Individual records normally have one logical writer - one server or subsystem that is effectively king for that data. The code working with those records knows about that architecture.&lt;/p&gt;
&lt;p&gt;That is an important distinction. I could add version vectors, distributed locking, automatic conflict resolution and considerably more machinery. Those are perfectly valid solutions to problems other systems have.&lt;/p&gt;
&lt;p&gt;But if I can structure the application so the problem doesn't normally exist, I would rather do that.&lt;/p&gt;
&lt;p&gt;The same philosophy applies elsewhere. Synchronization is at-least-once rather than exactly-once. Duplicate work happens, but the operations are idempotent, so that is mostly harmless. The change log isn't an event store either. If a record changes ten times, I don't need to reconstruct all ten historical versions. I care about getting the current state where it needs to go.&lt;/p&gt;
&lt;h3 id=&quot;recovery-is-deliberately-boring&quot;&gt;Recovery is deliberately boring&lt;/h3&gt;
&lt;p&gt;For every table and destination node, the synchronizer remembers how far through the local change log that node has progressed.&lt;/p&gt;
&lt;p&gt;Important data is typically synchronized every 10 – 20 seconds, while less important tables may only be processed every ten minutes. If a destination disappears, its position simply stops advancing while everybody else continues.&lt;/p&gt;
&lt;p&gt;When it returns, there is normally nothing for me to do. The synchronizer knows where it stopped and continues from there. Once all relevant nodes have moved beyond old change-log entries, those entries can be removed.&lt;/p&gt;
&lt;p&gt;This isn't just how I hope it works. Servers have disappeared and returned plenty of times in production.&lt;/p&gt;
&lt;p&gt;The recovery procedure is dead simple: sit back and wait.&lt;/p&gt;
&lt;p&gt;I like recovery procedures like that.&lt;/p&gt;
&lt;h3 id=&quot;why-not-just-use-mysql-replication-&quot;&gt;Why not just use MySQL replication?&lt;/h3&gt;
&lt;p&gt;MySQL already provides very capable replication, and this isn't an attempt to build a better replacement.&lt;/p&gt;
&lt;p&gt;I wanted slightly different properties.&lt;/p&gt;
&lt;p&gt;Not every table needs to exist everywhere. Different data can have different synchronization intervals and destinations. More importantly, replication isn't part of the availability model for an individual application server. A slow destination creates work for the synchronizer, not latency for the application serving a user.&lt;/p&gt;
&lt;p&gt;There are obvious trade-offs. The synchronizer doesn't automatically understand referential integrity between tables, although I can control their processing order. It doesn't negotiate schemas either; keeping compatible schemas across the relevant servers is my responsibility.&lt;/p&gt;
&lt;p&gt;Those are not necessarily features waiting to be implemented. They are boundaries I know about and can design around.&lt;/p&gt;
&lt;h3 id=&quot;boring-as-a-feature&quot;&gt;Boring as a feature&lt;/h3&gt;
&lt;p&gt;Around 10 million changes pass through this setup every day, with the ASN database alone sometimes generating several million updates during a processing run.&lt;/p&gt;
&lt;p&gt;Could I make the synchronization system considerably smarter? Absolutely.&lt;/p&gt;
&lt;p&gt;But I didn't set out to build a distributed database. I wanted a multitude of servers that could operate independently, share the data they needed, tolerate each other disappearing, and recover without me getting involved.&lt;/p&gt;
&lt;p&gt;The synchronization system is simply what fell out of those requirements.&lt;/p&gt;
&lt;p&gt;There are plenty of situations where this design would be completely inappropriate. There are also plenty where sophisticated distributed database technology solves very real problems.&lt;/p&gt;
&lt;p&gt;But complexity has a real cost.&lt;/p&gt;
&lt;p&gt;For iamroot.tech, I'd rather have infrastructure I can explain on a single piece of paper, predict when something breaks, and largely leave alone when a server disappears.&lt;/p&gt;
&lt;p&gt;Extremely simple, asynchronous and a tad inefficient in the actual operations, yes - but very efficient at solving the problem, very predictable in the way it works, and - most importantly - it does not require any human intervention at any point.&lt;/p&gt;
&lt;p&gt;That is all I need.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26522/building-database-synchronization-the-boring-way</guid>
<pubDate>Tue, 08 Sep 2026 21:13:56 +0000</pubDate>
</item>
<item>
<title>Arabic search in WordPress: the matches it silently misses</title>
<link>https://coderlegion.com/23415/arabic-search-in-wordpress-the-matches-it-silently-misses</link>
<description>&lt;p&gt;Two hundred and fifty thousand articles, and the thing editors complained about was the search box. Not the accuracy of it. The wait. Type a name into the newsroom's admin search, go and do something else, come back.&lt;/p&gt;
&lt;p&gt;That is the easy complaint, because it arrives. Someone tells you. What never arrives is the other one, and it took me far too long to notice it was missing: nobody has ever told me the search came back empty for a story we definitely published. Nobody ever will. A search that returns nothing does not look like a bug. It looks like proof you never covered it.&lt;/p&gt;
&lt;p&gt;So we fixed the loud one, and left the quiet one running for years.&lt;/p&gt;
&lt;p&gt;It is the same seam as &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/insights/wordpress-arabic-slug-truncation&quot;&gt;an earlier piece on Arabic slugs&lt;/a&gt;: Arabic quietly breaking an assumption WordPress never knew it was making. Last time it was the URL. This time it is the search box.&lt;/p&gt;
&lt;h3 id=&quot;what-wordpress-actually-does-with-an-arabic-query&quot;&gt;What WordPress actually does with an Arabic query&lt;/h3&gt;
&lt;p&gt;Type a term into WordPress search and &lt;code&gt;WP_Query&lt;/code&gt; builds this, near enough:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WHERE post_title   LIKE '%term%'
   OR post_excerpt LIKE '%term%'
   OR post_content LIKE '%term%'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the whole mechanism. Not a search engine, not an index, a substring test run three times per row. It knows nothing about words, and nothing at all about Arabic.&lt;/p&gt;
&lt;p&gt;We measured what that means on a throwaway WordPress 7.0.2 and MySQL 8.0.35: one post per spelling, then the obvious searches.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;you search &lt;/th&gt;&lt;th&gt;you get back&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;محمد &lt;/td&gt;&lt;td&gt;only the plain post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;مُحَمَّد (with tashkeel) &lt;/td&gt;&lt;td&gt;only the tashkeel post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;محمـــد (with tatweel) &lt;/td&gt;&lt;td&gt;only the tatweel post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;أحمد (hamza alef) &lt;/td&gt;&lt;td&gt;only the hamza post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;احمد (bare alef) &lt;/td&gt;&lt;td&gt;only the bare alef post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;مصطفى (alef maqsura) &lt;/td&gt;&lt;td&gt;only the maqsura post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;مصطفي (yaa) &lt;/td&gt;&lt;td&gt;only the yaa post&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;مكتبه (haa for taa marbuta) &lt;/td&gt;&lt;td&gt;nothing at all&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Read that right-hand column again. Every search returns only its own exact spelling, and the last returns nothing at all, even though every term on the left is the same word to a human being. A reader who types a name without diacritics never sees the article that has them. Someone who writes أحمد never finds احمد. Someone who ends a word with ه instead of ة gets an empty page.&lt;/p&gt;
&lt;p&gt;The one result that looks like a success is the exception that proves the point. Searching كتاب does return الكتاب. That is not the search understanding the definite article, it is the letters ك ت ا ب happening to sit inside الكتاب as a literal substring. Search وكتاب, the same word with a prefixed و, and you get nothing, because the bare word is no longer embedded in it.&lt;/p&gt;
&lt;p&gt;The admin post list behaves identically to the front end, which is the part that matters, because that is where your editors live.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.mantek.io/insights/wordpress-arabic-search-diagram-1.png&quot; alt=&quot;One word, many spellings: why LIKE misses and normalising matches&quot;&gt;&lt;br&gt;
&lt;em&gt;The same word, written three ways, and a reader means one thing by all of them. WordPress compares raw bytes with LIKE, so the three stay three different strings and none finds the others. Fold them with the same normaliser and they collapse to a single token that matches every time.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-database-already-knows-wordpress-never-asks-it-&quot;&gt;The database already knows. WordPress never asks it.&lt;/h3&gt;
&lt;p&gt;This is the part that genuinely surprised me.&lt;/p&gt;
&lt;p&gt;WordPress does not inherit your server's collation. It forces its own onto its tables, &lt;code&gt;utf8mb4_unicode_520_ci&lt;/code&gt;, whatever MySQL's default happens to be. And that collation is not naive about Arabic. Ask MySQL directly, with an equality test:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT 'مُحَمَّد' = 'محمد' COLLATE utf8mb4_unicode_520_ci;   -- 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One. True. The collation folds the diacritics away and says these are the same string. On MySQL 8's &lt;code&gt;utf8mb4_0900_ai_ci&lt;/code&gt; it folds tatweel as well.&lt;/p&gt;
&lt;p&gt;Now ask it the way WordPress asks it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SELECT 'مُحَمَّد' LIKE '%محمد%' COLLATE utf8mb4_unicode_520_ci;   -- 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Zero. Same data, same collation, same server, opposite answer. Wildcard pattern matching does not apply the collation's character folding, so every piece of Arabic knowledge sitting in your database is thrown away the moment you wrap the term in &lt;code&gt;%&lt;/code&gt;. We tested four collations. Under &lt;code&gt;=&lt;/code&gt;, the good ones fold. Under &lt;code&gt;LIKE '%…%'&lt;/code&gt;, all four return zero.&lt;/p&gt;
&lt;p&gt;The capability was already in the building. The search architecture walks straight past it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.mantek.io/insights/wordpress-arabic-search-diagram-2.png&quot; alt=&quot;The collation folds Arabic under = but not under LIKE&quot;&gt;&lt;br&gt;
&lt;em&gt;The identical pair, the diacritic-marked spelling and the plain one, under WordPress's own collation. Compared with =, the collation folds the tashkeel away and returns 1: they are equal. Compared with LIKE and a wildcard, that folding is skipped and it returns 0. Same data, same collation, opposite answer, and WordPress search asks with LIKE.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;even-reaching-it-would-not-be-enough&quot;&gt;Even reaching it would not be enough&lt;/h3&gt;
&lt;p&gt;It is tempting to stop here and go looking for a way to make WordPress compare with &lt;code&gt;=&lt;/code&gt;. It would not save you, and the measurements show why.&lt;/p&gt;
&lt;p&gt;A collation folds &lt;strong&gt;accents&lt;/strong&gt;. Tashkeel are accents, combining marks that sit on top of a letter, which is exactly why they fold.&lt;/p&gt;
&lt;p&gt;Hamza is not an accent. أ and ا are different letters. So are ى and ي. So are ة and ه. None of them fold, in any collation we tested, under any comparison. And these are not edge cases in Arabic, they are the ordinary daily variation: the same name spelled أحمد by one editor and احمد by another, the same word ending ى in one headline and ي in the next.&lt;/p&gt;
&lt;p&gt;So the database will hand you diacritics for free if you ask it correctly, and it will never hand you the rest. Anything that actually works has to normalise the text itself, deliberately, on both sides of the comparison.&lt;/p&gt;
&lt;h3 id=&quot;what-it-costs-and-why-the-broken-searches-are-the-expensive-ones&quot;&gt;What it costs, and why the broken searches are the expensive ones&lt;/h3&gt;
&lt;p&gt;The other half of this is the half your editors do complain about.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;LIKE&lt;/code&gt; with a leading wildcard cannot use an index. Not &quot;is slow to use one&quot;, cannot use one, ever. &lt;code&gt;EXPLAIN&lt;/code&gt; on the query WordPress generates says it plainly:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type: ALL     key: NULL     rows: 50013
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Full scan, no key, every row. We seeded 50,000 realistic Arabic posts and timed it with a fresh PHP process per run, because repeating it inside one process makes WordPress's in-request cache report a fraction of a millisecond, which is a lie:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a term matching one post: about 215 ms&lt;/li&gt;
&lt;li&gt;a term matching nothing: about 225 ms&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The search that finds nothing is the slowest, and that is not a rounding artefact. &lt;code&gt;LIKE '%term%'&lt;/code&gt; gives up on a row the instant it finds the substring, so a row that matches is cheap: often the title matches and the long body is never read at all. A row that does &lt;strong&gt;not&lt;/strong&gt; match has to be read to the last byte of every field before MySQL can be certain the word is absent. Proving absence is the expensive case. And in Arabic, the searches that come back empty are precisely the variant spellings in the table above.&lt;/p&gt;
&lt;p&gt;You might expect &lt;code&gt;LIMIT 10&lt;/code&gt; to rescue this. It cannot, and MySQL says so in plain words. WordPress needs a total for pagination, so it asks for &lt;code&gt;SQL_CALC_FOUND_ROWS&lt;/code&gt;, and the query plan reports the consequence itself:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Limit: 10 row(s) (no early end due to SQL_CALC_FOUND_ROWS)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ten results on the page, every matching row enumerated to produce them.&lt;/p&gt;
&lt;p&gt;The scan cost tracks table size, so a 250,000-article archive lands somewhere around a second per search, per concurrent searcher, with no index able to help. (That figure is an extrapolation from the 50k measurement, not a measurement of a 250k table.)&lt;/p&gt;
&lt;p&gt;Now put the two halves together. The searches that silently return nothing are the same searches that scan your entire archive in order to return nothing. In Arabic, those are every variant spelling your readers and editors actually type. The complaint you get and the complaint you never get are the same query.&lt;/p&gt;
&lt;h3 id=&quot;what-we-built-and-what-it-fixed&quot;&gt;What we built, and what it fixed&lt;/h3&gt;
&lt;p&gt;We did the obvious right thing, and it worked.&lt;/p&gt;
&lt;p&gt;We built a table outside WordPress, on AWS, one row per article: id, title, body, author, categories. Every time a post was published, updated or deleted, that row was kept in step. Search stopped asking the WordPress database to scan a quarter of a million rows and started asking a store built for the job. Public search had already been handed off to a Google Custom Search Engine, so between the two, search stopped being the thing anyone raised in a meeting.&lt;/p&gt;
&lt;p&gt;(That table later grew into something much larger, a recommendation engine that learns what a reader is interested in and chooses their next article. That is a story for another day.)&lt;/p&gt;
&lt;p&gt;By every measure we were watching, we had fixed search.&lt;/p&gt;
&lt;h3 id=&quot;what-it-did-not-fix&quot;&gt;What it did not fix&lt;/h3&gt;
&lt;p&gt;We never normalised the Arabic.&lt;/p&gt;
&lt;p&gt;The new table was faster. It was still storing and matching the same raw strings, with the same orthographic variation, in the same literal way. A reader typing محمد still missed مُحَمَّد. أحمد still missed احمد. مكتبه still found nothing. We had rebuilt the entire search path, moved it to another cloud, and put a proper store underneath it, and every one of those searches failed exactly as it had before.&lt;/p&gt;
&lt;p&gt;That is the lesson, and it took us years to see it: &lt;strong&gt;the speed problem was an infrastructure problem, and the matching problem never was.&lt;/strong&gt; Scale, indexes and a store designed for querying will fix a scan. None of them will turn two spellings of the same word into the same token. Normalisation is a text problem, and if you do not solve it, it follows you to AWS.&lt;/p&gt;
&lt;p&gt;And because nobody ever complains about a search that returns nothing, nothing ever forced the issue. The loud problem got an entire re-architecture. The quiet one just carried on.&lt;/p&gt;
&lt;h3 id=&quot;the-layer-we-were-missing&quot;&gt;The layer we were missing&lt;/h3&gt;
&lt;p&gt;The fix is small, and it is the same fix wherever your index lives.&lt;/p&gt;
&lt;p&gt;Normalise both sides. Keep a normalised copy of the searchable text, index that, and put every incoming query through the identical normaliser before matching. Both halves matter: normalise only the stored text and a query carrying diacritics still misses, normalise only the query and it misses the stored variants.&lt;/p&gt;
&lt;p&gt;What the normaliser has to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Strip the diacritics.&lt;/strong&gt; Tashkeel are combining marks (U+064B to U+0652 and friends). Remove them entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strip tatweel&lt;/strong&gt; (U+0640). It is decoration and carries no meaning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unify the alef forms.&lt;/strong&gt; أ إ آ ٱ all become ا.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unify ى to ي&lt;/strong&gt;, and decide about &lt;strong&gt;ة to ه&lt;/strong&gt;. These are conventions, not truths. Pick one, apply it to both sides, write it down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strip the definite article&lt;/strong&gt; الـ, so الكتاب and كتاب fold to one token. Guard it, so it only comes off when enough of the word is left to still be a word, otherwise ال on the front of a short word gets eaten too. And stop there. It is tempting to strip the single-letter prefixes و ف ب ك ل as well, but those letters begin ordinary words, so بيت would lose its ب and فكرة would become كرة, a genuinely different word. So وكتاب will not fold to كتاب, and that conservative choice is the correct one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leave Latin alone.&lt;/strong&gt; Newsroom copy is full of WordPress, AWS, S3 and half-transliterated terms. A normaliser that mangles those breaks more than it fixes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do that and the table from earlier collapses into a single row: every spelling of محمد, however it was typed, becomes one token that matches all the others. And because the normalised copy is matched through an index instead of a leading-wildcard scan, the query that used to take a second stops scanning the table at all.&lt;/p&gt;
&lt;p&gt;And none of this needs a cloud. The story above happened to run through a table we put on AWS, but that was our answer to scale, not to matching. On an ordinary single-server WordPress, the same normalised copy in the same database, with the query folded the same way, fixes both the misses and the scan with nothing external at all. That is the layer we were missing, and it would have been missing just as badly on one server as on twenty.&lt;/p&gt;
&lt;p&gt;One change, both complaints.&lt;/p&gt;
&lt;h3 id=&quot;the-plugin&quot;&gt;The plugin&lt;/h3&gt;
&lt;p&gt;The whole fix is one file. It goes in &lt;code&gt;wp-content/mu-plugins/&lt;/code&gt;, and in its default mode it needs nothing else: no cloud, no service, no configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;composer require mantekio/wp-arabic-search
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On save it folds everything searchable about a post, title, excerpt and body, into one normalised column in a shadow table with a real &lt;code&gt;FULLTEXT&lt;/code&gt; index, and rewrites WordPress search to match against that column through the identical normaliser. The public site and the wp-admin post list both run through &lt;code&gt;WP_Query&lt;/code&gt;, so both are fixed at once. Build the index over what you already have with one command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wp arabic-search reindex
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Media gets the same treatment, which turned out to matter more than expected. An attachment keeps its text in four places: the title, the caption, the alt text and the filename. All four go into the same column, so a picture captioned in Arabic is findable by that caption, and &lt;code&gt;صورة-بيروت.jpg&lt;/code&gt; is findable by its own name. WordPress does not search alt text at all, so on that one the plugin is not restoring parity, it is adding something core never had. This changes what the Media Library can find, not what appears anywhere else: the plugin decides which rows &lt;em&gt;match the text&lt;/em&gt;, and WordPress still decides which post types and statuses are allowed in the results.&lt;/p&gt;
&lt;p&gt;It ships with two modes, because the normaliser is the reusable part and the index is not. The default, &lt;code&gt;index&lt;/code&gt;, is the self-contained one just described. The other, &lt;code&gt;normaliser&lt;/code&gt;, maintains nothing and rewrites nothing: it exposes the normalising function and fires an action carrying the normalised text on every save, so a pipeline that indexes somewhere else (the AWS table we built, OpenSearch, anything) can fold its stored text and its queries with the same code. That is the guarantee that actually matters, and it is the one we did not have for years: both sides normalised by identical code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;define( 'WPAS_MODE', 'normaliser' );
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing it deliberately refuses to do. InnoDB does not index words shorter than three characters, which in Arabic covers من, في, ما and عن: real words, typed constantly. An index that has never seen a word cannot find it, so requiring one would return an empty page for a search plain WordPress answers perfectly well. That is exactly the failure this article is about, and it would have been ours. So the plugin drops those tokens rather than requiring them, and where a search contains nothing else it hands the query back to core's &lt;code&gt;LIKE&lt;/code&gt; untouched. Search a two-letter word and you get precisely what WordPress would have given you; search two words where only one clears the threshold and the long one still matches. The rule it follows is that where it cannot help, it stands aside rather than answer worse than the search it replaced. There is nothing to configure for this, and no reason to go changing &lt;code&gt;innodb_ft_min_token_size&lt;/code&gt; on your server: it needs a MySQL restart and a rebuild of every full-text index on the box, and it buys you nothing here.&lt;/p&gt;
&lt;p&gt;Which raises the question any search tool eventually gets asked, usually by an editor who is certain the article exists: why did this not match? Answering it used to mean reading the source, so there is now a command that just tells you.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ wp arabic-search explain &quot;من المكتبة&quot;
normalised:  من مكتبه

+--------+------+--------------------------------------------------+
| token  | used | reason                                           |
+--------+------+--------------------------------------------------+
| من     | no   | shorter than innodb_ft_min_token_size (3)        |
| مكتبه  | yes  | indexable                                        |
+--------+------+--------------------------------------------------+

matches with: MATCH(search_text) AGAINST ('+مكتبه' IN BOOLEAN MODE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The folded form, every token with whether it was used and why not, and the exact query that runs. It is the smallest feature in the plugin and the one I would miss most, because &quot;search is broken&quot; and &quot;search is working exactly as designed on a word the index cannot hold&quot; look identical from the outside, and this is the difference between them.&lt;/p&gt;
&lt;p&gt;We eventually ran it against the real thing: a quarter of a million Arabic articles, plus half a million media items, on a copy of a live newsroom archive. One search settles the whole argument.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;searching مُحَمَّد across 250,000 articles &lt;/th&gt;&lt;th&gt;results &lt;/th&gt;&lt;th&gt;time&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;WordPress &lt;/td&gt;&lt;td&gt;&lt;strong&gt;1&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;4.8 s&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;with the plugin &lt;/td&gt;&lt;td&gt;&lt;strong&gt;28,238&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;0.55 s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;One. Out of twenty-eight thousand articles that contain that name, plain WordPress found a single one, because only one of them happened to spell it with exactly those diacritics. The other 28,237 were in the database the whole time, and no one searching for that name would ever have seen them.&lt;/p&gt;
&lt;p&gt;The speed is the part you would have put in the ticket. The other column is the part nobody was ever going to report.&lt;/p&gt;
&lt;p&gt;The rest of the numbers come from a controlled 50,000-post rig, where the match count can be dialled precisely, before and after:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;matches, out of 50,000 &lt;/th&gt;&lt;th&gt;default WordPress &lt;/th&gt;&lt;th&gt;with the plugin&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;about 215 ms &lt;/td&gt;&lt;td&gt;5 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;500 (1%) &lt;/td&gt;&lt;td&gt;about 212 ms &lt;/td&gt;&lt;td&gt;10 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5,000 (10%) &lt;/td&gt;&lt;td&gt;about 230 ms &lt;/td&gt;&lt;td&gt;24 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;25,000 (50%) &lt;/td&gt;&lt;td&gt;about 250 ms &lt;/td&gt;&lt;td&gt;115 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;50,000 (100%) &lt;/td&gt;&lt;td&gt;about 310 ms &lt;/td&gt;&lt;td&gt;about 210 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; tells the same story as the clock. Before, every search was the full scan:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type: ALL        key: NULL        rows: 50013
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After, the index drives it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type: fulltext   key: search_ft   rows: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We had that bottom row wrong for a while, and the way it was wrong is worth repeating, because it is a trap set for anyone who benchmarks a text search. Our first measurement said the index &lt;strong&gt;lost&lt;/strong&gt; on a term matching everything. It did not. The word we had seeded happened to sit near the &lt;em&gt;start&lt;/em&gt; of every document, and since &lt;code&gt;LIKE&lt;/code&gt; abandons a row the moment it finds a match, we were timing the scan at its luckiest: first few bytes, done, next row. Move the same word to the end of the same documents, and the result inverts.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;a term matching all 50,000 rows &lt;/th&gt;&lt;th&gt;default WordPress &lt;/th&gt;&lt;th&gt;with the plugin&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;the word sits at the start of every document &lt;/td&gt;&lt;td&gt;140 ms &lt;/td&gt;&lt;td&gt;208 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;the word sits at the end of every document &lt;/td&gt;&lt;td&gt;300 ms &lt;/td&gt;&lt;td&gt;205 ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Read the plugin column first: it barely moves, because an index does not care where in the text the word happens to sit. Now read the other one, which doubles. We had benchmarked the scan's single luckiest case and called it a fair fight.&lt;/p&gt;
&lt;p&gt;So the honest shape is a crossover, not a caveat. Against a term the scan actually has to read, the index wins across the entire range in the table above. Against a term positioned for the scan's best possible case, the index still wins until roughly &lt;strong&gt;70%&lt;/strong&gt; of the archive matches. A query that matches seventy per cent of a newsroom archive is not a search, it is a &lt;code&gt;SELECT *&lt;/code&gt; with extra steps.&lt;/p&gt;
&lt;p&gt;And the case that used to hurt most is now the cheapest. The old worst case was the search that came back empty, because proving absence meant reading every byte of every row. An index that finds nothing has nothing to read.&lt;/p&gt;
&lt;p&gt;One honest word on where it is in its life. It is &lt;strong&gt;new&lt;/strong&gt;, and it ships as &lt;code&gt;0.9.3&lt;/code&gt;. That it is already on its fourth release says something worth saying plainly: six rounds of adversarial testing found a bug every single time, and not one of them was found by reading the code.&lt;/p&gt;
&lt;p&gt;The worst was in the first release. Search a two-letter Arabic word, من or في, and it returned nothing at all. The check meant to drop words too short for the index counted &lt;em&gt;bytes&lt;/em&gt;, and an Arabic letter is two bytes, so every one of those words sailed through and became a required term the index had never held. A plugin whose entire argument is that Arabic search silently returns nothing had shipped a way to make Arabic search silently return nothing. Others followed: a health check that could not detect the condition it existed to detect, and a reindex command that announced success having written not one row.&lt;/p&gt;
&lt;p&gt;They are all fixed, and every fix is in the changelog, but the pattern is the point. Each one was invisible from the code and obvious the moment something actually ran it. If this piece has a moral it is that one, and it applies to your search too: the failure that returns nothing looks exactly like the absence of anything to find.&lt;/p&gt;
&lt;p&gt;What it has not yet had is months in a live newsroom, so it is out in the open under the GPL, on GitHub and Packagist, being hardened in daylight. The repository is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mantekio/wp-arabic-search&quot;&gt;github.com/mantekio/wp-arabic-search&lt;/a&gt;. Same loop as the pieces before it: the article explains the thinking, the repository is the plugin, the package is the one-line install, and each points at the other two.&lt;/p&gt;
&lt;h3 id=&quot;known-limits&quot;&gt;Known limits&lt;/h3&gt;
&lt;p&gt;Being clear about what a tool does not do is part of the tool.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Single-letter prefixes are deliberately not stripped.&lt;/strong&gt; Folding the definite article is safe behind a length guard; folding و ف ب ك ل is not, because they also start ordinary words, so it would turn بيت into يت and فكرة into كرة. The cost of that safety is that وكتاب does not fold to كتاب. Real prefix handling needs morphology, not a character map.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Folding is not stemming.&lt;/strong&gt; It will not connect كتاب to its broken plural كتب, because Arabic plurals rewrite the word rather than append to it. That needs a root dictionary, and that is a different project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The results are only as complete as the index.&lt;/strong&gt; Nothing walks your archive when you install it, so run &lt;code&gt;wp arabic-search reindex&lt;/code&gt; once. After that each save keeps its own row current, and &lt;code&gt;wp arabic-search status&lt;/code&gt; exits non-zero the moment the index falls behind, goes stale, or carries rows for posts that no longer exist. Drift is something you get told about rather than something a reader discovers for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It costs disk, inside the database.&lt;/strong&gt; A second normalised copy of your searchable text is still a copy. On that quarter-million-article archive the table came to about 1.1 GB, roughly 3 KB per article, though the &lt;code&gt;FULLTEXT&lt;/code&gt; index on top of it was only 17 MB. Media is nearly free: attachments were two thirds of the rows and a sixteenth of the bytes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Words the index cannot hold are correct, not fast.&lt;/strong&gt; A search that falls back gets core's behaviour entirely, including core's speed, because it is core's query. On that same archive a two-letter word took about three seconds, exactly as it always had, while an indexed search of the same corpus took well under one. Nothing is worse than before. Nothing is better either.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Normalisation is lossy on purpose.&lt;/strong&gt; Folding ة into ه, or ى into ي, will occasionally merge two words that genuinely differ. In an archive search that trade is nearly always worth it, but it is a trade: decide it deliberately and document it, rather than discovering it later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Past a certain size, use a real search engine.&lt;/strong&gt; OpenSearch ships an &lt;code&gt;arabic&lt;/code&gt; analyser that does normalisation, stemming and stopwords properly, and gives you relevance ranking a &lt;code&gt;LIKE&lt;/code&gt; will never have. If the archive is large and search matters, that is the destination. You still need the normaliser, because the same text problem travels with you. Ours did.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;A quarter of a million articles, and the only thing anyone ever told us about search was that it was slow. So we made it fast. We built the table, moved the queries to AWS, kept it in sync on every post, the complaints stopped, and for a long time we took that to mean we had finished.&lt;/p&gt;
&lt;p&gt;The searches that returned nothing never produced a single complaint, because a search that returns nothing looks exactly like a subject you never covered. The archive was full of articles nobody could find, and the silence around them read as success.&lt;/p&gt;
&lt;p&gt;That is the part worth keeping, whatever your stack: complaints tell you about the loud failures and nothing at all about the quiet ones. Someone has to go and look.&lt;/p&gt;
&lt;p&gt;That is how we run &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/wordpress-aws&quot;&gt;WordPress on AWS&lt;/a&gt; for newsrooms: fix what people report, then go hunting for what they cannot. If your archive is in Arabic and you have never tested what your search does with a hamza, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mantek.io/contact&quot;&gt;let's talk&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23415/arabic-search-in-wordpress-the-matches-it-silently-misses</guid>
<pubDate>Fri, 24 Jul 2026 23:41:33 +0000</pubDate>
</item>
<item>
<title>How to Create a Trigger That Prevents an Update</title>
<link>https://coderlegion.com/20230/how-to-create-a-trigger-that-prevents-an-update</link>
<description>&lt;p&gt;To stop a table update in modern MySQL, you use the SIGNAL statement. This statement allows you to return an error state and a custom message to the application, which immediately aborts the triggering operation and rolls back the uncommitted transaction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Code Implementation&lt;/strong&gt;&lt;br&gt;
SQL&lt;br&gt;
DELIMITER $$&lt;/p&gt;
&lt;p&gt;CREATE TRIGGER before_employee_update&lt;br&gt;
BEFORE UPDATE ON employees&lt;br&gt;
FOR EACH ROW&lt;br&gt;
BEGIN&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- Check if the salary is being decreased
IF NEW.salary &amp;lt; OLD.salary THEN
    SIGNAL SQLSTATE '45000' 
    SET MESSAGE_TEXT = 'Error: Employee salary cannot be decreased.';
END IF;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;END$$&lt;/p&gt;
&lt;p&gt;DELIMITER ;&lt;br&gt;
Key Trigger Mechanics&lt;br&gt;
BEFORE UPDATE ensures the validation happens before any changes are actually written to the disk, allowing for a clean interception.&lt;/p&gt;
&lt;p&gt;NEW vs OLD modifiers let you compare the incoming updated value against the existing historical value.&lt;/p&gt;
&lt;p&gt;SQLSTATE '45000' is a generic state that represents an &quot;unhandled user-defined exception,&quot; making it perfect for custom business rule enforcement.&lt;/p&gt;
&lt;p&gt;The Potential Problems&lt;br&gt;
While this approach is highly effective, relying solely on database-level triggers to block updates introduces a few specific challenges in real-world applications.&lt;/p&gt;
&lt;p&gt;Silent Application Failures: If your backend code isn't explicitly wrapped in robust try-catch blocks, a trigger-enforced database error can crash your API route or leave end-users looking at a generic, unhelpful 500 error page.&lt;/p&gt;
&lt;p&gt;Hidden Business Logic: Moving critical validation rules exclusively into MySQL means your application logic becomes split. Developers reviewing backend code might not realize why an update is failing if the rule lives entirely inside a database trigger.&lt;/p&gt;
&lt;p&gt;Performance Overhead: For massive bulk updates, executing conditional checks and firing state signals for every single row (FOR EACH ROW) can noticeably degrade database throughput.&lt;/p&gt;
&lt;p&gt;How to Solve These Problems&lt;br&gt;
To handle these issues gracefully, you need a strategy that bridges the gap between your database and your application layer.&lt;/p&gt;
&lt;p&gt;Graceful Exception Handling: Always catch the SQL exception in your backend application and map the 45000 custom error message directly to a user-friendly frontend alert.&lt;/p&gt;
&lt;p&gt;Dual-Layer Validation: Mirror the trigger logic inside your application code. Use the database trigger as a bulletproof final line of defense, while using application validation to provide immediate, low-latency feedback to users.&lt;/p&gt;
&lt;p&gt;Clear Database Documentation: Ensure all custom triggers are heavily documented in your codebase repository so team members maintain full visibility over automated data restrictions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Sumita&lt;/strong&gt; &lt;br&gt;
&lt;strong&gt;Web Developer&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20230/how-to-create-a-trigger-that-prevents-an-update</guid>
<pubDate>Thu, 11 Jun 2026 05:23:15 +0000</pubDate>
</item>
<item>
<title>Your Database GUI Shouldn't Need an Account</title>
<link>https://coderlegion.com/20175/your-database-gui-shouldnt-need-an-account</link>
<description>&lt;p&gt;A critique of the trend toward account-required database GUI clients, arguing that tools holding production credentials should never route data through vendor backends. The post makes the case for local-first design using Tabularis as a concrete example: no signup, secrets stored in the OS keychain, all config as plain files on disk, bring-your-own-key AI, and only one network call (a GitHub version check). The tradeoffs — no sync, no team workspaces, no telemetry — are acknowledged openly. The core argument is that database clients are plumbing and should be durable and vendor-independent, not coupled to a company's runway.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20175/your-database-gui-shouldnt-need-an-account</guid>
<pubDate>Wed, 10 Jun 2026 13:52:44 +0000</pubDate>
</item>
<item>
<title>Public key retrieval is not allowed / Needs improvement</title>
<link>https://coderlegion.com/18848/public-key-retrieval-is-not-allowed-needs-improvement</link>
<description>&lt;p&gt;&lt;strong&gt;Today, in this article we will discuss the error message &quot;Public key retrieval is not allowed&quot;. This error &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;message is indicating that the system is configured to disallow the retrieval of a public key&lt;/span&gt;. This is likely a security measure &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;to prevent unauthorized access&lt;/span&gt; to sensitive information. It is &lt;span style=&quot;color: rgb(255, 4, 252);&quot;&gt;recommended to check with the administrator&lt;/span&gt; or technical support for further assistance.&lt;/strong&gt;&lt;/p&gt;
&lt;div id=&quot;toc-div&quot;&gt;
&lt;h4&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Table of Contents: &lt;a id=&quot;toc-toogle&quot; class=&quot;open&quot;&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;div id=&quot;toc-list&quot;&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#what&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span data-sheets-value=&quot;{&quot; data-sheets-userformat=&quot;{&quot;&gt;What does Error &quot;Public key retrieval is not allowed&quot; mean?&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#why&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;Why is the Error Generated?&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#sol&quot; rel=&quot;nofollow&quot;&gt;How to Solve the Error?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#con&quot; rel=&quot;nofollow&quot;&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;The Conclusion&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;a href=&quot;#con&quot; rel=&quot;nofollow&quot;&gt;The References&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;section&gt;&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;What does error &quot;Public key retrieval is not allowed&quot; mean?&lt;/strong&gt;&lt;/span&gt;&lt;strong&gt; &lt;a id=&quot;TypeError-str-object&quot; style=&quot;color: blue;&quot; href=&quot;#what&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The error message &quot;Public key retrieval is not allowed&quot; usually &lt;strong&gt;occurs when an application or service is trying to access a public key&lt;/strong&gt; from the server it is trying to connect to, but the operation is &lt;strong&gt;not allowed due to security or access control measures&lt;/strong&gt; that prevent the retrieval of the public key.&lt;/p&gt;
&lt;p&gt;In cryptography, public keys are used for encryption and digital signatures, while private keys are used for decryption and signing. When &lt;strong&gt;you attempt to connect to a server&lt;/strong&gt;, the system must first retrieve the server's public key &lt;strong&gt;to establish a secure connection&lt;/strong&gt;. If the &lt;strong&gt;server's public key cannot be retrieved&lt;/strong&gt;, the system may &lt;strong&gt;not be able to establish a secure connection&lt;/strong&gt;, and the error message &lt;strong&gt;&quot;Public key retrieval is not allowed&quot; will be displayed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The cause of this error can vary. This &lt;strong&gt;may happen be due to a firewall, network restriction&lt;/strong&gt;, or a policy set by the system administrator. To resolve this issue, you may &lt;strong&gt;need to contact your system administrator&lt;/strong&gt; or network administrator to request access to the public key &lt;strong&gt;or check the firewall&lt;/strong&gt; or network configurations.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Why is the Error Generated? &lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;reasons-that-will-cause&quot; style=&quot;color: blue;&quot; href=&quot;#why&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The &quot;Public key retrieval is not allowed&quot; error message can occur due to various causes. Some of the most common causes are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firewall restriction:&lt;/strong&gt; Your system may not be able to retrieve the server's public key if the connection is being blocked by a firewall. The firewall may be configured to block connections to the server or to block the retrieval of the public key.&lt;br&gt;For example; you are trying to connect to a server over a secure connection using SSL/TLS, but the firewall is blocking the connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network restriction:&lt;/strong&gt; The system may not be able to retrieve the server's public key if there are restrictions in place on the network that prevent the retrieval. This could be due to a policy set by the network administrator or due to a misconfiguration of the network.&lt;br&gt;Say, you try to retrieve the public key of a server that is located in a different country, but the connection is blocked by the network administrator due to security policies. Then you will receives the error message &quot;Public key retrieval is not allowed.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access control measures:&lt;/strong&gt; The server may have security measures in place that prevent unauthorized access to the public key. This could be due to a policy set by the system administrator or due to security software installed on the server e.g. you are trying to connect to a server that has security measures in place to prevent unauthorized access to the public key.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MySQLConnectionExample {

  public static void main(String[] args) {
    try {
      // Load the MySQL Connector/J driver
      Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

      // Establish a connection to the MySQL server
      Connection conn = DriverManager.getConnection(
        &quot;jdbc:mysql://localhost:3306/test_db?useSSL=true&quot;,
        &quot;username&quot;,
        &quot;password&quot;
      );

      System.out.println(&quot;Connected to the MySQL server successfully!&quot;);

      // Close the connection
      conn.close();
    } catch (SQLException | ClassNotFoundException e) {
      System.out.println(&quot;An error occurred while connecting to the MySQL server: &quot; + e.getMessage());
    }
  }
}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we first load the MySQL Connector/J driver using the &lt;code&gt;Class.forName&lt;/code&gt; method. Then, we use the &lt;code&gt;DriverManager.getConnection&lt;/code&gt; method to establish a connection to the MySQL server. The connection URL includes the &lt;code&gt;useSSL=true&lt;/code&gt; parameter, which indicates that we want to use SSL/TLS to secure the connection. We also specify the username and password to use when connecting to the server.&lt;br&gt;Here when the client is unable to retrieve the public key an error occurs, an exception will be thrown and an error message will be displayed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP/domain restrictions:&lt;/strong&gt; The server may be configured to only allow public key retrieval from specific IP addresses or domains. If you are not on the approved list, it may not be able to retrieve the public key.&lt;br&gt;An example is that you are trying to connect to a server that is configured to only allow public key retrieval from specific IP addresses or domains and your system is not on the approved list, so the will receive the error.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incorrect server configuration:&lt;/strong&gt; The server may be misconfigured, which could prevent the retrieval of the public key. This could be due to an error in the server's settings or due to an incorrect configuration of the server's software. For example, you try to connect to a server that is misconfigured, which prevents the retrieval of the public key.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate issues:&lt;/strong&gt; The server may have an expired or revoked certificate, which could prevent the retrieval of the public key. In this case, the system may not be able to establish a secure connection with the server. For example, you will try to connect to a server that has an expired or revoked certificate, which prevents the retrieval of the public key.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt; &lt;/p&gt;
&lt;div class=&quot;div-purple&quot;&gt;&lt;span class=&quot;alert-header&quot;&gt;Note&lt;/span&gt; &lt;span class=&quot;alert-body&quot;&gt;It is important to understand the cause of the &quot;Public key retrieval is not allowed&quot; error in order to resolve it effectively. Depending on the cause, you may need to make apply the appropiate solution.&lt;/span&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;sol&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;How to Solve the Error? &lt;/strong&gt;&lt;/span&gt;&lt;strong&gt;&lt;a id=&quot;the-solution&quot; style=&quot;color: blue;&quot; href=&quot;#sol&quot; rel=&quot;nofollow&quot;&gt;#&lt;/a&gt;&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;The possible solutions for the causes of the &quot;Public key retrieval is not allowed&quot; error message are as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Firewall restriction:&lt;/strong&gt; To resolve this issue, you can check the firewall configuration and ensure that the connection to the server is not being blocked. You may also need to open specific ports or add an exception to the firewall to allow the connection.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network restriction:&lt;/strong&gt; To resolve this issue, you may need to contact the network administrator and request that the restriction be lifted or that the necessary changes be made to the network configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access control measures:&lt;/strong&gt; To resolve this issue, you may need to request access to the public key from the system administrator or the security software provider. The system administrator may need to change the security measures in place to allow you to retrieve the public key. Like, the error in previous coding example occurs due to &lt;code&gt;useSSL=true&lt;/code&gt; parameter. Therefore, we change it to &lt;code&gt;useSSL=false&lt;/code&gt; as done below.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MySQLConnectionExample {

  public static void main(String[] args) {
    try {
      // Load the MySQL Connector/J driver
      Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

      // Establish a connection to the MySQL server
      Connection conn = DriverManager.getConnection(
        &quot;jdbc:mysql://localhost:3306/test_db?useSSL=false&quot;,
        &quot;username&quot;,
        &quot;password&quot;
      );

      System.out.println(&quot;Connected to the MySQL server successfully!&quot;);

      // Close the connection
      conn.close();
    } catch (SQLException | ClassNotFoundException e) {
      System.out.println(&quot;An error occurred while connecting to the MySQL server: &quot; + e.getMessage());
    }
  }
}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you change the value of &lt;code&gt;useSSL&lt;/code&gt; in the connection string from &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt;, then the connection will no longer attempt to use SSL encryption. This change can resolve the &quot;Public key retrieval is not allowed&quot; error if the issue was caused by a problem with the server's SSL certificate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP/domain restrictions:&lt;/strong&gt; To resolve this issue, one may need to check the server configuration and ensure that your IP address or domain is on the approved list. If the IP address or domain is not on the approved list, you may need to request that it be added.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Incorrect server configuration:&lt;/strong&gt; To resolve this issue, you may need to check the server configuration and make any necessary changes to ensure that the server is configured correctly. You may also need to check the server software and ensure that it is installed and configured correctly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Certificate issues:&lt;/strong&gt; To resolve this issue, you may need to check the server certificate and ensure that it is valid and up-to-date. If the certificate is expired or revoked, you may need to request a new certificate from a trusted certificate authority.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section&gt;&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;con&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;The Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;In conclusion, the &quot;Public key retrieval is not allowed&quot; &lt;strong&gt;error message indicates that the your are unable to retrieve the server's public key&lt;/strong&gt;, which is &lt;strong&gt;required to establish a secure connection&lt;/strong&gt; using SSL/TLS. This error can be &lt;strong&gt;caused by various factors&lt;/strong&gt;, including firewall restrictions, network restrictions, access control measures, IP/domain restrictions, incorrect server configuration, and certificate issues. To resolve the issue you may need to &lt;strong&gt;check for the cause&lt;/strong&gt; of the error in your specific situation and &lt;strong&gt;apply the suitable solution&lt;/strong&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;&lt;a id=&quot;ref&quot; class=&quot;custom_anchor&quot;&gt;&lt;/a&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;The References&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Some resources for reference or further information are given below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;MySQL Connector/J documentation:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html&quot; target=&quot;_new&quot;&gt;https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-using-ssl.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java API documentation:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.oracle.com/en/java/javase/14/docs/api/java.sql/java/sql/Driver.html&quot; target=&quot;_new&quot;&gt;https://docs.oracle.com/en/java/javase/14/docs/api/java.sql/java/sql/Driver.html&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;MySQL official forums:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://forums.mysql.com/&quot; target=&quot;_new&quot;&gt;https://forums.mysql.com/&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18848/public-key-retrieval-is-not-allowed-needs-improvement</guid>
<pubDate>Mon, 25 May 2026 04:13:39 +0000</pubDate>
</item>
<item>
<title>I Lost a Client's Database on a $5 VPS. Here's the 12-Line Script That Would Have Saved It.</title>
<link>https://coderlegion.com/18480/i-lost-a-clients-database-on-a-5-vps-heres-the-12-line-script-that-would-have-saved-it</link>
<description>&lt;p&gt;It was a WordPress site on a $5 DigitalOcean droplet. Client's small business. Nothing fancy.&lt;/p&gt;
&lt;p&gt;I had SSH access. I had root. I had every tool I needed to set up automated backups. I just... didn't. Because it was a small site. Because I'd &quot;get to it later.&quot; Because the database was only 40MB and what could go wrong?&lt;/p&gt;
&lt;p&gt;What went wrong was a botched plugin update that corrupted the wp_options table. No backup. No snapshot. No dump. Just a client on the phone asking why their site shows a white screen and me trying to explain that their content might be gone.&lt;/p&gt;
&lt;p&gt;I rebuilt it from a cached Google version and a Wayback Machine scrape. Took two days. Billed zero.&lt;/p&gt;
&lt;p&gt;That was the last time I ran a database without automated backups.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-script&quot;&gt;The Script&lt;/h3&gt;
&lt;p&gt;This is what I use now. Every server, every database, no exceptions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;#!/bin/bash

CHECK=&quot;✓&quot;
CROSS=&quot;✗&quot;

# --- Configuration ---
DB_USER=&quot;root&quot;
DB_PASS=&quot;your_password&quot;          # Or use ~/.my.cnf for security
DB_NAME=&quot;your_database&quot;          # Change to your DB name
BACKUP_DIR=&quot;/var/backups/mysql&quot;
KEEP_DAYS=7                       # Delete backups older than this
DATE=$(date '+%Y-%m-%d_%H-%M-%S')
FILENAME=&quot;${DB_NAME}_${DATE}.sql.gz&quot;

# --- Create backup directory if it doesn't exist ---
mkdir -p &quot;$BACKUP_DIR&quot;

# --- Run the backup ---
echo &quot;Backing up database: $DB_NAME...&quot;

if mysqldump -u&quot;$DB_USER&quot; -p&quot;$DB_PASS&quot; &quot;$DB_NAME&quot; | gzip &amp;gt; &quot;${BACKUP_DIR}/${FILENAME}&quot;; then
  echo &quot;$CHECK Backup created: ${BACKUP_DIR}/${FILENAME}&quot;
  echo &quot;$CHECK Size: $(du -sh &quot;${BACKUP_DIR}/${FILENAME}&quot; | cut -f1)&quot;
else
  echo &quot;$CROSS Backup FAILED for $DB_NAME — check credentials and database name&quot;
  exit 1
fi

# --- Delete old backups ---
echo &quot;Removing backups older than $KEEP_DAYS days...&quot;
find &quot;$BACKUP_DIR&quot; -name &quot;*.sql.gz&quot; -mtime +&quot;$KEEP_DAYS&quot; -delete
echo &quot;$CHECK Cleanup complete&quot;

# --- Show current backups ---
echo &quot;Current backups:&quot;
ls -lh &quot;$BACKUP_DIR&quot;/*.sql.gz 2&amp;gt;/dev/null || echo &quot;No backups found&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it. &lt;code&gt;mysqldump&lt;/code&gt; → &lt;code&gt;gzip&lt;/code&gt; → timestamp → auto-cleanup. Runs in under a second on most databases.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-each-piece-does&quot;&gt;What Each Piece Does&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;mysqldump -u&quot;$DB_USER&quot; -p&quot;$DB_PASS&quot; &quot;$DB_NAME&quot;&lt;/code&gt;&lt;/strong&gt; — dumps every table, every row, every schema definition into a single SQL file. This is the standard MySQL backup tool and it's been reliable for decades.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;| gzip&lt;/code&gt;&lt;/strong&gt; — pipes the dump straight into compression without ever writing an uncompressed file to disk. A 200MB database compresses to maybe 15MB. On a cheap VPS with limited storage, this matters.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;date '+%Y-%m-%d_%H-%M-%S'&lt;/code&gt;&lt;/strong&gt; — timestamps every backup file. You get filenames like &lt;code&gt;mydb_2026-05-20_03-00-01.sql.gz&lt;/code&gt;. When something breaks, you can see exactly which point in time each backup represents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;find ... -mtime +&quot;$KEEP_DAYS&quot; -delete&lt;/code&gt;&lt;/strong&gt; — the line most people forget. Without this, your backup folder grows forever. &lt;code&gt;-mtime +7&lt;/code&gt; means &quot;modified more than 7 days ago.&quot; Adjust &lt;code&gt;KEEP_DAYS&lt;/code&gt; based on how much disk you have.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stop-hardcoding-passwords&quot;&gt;Stop Hardcoding Passwords&lt;/h3&gt;
&lt;p&gt;The script above works, but &lt;code&gt;DB_PASS=&quot;your_password&quot;&lt;/code&gt; sitting in a plain text file is a liability. In production, use a &lt;code&gt;~/.my.cnf&lt;/code&gt; file instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;[client]
user=root
password=yourpassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then &lt;code&gt;chmod 600 ~/.my.cnf&lt;/code&gt; and remove the &lt;code&gt;-u&lt;/code&gt; and &lt;code&gt;-p&lt;/code&gt; flags from the &lt;code&gt;mysqldump&lt;/code&gt; command entirely. MySQL reads credentials from that file automatically. Nobody sees the password in &lt;code&gt;ps aux&lt;/code&gt; output, nobody finds it in your bash history.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;schedule-it&quot;&gt;Schedule It&lt;/h3&gt;
&lt;p&gt;Open &lt;code&gt;crontab -e&lt;/code&gt; and add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0 3 * * * /home/user/mysql-backup.sh &amp;gt;&amp;gt; /var/log/mysql-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That runs the backup every night at 3 AM and logs the output. If you want it every 6 hours instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;0 */6 * * * /home/user/mysql-backup.sh &amp;gt;&amp;gt; /var/log/mysql-backup.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you're not comfortable with cron syntax, I built a &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/tools/cron-job-builder.html&quot;&gt;free cron job builder&lt;/a&gt; that generates the line for you — click the schedule you want, copy the output.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;variations-i-actually-use&quot;&gt;Variations I Actually Use&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Back up ALL databases on the server:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mysqldump --all-databases | gzip &amp;gt; &quot;${BACKUP_DIR}/all_dbs_${DATE}.sql.gz&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Copy the backup to a remote server (so it survives a full disk failure):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;scp &quot;${BACKUP_DIR}/${FILENAME}&quot; backupuser@remote-server:/backups/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Send yourself an email when the backup runs (or fails):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pair this with my &lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/bash-send-email-alert.html&quot;&gt;email alert script&lt;/a&gt; and you'll get a notification either way.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-part-nobody-mentions&quot;&gt;The Part Nobody Mentions&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Test your restores.&lt;/strong&gt; A backup you've never restored is a backup that might not work. Once a month, spin up a test database and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gunzip &amp;lt; your_backup.sql.gz | mysql -u root -p test_database
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that command works, your backups are real. If it doesn't, you've been collecting dead files.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The full script with the line-by-line breakdown, the &lt;code&gt;~/.my.cnf&lt;/code&gt; setup walkthrough, and three more variations is at:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://bashsnippets.xyz/snippets/mysql-database-backup.html&quot;&gt;bashsnippets.xyz/snippets/mysql-database-backup.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you're running a database on a VPS without automated backups, take 90 seconds and set this up today. Future you will be grateful.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18480/i-lost-a-clients-database-on-a-5-vps-heres-the-12-line-script-that-would-have-saved-it</guid>
<pubDate>Wed, 20 May 2026 17:42:54 +0000</pubDate>
</item>
<item>
<title>CAP Theorem Explained: What I Learned Setting Up MySQL Replication</title>
<link>https://coderlegion.com/17423/cap-theorem-explained-what-i-learned-setting-up-mysql-replication</link>
<description>&lt;hr&gt;
&lt;p&gt;title: CAP Theorem Explained: What I Learned Setting Up MySQL Replication&lt;br&gt;
published: true&lt;/p&gt;
&lt;h3 id=&quot;tags-systemdesign-mysql-distributedsystems-beginners&quot;&gt;tags: systemdesign, mysql, distributedsystems, beginners&lt;/h3&gt;
&lt;p&gt;I recently started my journey into system design and data-intensive applications. This experience has completely changed how I look at software — especially around the hard tradeoffs of consistency, availability, and what happens when things go wrong at scale.&lt;/p&gt;
&lt;p&gt;Let me start with a scenario that surprised me when I first encountered it:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Imagine you just inserted a record in your app. A millisecond later, a read on a different server returns the old data. How? Welcome to distributed systems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-cap-theorem-&quot;&gt;What is CAP Theorem?&lt;/h3&gt;
&lt;p&gt;CAP stands for &lt;strong&gt;Consistency&lt;/strong&gt;, &lt;strong&gt;Availability&lt;/strong&gt;, and &lt;strong&gt;Partition Tolerance&lt;/strong&gt;. The theorem states that a distributed system can only guarantee 2 out of these 3 properties at any given time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistency&lt;/strong&gt; — Every read returns the most recent write, or an error. All nodes in the system see the same data at the same time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Availability&lt;/strong&gt; — Every request gets a response (not an error), even if some nodes are down. The system is always up.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Partition Tolerance&lt;/strong&gt; — The system keeps running even when network communication between nodes is lost or delayed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-choice-cp-vs-ap&quot;&gt;The Real Choice: CP vs AP&lt;/h3&gt;
&lt;p&gt;Here is the most important thing to understand: &lt;strong&gt;you cannot actually drop Partition Tolerance.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Network partitions are not a design choice — they are a reality. Hardware fails, packets drop, data centers lose connectivity. Any system that runs across multiple machines will eventually face a partition.&lt;/p&gt;
&lt;p&gt;So the real decision every distributed system makes is: &lt;em&gt;when a partition happens, do you stay consistent or stay available?&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-analogy-the-bank-branch&quot;&gt;A Simple Analogy: The Bank Branch&lt;/h3&gt;
&lt;p&gt;Imagine a bank with its HQ in New York and a branch in Boston. The branch always calls HQ before confirming any transaction.&lt;/p&gt;
&lt;p&gt;A customer in Boston deposits $500. Right at that moment, the network between Boston and New York goes down.&lt;/p&gt;
&lt;p&gt;The Boston branch now has two options:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1 — CP (Consistency + Partition Tolerance):&lt;/strong&gt;&lt;br&gt;
The branch refuses to process any transactions until it can reach HQ. The data stays consistent, but the service is unavailable during the outage. If you walk up to the counter, you get turned away.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2 — AP (Availability + Partition Tolerance):&lt;/strong&gt;&lt;br&gt;
The branch processes the deposit locally and syncs with HQ once the network recovers. The service stays available, but if someone checks the account balance at the New York branch before the sync, they see the old amount — temporarily inconsistent data.&lt;/p&gt;
&lt;p&gt;Neither option is wrong. The right choice depends entirely on what your application can tolerate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;hands-on-mysql-replication-as-an-ap-system&quot;&gt;Hands-On: MySQL Replication as an AP System&lt;/h3&gt;
&lt;p&gt;To make this concrete, I built a small master-slave replication setup using Docker to see these tradeoffs in action.&lt;/p&gt;
&lt;p&gt;You can find the full setup in &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/g3x-gauransh/MySQL--Master-slave-replication&quot;&gt;this repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One MySQL container acts as the &lt;strong&gt;master&lt;/strong&gt; — it accepts all writes&lt;/li&gt;
&lt;li&gt;A second container acts as the &lt;strong&gt;slave&lt;/strong&gt; — it monitors the master and replays every change&lt;/li&gt;
&lt;li&gt;The master records every write to a &lt;strong&gt;binary log (binlog)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The slave runs two threads: an &lt;strong&gt;IO thread&lt;/strong&gt; that pulls the binlog from the master, and a &lt;strong&gt;SQL thread&lt;/strong&gt; that replays those events locally&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;What I observed:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I inserted rows on the master, they showed up on the slave within milliseconds under normal conditions. But when I inserted 10,000 rows rapidly, I could watch the &lt;code&gt;Seconds_Behind_Master&lt;/code&gt; value in &lt;code&gt;SHOW SLAVE STATUS&lt;/code&gt; grow and then shrink back to zero as the slave caught up.&lt;/p&gt;
&lt;p&gt;That gap — &lt;strong&gt;replication lag&lt;/strong&gt; — is the tradeoff in action. During those seconds, a read on the slave returns stale data. The slave is always available to serve reads, but it cannot guarantee it has the latest write.&lt;/p&gt;
&lt;p&gt;This makes the setup an &lt;strong&gt;AP system&lt;/strong&gt;: Available and Partition Tolerant, but not strongly Consistent.&lt;/p&gt;
&lt;p&gt;If you wanted CP behaviour, you would switch to synchronous replication — the master waits for the slave to confirm every write before acknowledging it to the client. You get consistency, but every write now pays the latency cost of that round-trip.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-to-choose-cp-vs-ap&quot;&gt;When to Choose CP vs AP&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Prefer CP when a wrong answer is worse than no answer:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bank balances and financial transactions&lt;/li&gt;
&lt;li&gt;Seat reservation systems (cinema, flights)&lt;/li&gt;
&lt;li&gt;Distributed locks and coordination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Prefer AP when brief staleness is acceptable:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Social media feeds&lt;/li&gt;
&lt;li&gt;Product catalogs and shopping carts&lt;/li&gt;
&lt;li&gt;DNS resolution&lt;/li&gt;
&lt;li&gt;Caching layers&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;beyond-cap-consistency-is-a-spectrum&quot;&gt;Beyond CAP: Consistency is a Spectrum&lt;/h3&gt;
&lt;p&gt;CAP theorem is a useful mental model, but it is a simplification. In practice, consistency is not binary — there is a whole spectrum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Linearizability&lt;/strong&gt; — the strongest guarantee; reads always reflect the latest write&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sequential consistency&lt;/strong&gt; — operations appear in the same order across all nodes, but may lag&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt; — all nodes will converge to the same value, given enough time (this is what MySQL async replication gives you)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to go deeper, &lt;em&gt;Designing Data-Intensive Applications&lt;/em&gt; by Martin Kleppmann covers replication in Chapter 5 and consistency models in Chapter 9. It is genuinely the best resource I have found on this topic.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;CAP is not a theorem you apply once and move on — it is a lens for thinking about tradeoffs. Every distributed system you design makes a CAP choice, whether you make it explicitly or not. Understanding that choice, and being able to justify it, is what separates a system that works in production from one that only works in theory.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17423/cap-theorem-explained-what-i-learned-setting-up-mysql-replication</guid>
<pubDate>Mon, 11 May 2026 11:37:29 +0000</pubDate>
</item>
<item>
<title>From Stream to Database: Processing Market Data with Spring Boot, Redis, and Flyway</title>
<link>https://coderlegion.com/17111/from-stream-to-database-processing-market-data-with-spring-boot-redis-and-flyway</link>
<description>&lt;p&gt;Hey folks!&lt;/p&gt;
&lt;p&gt;Continuing the &lt;strong&gt;My Broker B3&lt;/strong&gt; series, today we build &lt;code&gt;trading-broker-asset&lt;/code&gt; — the Java service that &lt;strong&gt;consumes the quotes published to Kafka by trading-broker-market-data&lt;/strong&gt; and transforms them into structured data in MySQL, with high-performance caching in Redis.&lt;/p&gt;
&lt;p&gt;If the Python service is the collector, this one is the processor.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-this-service-s-role-in-the-ecosystem&quot;&gt;️ This Service's Role in the Ecosystem&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[trading-broker-market-data]
         │
         │ Kafka: trading-assets-market-data-v1
         ▼
[trading-broker-asset]
         │
    ┌────┴────┐
    ▼         ▼
  MySQL     Redis
(assets)  market:price:{TICKER}
              │
    [b3-matching-engine]   (reads from here)
    [trading-broker-order] (validates ticker from here)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two critical consumers depend on the data this service maintains: the Matching Engine (for price decisions) and the Order API (to validate whether a ticker exists and is active).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;mvp-focus&quot;&gt;MVP Focus&lt;/h2&gt;
&lt;p&gt;In this phase, I prioritized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Robust Kafka consumer with error handling&lt;/li&gt;
&lt;li&gt;Asset upsert: creates on first event, updates on subsequent ones&lt;/li&gt;
&lt;li&gt;Redis cache with 10-minute TTL&lt;/li&gt;
&lt;li&gt;REST API for catalog queries&lt;/li&gt;
&lt;li&gt;Status filter: only &lt;code&gt;ACTIVE&lt;/code&gt; assets are returned&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-tech-stack&quot;&gt;️ Tech Stack&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technology &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Usage&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Java 21&lt;/strong&gt; + &lt;strong&gt;Spring Boot 3.5.11&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Service core&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Kafka&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Quote event consumption&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;MySQL&lt;/strong&gt; + &lt;strong&gt;Flyway&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Versioned asset catalog&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Spring Data Redis&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Real-time price cache&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;SpringDoc OpenAPI&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Swagger UI documentation&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-implementation-pillars&quot;&gt;️ Implementation Pillars&lt;/h2&gt;
&lt;h3 id=&quot;1-the-database-schema-flyway-&quot;&gt;1. The Database Schema (Flyway)&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE assets (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    ticker VARCHAR(20) NOT NULL UNIQUE,
    name VARCHAR(200),
    current_price DECIMAL(19, 4),
    last_update DATETIME,
    status ENUM('ACTIVE', 'INACTIVE') NOT NULL DEFAULT 'ACTIVE',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Flyway?&lt;/strong&gt; Schema versioning ensures that any environment (local, CI, production) has exactly the same database structure, without surprises.&lt;/p&gt;
&lt;h3 id=&quot;2-the-kafka-consumer&quot;&gt;2. The Kafka Consumer&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@KafkaListener(
        topics = &quot;trading-assets-market-data-v1&quot;,
        groupId = &quot;trading-broker-asset&quot;
)
public void consume(AssetMarketDataDTO dto) {
    log.info(&quot;Market data received for ticker: {}&quot;, dto.getTicker());
    try {
        assetService.updateAsset(dto);
    } catch (Exception e) {
        log.error(&quot;Failed to process market data for ticker {}: {}&quot;,
                dto.getTicker(), e.getMessage(), e);
        // Rethrow so Kafka can apply retry policy instead of silently discarding
        throw e;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why rethrow the exception?&lt;/strong&gt; If processing fails silently, the offset is committed and the message is discarded — the asset is never updated. Rethrowing lets Kafka apply its retry policy.&lt;/p&gt;
&lt;h3 id=&quot;3-the-asset-upsert&quot;&gt;3. The Asset Upsert&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Transactional
public void updateAsset(AssetMarketDataDTO dto) {
    Asset asset = assetRepository.findByTicker(dto.getTicker())
            .orElse(Asset.builder()
                    .ticker(dto.getTicker())
                    .status(AssetStatus.ACTIVE)
                    .build());

    asset.setName(dto.getName());
    asset.setCurrentPrice(dto.getPrice());
    // Uses real market timestamp from event — not LocalDateTime.now()
    asset.setLastUpdate(dto.getUpdatedAt() != null ? dto.getUpdatedAt() : LocalDateTime.now());

    assetRepository.save(asset);

    // Update Redis cache after persisting
    marketPriceCacheService.updatePrice(dto.getTicker(), dto.getPrice());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important decision:&lt;/strong&gt; we use the real &lt;code&gt;updatedAt&lt;/code&gt; from the Kafka event, not &lt;code&gt;LocalDateTime.now()&lt;/code&gt;. This ensures &lt;code&gt;lastUpdate&lt;/code&gt; in the database reflects when the price was generated by the market, not when it was processed by the service.&lt;/p&gt;
&lt;h3 id=&quot;4-redis-cache-with-ttl&quot;&gt;4. Redis Cache with TTL&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static final Duration CACHE_TTL = Duration.ofMinutes(10);

public void updatePrice(String ticker, BigDecimal price) {
    String key = &quot;market:price:&quot; + ticker;
    // TTL of 10 minutes — prevents stale prices for inactive or delisted assets
    redisTemplate.opsForValue().set(key, price.toString(), CACHE_TTL);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why TTL?&lt;/strong&gt; If an asset is deactivated or delisted, its price will expire from Redis in 10 minutes. Without TTL, the Matching Engine could use prices for assets that no longer exist.&lt;/p&gt;
&lt;h3 id=&quot;5-status-filter-in-queries&quot;&gt;5. Status Filter in Queries&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public List&amp;lt;AssetDTO&amp;gt; findAllActive() {
    // Query directly by status — not findAll() + filter in memory
    return assetRepository.findAllByStatus(AssetStatus.ACTIVE).stream()
            .map(AssetDTO::fromEntity)
            .toList();
}

public Optional&amp;lt;AssetDTO&amp;gt; findByTicker(String ticker) {
    // Only returns ACTIVE assets — inactive returns 404
    return assetRepository.findByTickerAndStatus(ticker.toUpperCase(), AssetStatus.ACTIVE)
            .map(AssetDTO::fromEntity);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When &lt;code&gt;trading-broker-order&lt;/code&gt; validates a ticker before creating an order, it calls &lt;code&gt;GET /api/v1/assets/{ticker}&lt;/code&gt;. If the asset is inactive, it gets a 404 and the order is rejected. Correct and predictable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;rest-api&quot;&gt;REST API&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Method &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Endpoint &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Description&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/assets&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;List all active assets&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;GET &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;code&gt;/api/v1/assets/{ticker}&lt;/code&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Find asset by ticker (404 if inactive)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt; &lt;strong&gt;Swagger UI&lt;/strong&gt;: &lt;code&gt;http://localhost:8083/swagger-ui.html&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;-validating-the-execution&quot;&gt;✅ Validating the Execution&lt;/h2&gt;
&lt;p&gt;With the application running and &lt;code&gt;trading-broker-market-data&lt;/code&gt; publishing to Kafka:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ Consumer connected to the &lt;code&gt;trading-assets-market-data-v1&lt;/code&gt; topic&lt;/li&gt;
&lt;li&gt;✅ Assets being created/updated in MySQL in real time&lt;/li&gt;
&lt;li&gt;✅ Redis being updated with TTL after each event&lt;/li&gt;
&lt;li&gt;✅ Logs showing &lt;code&gt;Updating ticker PETR4 with price R$ 47.37&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-s-next-&quot;&gt;What's Next?&lt;/h2&gt;
&lt;p&gt;With the asset catalog working, the next step is &lt;code&gt;b3-market-sync-api&lt;/code&gt; — which synchronizes prices directly to the B3's Redis — followed by &lt;code&gt;b3-matching-engine-api&lt;/code&gt;, which uses those prices to execute orders.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;about-the-series&quot;&gt;About the Series&lt;/h2&gt;
&lt;p&gt;⬅️ Previous Post: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/tooling-tips-visualizing-your-data-in-mongodb-and-kafka-1k1p&quot;&gt;Tooling Tips: MongoDB and Kafka&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;➡️ Next Post: Syncing the Real Market: Consuming Brapi and Feeding Redis with Spring Boot&lt;/p&gt;
&lt;p&gt; Series Index: &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/rvneto/series-roadmap-building-a-stock-brokerage-simulator-with-microservices-kgh&quot;&gt;Series Roadmap&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/roberto-de-vargas/&quot;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rvneto/trading-broker-asset&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17111/from-stream-to-database-processing-market-data-with-spring-boot-redis-and-flyway</guid>
<pubDate>Fri, 08 May 2026 20:51:06 +0000</pubDate>
</item>
<item>
<title>Database Optimization Strategies That Cut Query Execution Time by 60%: A Practical Guide</title>
<link>https://coderlegion.com/14921/database-optimization-strategies-that-cut-query-execution-time-by-60-a-practical-guide</link>
<description>&lt;p&gt;If your web application is slow, the database is almost certainly the bottleneck. Not the frontend framework. Not the web server. Not the network. The database.&lt;/p&gt;
&lt;p&gt;I have spent nine years building backend systems across seven companies, and in every single engagement, database performance has been the most impactful area of optimization. At VacancySoft, where I currently serve as a Full Stack Engineer, I reduced query execution times by 60% across our core platform. At Univelcity, I achieved a 45% reduction in API response times, with database optimization accounting for the majority of those gains. At Lordwin Group, I optimised a real-time gaming backend to sustain 3,000+ concurrent users with sub-100ms query responses.&lt;/p&gt;
&lt;p&gt;This article is the practical guide I wish I had when I started. It covers the exact strategies, tools, and techniques I use to diagnose and fix database performance problems in production systems running PostgreSQL and MySQL.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-1-query-analysis-and-profiling-know-before-you-optimize&quot;&gt;Strategy 1: Query Analysis and Profiling — Know Before You Optimize&lt;/h3&gt;
&lt;p&gt;The biggest mistake engineers make with database optimization is guessing. They add indexes randomly, rewrite queries based on intuition, or throw caching at the problem without understanding what is actually slow.&lt;/p&gt;
&lt;p&gt;I start every optimization effort the same way: &lt;strong&gt;profiling&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;enable-slow-query-logging&quot;&gt;Enable Slow Query Logging&lt;/h4&gt;
&lt;p&gt;In PostgreSQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- postgresql.conf
log_min_duration_statement = 200  -- Log queries taking &amp;gt; 200ms
log_statement = 'none'            -- Don't log all queries, just slow ones
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In MySQL:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;  -- 200ms threshold
SET GLOBAL log_queries_not_using_indexes = 'ON';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At VacancySoft, I established a weekly ritual of reviewing the slow query log. This alone surfaced dozens of optimization opportunities that no amount of code review would have caught. Queries that looked clean in the ORM were generating catastrophic execution plans.&lt;/p&gt;
&lt;h4 id=&quot;use-explain-analyze-religiously&quot;&gt;Use EXPLAIN ANALYZE Religiously&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;EXPLAIN&lt;/code&gt; shows you the query plan. &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; actually executes the query and shows real timing data. The difference matters.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;EXPLAIN ANALYZE
SELECT v.id, v.title, o.name AS organisation_name, s.name AS sector_name
FROM vacancies v
JOIN organisations o ON o.id = v.organisation_id
JOIN sectors s ON s.id = v.sector_id
WHERE v.status = 'active'
  AND v.created_at &amp;gt;= NOW() - INTERVAL '30 days'
ORDER BY v.created_at DESC
LIMIT 20;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before optimization, this query at VacancySoft returned an &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; output like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sort  (cost=15234.56..15234.61 rows=20 width=128) (actual time=1842.331..1842.345 rows=20 loops=1)
  Sort Key: v.created_at DESC
  -&amp;gt;  Hash Join  (cost=8234.12..15234.43 rows=20 width=128) (actual time=1201.442..1841.892 rows=24531 loops=1)
        -&amp;gt;  Seq Scan on vacancies v  (cost=0.00..6842.00 rows=24531 width=96) (actual time=0.021..892.134 rows=24531 loops=1)
              Filter: ((status = 'active') AND (created_at &amp;gt;= ...))
              Rows Removed by Filter: 187234
Planning Time: 0.892 ms
Execution Time: 1843.012 ms
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two immediate red flags: a &lt;strong&gt;sequential scan&lt;/strong&gt; on the vacancies table scanning over 200,000 rows and filtering out 187,000 of them, and a &lt;strong&gt;hash join&lt;/strong&gt; that is materialising far more rows than the final result needs.&lt;/p&gt;
&lt;p&gt;This is the kind of visibility that transforms optimization from guesswork into engineering. Olamilekan Lamidi's first rule of database optimization: never optimise a query you have not profiled.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-2-indexing-strategies-the-highest-impact-change&quot;&gt;Strategy 2: Indexing Strategies — The Highest-Impact Change&lt;/h3&gt;
&lt;p&gt;Indexing is the single most impactful optimization technique in my toolkit. A well-placed index can turn a 2-second query into a 5-millisecond query. But indexes are not free — they consume storage, slow down writes, and can mislead the query planner if poorly designed.&lt;/p&gt;
&lt;h4 id=&quot;composite-indexes&quot;&gt;Composite Indexes&lt;/h4&gt;
&lt;p&gt;Order matters in composite indexes. The index &lt;code&gt;(status, created_at)&lt;/code&gt; is useful for queries that filter on &lt;code&gt;status&lt;/code&gt; and sort or filter on &lt;code&gt;created_at&lt;/code&gt;. It is useless for queries that filter only on &lt;code&gt;created_at&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the vacancy search query above, I created:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_vacancies_status_created_at
ON vacancies (status, created_at DESC);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This single index eliminated the sequential scan. The query planner now uses an index scan that reads only the relevant rows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Index Scan using idx_vacancies_status_created_at on vacancies v
  (cost=0.42..234.56 rows=20 width=96) (actual time=0.089..2.341 rows=20 loops=1)
  Index Cond: ((status = 'active') AND (created_at &amp;gt;= ...))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Execution time: from &lt;strong&gt;1,843ms to 12ms&lt;/strong&gt;. A 99.3% reduction from a single composite index.&lt;/p&gt;
&lt;h4 id=&quot;partial-indexes&quot;&gt;Partial Indexes&lt;/h4&gt;
&lt;p&gt;At VacancySoft, the majority of our queries filter on &lt;code&gt;status = 'active'&lt;/code&gt;. Only about 15% of vacancies are active at any given time. A partial index dramatically reduces the index size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_vacancies_active_created_at
ON vacancies (created_at DESC)
WHERE status = 'active';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This index is roughly 85% smaller than the full composite index, which means it fits in memory more easily and produces faster index scans.&lt;/p&gt;
&lt;h4 id=&quot;covering-indexes-index-only-scans-&quot;&gt;Covering Indexes (Index-Only Scans)&lt;/h4&gt;
&lt;p&gt;A covering index includes all columns that a query needs, allowing PostgreSQL to serve the query entirely from the index without touching the heap (the actual table data). This eliminates random I/O, which is the most expensive operation on traditional storage.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE INDEX idx_vacancies_covering
ON vacancies (status, created_at DESC)
INCLUDE (id, title, organisation_id, sector_id)
WHERE status = 'active';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this covering index, PostgreSQL executes an &lt;strong&gt;index-only scan&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Index Only Scan using idx_vacancies_covering on vacancies v
  (cost=0.42..45.23 rows=20 width=96) (actual time=0.067..0.412 rows=20 loops=1)
  Heap Fetches: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Heap Fetches: 0&lt;/code&gt; is the goal. The database never touches the table at all.&lt;/p&gt;
&lt;h4 id=&quot;my-indexing-methodology&quot;&gt;My Indexing Methodology&lt;/h4&gt;
&lt;p&gt;I follow a systematic process:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify the top 20 slowest queries&lt;/strong&gt; from the slow query log.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run EXPLAIN ANALYZE&lt;/strong&gt; on each one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Look for sequential scans&lt;/strong&gt; on tables with more than 10,000 rows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check for sort operations&lt;/strong&gt; that are not backed by an index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create targeted indexes&lt;/strong&gt; and verify the plan changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor write performance&lt;/strong&gt; to ensure indexes are not degrading INSERT/UPDATE speed beyond acceptable thresholds.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;At VacancySoft, this methodology across 15+ modules produced the 60% aggregate reduction in query execution time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-3-query-refactoring-fixing-what-the-orm-gets-wrong&quot;&gt;Strategy 3: Query Refactoring — Fixing What the ORM Gets Wrong&lt;/h3&gt;
&lt;p&gt;ORMs are excellent for productivity but frequently generate suboptimal SQL. The N+1 query problem is the most well-known example, but I encounter more subtle issues regularly.&lt;/p&gt;
&lt;h4 id=&quot;eliminating-n-1-queries&quot;&gt;Eliminating N+1 Queries&lt;/h4&gt;
&lt;p&gt;In Laravel (which I used extensively at 2am Tech, Lordwin Group, and Viaduct):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;// Bad: N+1 problem — 1 query for organisations + N queries for vacancies
$organisations = Organisation::all();
foreach ($organisations as $org) {
    echo $org-&amp;gt;vacancies-&amp;gt;count(); // Triggers a query per organisation
}

// Good: Eager loading — 2 queries total
$organisations = Organisation::with('vacancies')-&amp;gt;get();

// Better: When you only need the count
$organisations = Organisation::withCount('vacancies')-&amp;gt;get();
foreach ($organisations as $org) {
    echo $org-&amp;gt;vacancies_count; // No additional queries
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At 2am Tech, the Addio financial platform was suffering from response times exceeding 3 seconds on the organisation dashboard. Profiling revealed over 150 queries per page load, almost all of them N+1 queries from Eloquent relationships. I refactored the critical paths to use eager loading and &lt;code&gt;withCount&lt;/code&gt;, reducing the query count to 8 and the response time to 400ms.&lt;/p&gt;
&lt;h4 id=&quot;replacing-subqueries-with-joins-and-ctes&quot;&gt;Replacing Subqueries with JOINs and CTEs&lt;/h4&gt;
&lt;p&gt;ORMs often generate correlated subqueries where a JOIN or CTE would be far more efficient:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- ORM-generated: correlated subquery (executes once per row)
SELECT o.id, o.name,
  (SELECT COUNT(*) FROM vacancies v WHERE v.organisation_id = o.id AND v.status = 'active')
    AS active_vacancy_count
FROM organisations o
WHERE o.sector_id = 5;

-- Refactored: single pass with JOIN
SELECT o.id, o.name, COUNT(v.id) AS active_vacancy_count
FROM organisations o
LEFT JOIN vacancies v ON v.organisation_id = o.id AND v.status = 'active'
WHERE o.sector_id = 5
GROUP BY o.id, o.name;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;window-functions-for-complex-analytics&quot;&gt;Window Functions for Complex Analytics&lt;/h4&gt;
&lt;p&gt;At VacancySoft, we needed to show month-over-month vacancy trends per sector. The initial implementation used multiple queries with application-level aggregation. I replaced it with a single query using window functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;WITH monthly_counts AS (
  SELECT
    s.name AS sector_name,
    DATE_TRUNC('month', v.created_at) AS month,
    COUNT(*) AS vacancy_count
  FROM vacancies v
  JOIN sectors s ON s.id = v.sector_id
  WHERE v.created_at &amp;gt;= NOW() - INTERVAL '12 months'
  GROUP BY s.name, DATE_TRUNC('month', v.created_at)
)
SELECT
  sector_name,
  month,
  vacancy_count,
  LAG(vacancy_count) OVER (PARTITION BY sector_name ORDER BY month) AS prev_month_count,
  ROUND(
    (vacancy_count - LAG(vacancy_count) OVER (PARTITION BY sector_name ORDER BY month))::numeric
    / NULLIF(LAG(vacancy_count) OVER (PARTITION BY sector_name ORDER BY month), 0) * 100,
    1
  ) AS month_over_month_pct
FROM monthly_counts
ORDER BY sector_name, month;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This single query replaced five application-level queries and a significant amount of JavaScript aggregation logic. Response time for the trends endpoint dropped from 2.1 seconds to 280ms.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-4-schema-redesign-for-performance&quot;&gt;Strategy 4: Schema Redesign for Performance&lt;/h3&gt;
&lt;p&gt;Sometimes the problem is not the query — it is the schema. Normalization is taught as gospel in database courses, but production systems often require strategic denormalization.&lt;/p&gt;
&lt;h4 id=&quot;materialized-views&quot;&gt;Materialized Views&lt;/h4&gt;
&lt;p&gt;At VacancySoft, our analytics dashboard required aggregating data across five tables. Even with optimized queries and proper indexes, the aggregation was too slow for real-time display. I introduced materialized views:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE MATERIALIZED VIEW mv_sector_analytics AS
SELECT
  s.id AS sector_id,
  s.name AS sector_name,
  COUNT(v.id) AS total_vacancies,
  COUNT(v.id) FILTER (WHERE v.status = 'active') AS active_vacancies,
  COUNT(DISTINCT v.organisation_id) AS hiring_organisations,
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY v.salary_max) AS median_salary,
  DATE_TRUNC('day', NOW()) AS snapshot_date
FROM sectors s
LEFT JOIN vacancies v ON v.sector_id = s.id
  AND v.created_at &amp;gt;= NOW() - INTERVAL '90 days'
GROUP BY s.id, s.name;

CREATE UNIQUE INDEX ON mv_sector_analytics (sector_id);

-- Refresh concurrently (no lock on reads)
REFRESH MATERIALIZED VIEW CONCURRENTLY mv_sector_analytics;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I set up a cron job to refresh this view every 15 minutes. The dashboard query that previously took 3.8 seconds now completes in under 10ms because it reads from the pre-computed view.&lt;/p&gt;
&lt;h4 id=&quot;strategic-denormalization&quot;&gt;Strategic Denormalization&lt;/h4&gt;
&lt;p&gt;For the organisation profile pages, rather than joining five tables on every request, I added a &lt;code&gt;cached_stats&lt;/code&gt; JSONB column on the organisations table:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ALTER TABLE organisations ADD COLUMN cached_stats JSONB DEFAULT '{}';

-- Updated by a background job every hour
UPDATE organisations SET cached_stats = jsonb_build_object(
  'active_vacancies', (SELECT COUNT(*) FROM vacancies WHERE organisation_id = organisations.id AND status = 'active'),
  'total_vacancies_90d', (SELECT COUNT(*) FROM vacancies WHERE organisation_id = organisations.id AND created_at &amp;gt;= NOW() - INTERVAL '90 days'),
  'avg_time_to_fill', (SELECT AVG(EXTRACT(EPOCH FROM (filled_at - created_at)) / 86400) FROM vacancies WHERE organisation_id = organisations.id AND filled_at IS NOT NULL),
  'updated_at', NOW()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a tradeoff: the data can be up to one hour stale, and we have the overhead of the background update job. But for this use case, slight staleness is acceptable, and the performance gain — from 450ms to 8ms per profile page — is transformative.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-5-connection-pooling-and-read-replicas&quot;&gt;Strategy 5: Connection Pooling and Read Replicas&lt;/h3&gt;
&lt;p&gt;At scale, query optimization alone is not enough. You also need to manage how connections to the database are handled.&lt;/p&gt;
&lt;h4 id=&quot;connection-pooling-with-pgbouncer&quot;&gt;Connection Pooling with PgBouncer&lt;/h4&gt;
&lt;p&gt;PostgreSQL creates a new process for each connection, which is expensive. At VacancySoft, I deployed PgBouncer in front of PostgreSQL in transaction pooling mode:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ini&quot;&gt;; pgbouncer.ini
[databases]
vacancysoft = host=127.0.0.1 port=5432 dbname=vacancysoft

[pgbouncer]
listen_port = 6432
pool_mode = transaction
max_client_conn = 500
default_pool_size = 25
reserve_pool_size = 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This allows 500 application connections to share 25 database connections. Without pooling, our application would regularly hit PostgreSQL's connection limit during traffic spikes, causing cascading failures.&lt;/p&gt;
&lt;h4 id=&quot;read-replicas&quot;&gt;Read Replicas&lt;/h4&gt;
&lt;p&gt;For read-heavy workloads (which describe most web applications), I split reads and writes across primary and replica databases:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Database configuration with read/write splitting
const knex = require('knex');

const primary = knex({
  client: 'pg',
  connection: process.env.DATABASE_PRIMARY_URL,
});

const replica = knex({
  client: 'pg',
  connection: process.env.DATABASE_REPLICA_URL,
});

// Service layer decides which connection to use
class VacancyService {
  async search(filters) {
    return replica('vacancies')
      .where(filters)
      .orderBy('created_at', 'desc');
  }

  async create(data) {
    return primary('vacancies')
      .insert(data)
      .returning('*');
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At VacancySoft, this split reduced load on the primary database by approximately 70%, since the vast majority of our traffic is read operations (search, analytics, reporting).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;strategy-6-caching-layers&quot;&gt;Strategy 6: Caching Layers&lt;/h3&gt;
&lt;p&gt;The fastest database query is the one you never make. Caching is the final layer of my optimization strategy.&lt;/p&gt;
&lt;h4 id=&quot;redis-for-hot-data&quot;&gt;Redis for Hot Data&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);

async function getSectorAnalytics(sectorId) {
  const cacheKey = `sector:${sectorId}:analytics`;
  const cached = await redis.get(cacheKey);

  if (cached) {
    return JSON.parse(cached);
  }

  const data = await db('mv_sector_analytics')
    .where('sector_id', sectorId)
    .first();

  await redis.setex(cacheKey, 900, JSON.stringify(data)); // 15-minute TTL

  return data;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;cache-invalidation-strategy&quot;&gt;Cache Invalidation Strategy&lt;/h4&gt;
&lt;p&gt;Cache invalidation is famously one of the two hard problems in computer science. My approach is pragmatic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;TTL-based expiration&lt;/strong&gt; for data that can tolerate staleness (analytics, aggregations).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event-driven invalidation&lt;/strong&gt; for data that must be fresh (user profiles, active listings). When a vacancy is created or updated, I publish an event that invalidates the relevant cache keys.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache warming&lt;/strong&gt; for predictable access patterns. At VacancySoft, I pre-warm the cache for the top 50 sectors and top 100 organisations every 15 minutes, so the first user to access these pages never experiences a cache miss.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// Event-driven cache invalidation
eventBus.on('vacancy:created', async (vacancy) =&amp;gt; {
  await redis.del(`sector:${vacancy.sector_id}:analytics`);
  await redis.del(`org:${vacancy.organisation_id}:stats`);
  await redis.del('homepage:featured_vacancies');
});
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;results-before-and-after&quot;&gt;Results: Before and After&lt;/h3&gt;
&lt;p&gt;Here is a summary of the measurable impact these strategies produced across my career, as implemented by Olamilekan Lamidi:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Company &lt;/th&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After &lt;/th&gt;&lt;th&gt;Improvement&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;VacancySoft &lt;/td&gt;&lt;td&gt;Average query execution time &lt;/td&gt;&lt;td&gt;850ms &lt;/td&gt;&lt;td&gt;340ms &lt;/td&gt;&lt;td&gt;60% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;VacancySoft &lt;/td&gt;&lt;td&gt;Dashboard load time &lt;/td&gt;&lt;td&gt;3.8s &lt;/td&gt;&lt;td&gt;0.4s &lt;/td&gt;&lt;td&gt;89% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;VacancySoft &lt;/td&gt;&lt;td&gt;p95 API response time &lt;/td&gt;&lt;td&gt;1.2s &lt;/td&gt;&lt;td&gt;180ms &lt;/td&gt;&lt;td&gt;85% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Univelcity &lt;/td&gt;&lt;td&gt;API response time (avg) &lt;/td&gt;&lt;td&gt;620ms &lt;/td&gt;&lt;td&gt;340ms &lt;/td&gt;&lt;td&gt;45% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Univelcity &lt;/td&gt;&lt;td&gt;Queries per page load &lt;/td&gt;&lt;td&gt;45 &lt;/td&gt;&lt;td&gt;8 &lt;/td&gt;&lt;td&gt;82% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Lordwin Group &lt;/td&gt;&lt;td&gt;Real-time query latency &lt;/td&gt;&lt;td&gt;220ms &lt;/td&gt;&lt;td&gt;35ms &lt;/td&gt;&lt;td&gt;84% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2am Tech &lt;/td&gt;&lt;td&gt;Organisation dashboard load &lt;/td&gt;&lt;td&gt;3.1s &lt;/td&gt;&lt;td&gt;400ms &lt;/td&gt;&lt;td&gt;87% reduction&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-takeaways&quot;&gt;Key Takeaways&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Profile before you optimize.&lt;/strong&gt; EXPLAIN ANALYZE and slow query logs should be your starting point, not your intuition.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composite and partial indexes are the highest-leverage optimization&lt;/strong&gt; in most applications. One well-placed index can eliminate 99% of query time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ORMs are productivity tools, not performance tools.&lt;/strong&gt; Review the SQL they generate, especially for complex queries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Materialized views and strategic denormalization&lt;/strong&gt; are not anti-patterns — they are essential tools for read-heavy workloads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Connection pooling is mandatory&lt;/strong&gt; once you are handling more than a few hundred concurrent connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching is the last layer&lt;/strong&gt;, not the first. Optimize the underlying queries before adding cache complexity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Database optimization is not a one-time project. It is an ongoing discipline. The queries that perform well today may become bottlenecks tomorrow as data grows and access patterns change. Build profiling and monitoring into your workflow, and treat database performance as a first-class engineering concern.&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/14921/database-optimization-strategies-that-cut-query-execution-time-by-60-a-practical-guide</guid>
<pubDate>Wed, 15 Apr 2026 23:39:07 +0000</pubDate>
</item>
<item>
<title>My SQL Mistakes Almost Broke Me… But I Learned a Lot</title>
<link>https://coderlegion.com/6542/my-sql-mistakes-almost-broke-me-but-i-learned-a-lot</link>
<description>&lt;h4 id=&quot;introduction&quot;&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Let me be honest... when I first started working with SQL, I made &lt;em&gt;a LOT&lt;/em&gt; of mistakes.  From messing up queries to accidentally deleting test data, it was exhausting. But each error taught me something valuable about coding, debugging, and patience.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;my-biggest-sql-oops-moments&quot;&gt;&lt;strong&gt;My Biggest SQL Oops Moments&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;1️⃣ &lt;strong&gt;The Missing WHERE Clause&lt;/strong&gt;&lt;br&gt;
I once ran a &lt;code&gt;DELETE&lt;/code&gt; query without a &lt;code&gt;WHERE&lt;/code&gt; condition… and yes, I wiped out an entire table. Lesson: &lt;strong&gt;Always double-check before hitting execute.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2️⃣ &lt;strong&gt;Incorrect Joins&lt;/strong&gt;&lt;br&gt;
I joined two tables and got a result set that made no sense. After some digging, I realized I was joining on the wrong column. Lesson: &lt;strong&gt;Know your schema before writing joins.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3️⃣ &lt;strong&gt;Data Type Confusion&lt;/strong&gt;&lt;br&gt;
I tried comparing numbers stored as strings and got unexpected results. Lesson: &lt;strong&gt;Always know your data types.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;4️⃣ &lt;strong&gt;Installation Woes&lt;/strong&gt;&lt;br&gt;
At first, I was super excited to install MySQL manually in VS Code. But I hit one stubborn error after another. Even ChatGPT couldn’t save me! Lesson: &lt;strong&gt;Tech can be frustrating — patience is key.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-i-learned&quot;&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt; SQL mistakes aren’t failures .. they’re &lt;strong&gt;learning opportunities&lt;/strong&gt;.&lt;br&gt;
 Test your queries on small datasets first.&lt;br&gt;
 Keep backups or work in safe environments.&lt;br&gt;
 Document what works and what doesn’t ..it saves future headaches.&lt;br&gt;
 Tech journeys are full of errors, tears, and breakthroughs ..and that’s okay!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;advice-to-beginners&quot;&gt;&lt;strong&gt;Advice to Beginners&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you’re new to SQL or databases, know that &lt;strong&gt;everyone struggles at first&lt;/strong&gt;. Don’t give up. Mistakes are part of learning. Patience, persistence, and community support are your best friends.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;let-s-connect&quot;&gt;&lt;strong&gt;Let’s Connect&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;I’d love to hear your SQL horror stories or tips for avoiding mistakes! Share them with me on:&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/the_duchess_of_hackers&quot;&gt;dev.to&lt;/a&gt; • &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/gracequaye1&quot;&gt;GitHub&lt;/a&gt; • &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/grace-okailey-quaye&quot;&gt;LinkedIn&lt;/a&gt; • &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/DuchessCodes&quot;&gt;X/Twitter&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Signature&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;The Duchess of Hackers&lt;/em&gt; &lt;br&gt;
Full-Stack Developer | Digital Marketer&lt;/p&gt;
&lt;hr&gt;
</description>
<category>Database</category>
<guid isPermaLink="true">https://coderlegion.com/6542/my-sql-mistakes-almost-broke-me-but-i-learned-a-lot</guid>
<pubDate>Thu, 16 Oct 2025 21:45:41 +0000</pubDate>
</item>
<item>
<title>What is SQL? A Beginner's Complete Guide</title>
<link>https://coderlegion.com/3880/what-is-sql-a-beginners-complete-guide</link>
<description>&lt;h3 id=&quot;follow-me-for-more-content-&quot;&gt;Follow Me for More Content!&lt;/h3&gt;
&lt;p&gt;Before we dive into SQL, let's connect! Follow me on these platforms for more programming tutorials and tech insights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Abdelhakim-Baalla&quot;&gt;Abdelhakim-Baalla&lt;/a&gt; - Check out my projects and code&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/abdelhakimbaalla/&quot;&gt;abdelhakimbaalla&lt;/a&gt; - Professional updates and networking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Twitter (X):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/Abdelhakim99891&quot;&gt;@Abdelhakim99891&lt;/a&gt; - Quick tips and tech discussions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://abdelhakim-baalla.vercel.app/&quot;&gt;abdelhakim-baalla&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;introduction-1&quot;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;Have you ever wondered how websites store and retrieve information about users, products, or orders? Or how apps like Instagram know which photos to show you? The answer lies in databases, and the key to communicating with these databases is SQL.&lt;/p&gt;
&lt;p&gt;If you're completely new to programming or databases, don't worry! SQL is actually one of the most beginner-friendly programming languages you can learn. In this comprehensive guide, we'll explore what SQL is, why it's so important, and how you can start using it today.&lt;/p&gt;
&lt;p&gt;By the end of this post, you'll have a solid understanding of SQL fundamentals and be ready to start your database journey. Let's dive in!&lt;/p&gt;
&lt;h3 id=&quot;what-is-sql-&quot;&gt;What is SQL?&lt;/h3&gt;
&lt;p&gt;SQL stands for &lt;strong&gt;Structured Query Language&lt;/strong&gt; (pronounced &quot;S-Q-L&quot; or &quot;sequel&quot;). Think of it as a special language that allows you to communicate with databases. Just like you use English to talk to people, you use SQL to talk to databases.&lt;/p&gt;
&lt;h4 id=&quot;breaking-down-the-name&quot;&gt;Breaking Down the Name&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Structured:&lt;/strong&gt; SQL follows specific rules and formats&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query:&lt;/strong&gt; You ask questions (queries) to get information&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Language:&lt;/strong&gt; It's a programming language with its own syntax&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;simple-analogy&quot;&gt;Simple Analogy&lt;/h4&gt;
&lt;p&gt;Imagine a database as a massive digital filing cabinet with thousands of folders and documents. SQL is like having a super-efficient assistant who can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Find any document you need in seconds&lt;/li&gt;
&lt;li&gt;Organize documents in any order you want&lt;/li&gt;
&lt;li&gt;Add new documents to the right folders&lt;/li&gt;
&lt;li&gt;Update information in existing documents&lt;/li&gt;
&lt;li&gt;Remove documents you no longer need&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;why-is-sql-important-&quot;&gt;Why is SQL Important?&lt;/h3&gt;
&lt;h4 id=&quot;1-universal-language-for-databases&quot;&gt;1. &lt;strong&gt;Universal Language for Databases&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;SQL is the standard language for working with databases. Whether you're using MySQL, PostgreSQL, SQL Server, or Oracle, the basic SQL concepts remain the same.&lt;/p&gt;
&lt;h4 id=&quot;2-high-demand-in-the-job-market&quot;&gt;2. &lt;strong&gt;High Demand in the Job Market&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;According to recent surveys, SQL consistently ranks among the top 5 most in-demand programming skills. Jobs requiring SQL skills often offer competitive salaries.&lt;/p&gt;
&lt;h4 id=&quot;3-foundation-for-data-analysis&quot;&gt;3. &lt;strong&gt;Foundation for Data Analysis&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;If you're interested in data science, business analytics, or any field involving data, SQL is your starting point.&lt;/p&gt;
&lt;h4 id=&quot;4-easy-to-learn&quot;&gt;4. &lt;strong&gt;Easy to Learn&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Unlike complex programming languages, SQL uses English-like words and phrases, making it incredibly beginner-friendly.&lt;/p&gt;
&lt;h3 id=&quot;how-databases-work-the-basics-&quot;&gt;How Databases Work (The Basics)&lt;/h3&gt;
&lt;p&gt;Before we dive deeper into SQL, let's understand what databases are and how they're organized.&lt;/p&gt;
&lt;h4 id=&quot;what-is-a-database-&quot;&gt;What is a Database?&lt;/h4&gt;
&lt;p&gt;A database is a structured collection of information stored electronically. Think of it like a digital spreadsheet, but much more powerful and organized.&lt;/p&gt;
&lt;h4 id=&quot;database-structure&quot;&gt;Database Structure&lt;/h4&gt;
&lt;p&gt;Databases are organized in a hierarchy:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Database
├── Tables (like spreadsheet tabs)
│   ├── Rows (individual records)
│   └── Columns (categories of information)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;real-world-example&quot;&gt;Real-World Example&lt;/h4&gt;
&lt;p&gt;Let's say you're building an online bookstore. Your database might have these tables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Books Table:&lt;/strong&gt; Contains information about each book&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Customers Table:&lt;/strong&gt; Stores customer information&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orders Table:&lt;/strong&gt; Tracks customer purchases&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authors Table:&lt;/strong&gt; Information about book authors&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;basic-sql-operations-crud-&quot;&gt;Basic SQL Operations (CRUD)&lt;/h3&gt;
&lt;p&gt;SQL operations are often referred to as CRUD operations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C&lt;/strong&gt;reate - Add new data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;R&lt;/strong&gt;ead - Retrieve existing data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;U&lt;/strong&gt;pdate - Modify existing data&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D&lt;/strong&gt;elete - Remove data&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-creating-data-insert-&quot;&gt;1. Creating Data (INSERT)&lt;/h4&gt;
&lt;p&gt;Adding new information to a database:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Adding a new book to our bookstore
INSERT INTO books (title, author, price, genre)
VALUES ('The Great Gatsby', 'F. Scott Fitzgerald', 12.99, 'Fiction');
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-reading-data-select-&quot;&gt;2. Reading Data (SELECT)&lt;/h4&gt;
&lt;p&gt;Retrieving information from a database:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Get all books
SELECT * FROM books;

-- Get only book titles and prices
SELECT title, price FROM books;

-- Get books under $15
SELECT * FROM books WHERE price &amp;lt; 15.00;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-updating-data-update-&quot;&gt;3. Updating Data (UPDATE)&lt;/h4&gt;
&lt;p&gt;Modifying existing information:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Update the price of a specific book
UPDATE books 
SET price = 14.99 
WHERE title = 'The Great Gatsby';
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;4-deleting-data-delete-&quot;&gt;4. Deleting Data (DELETE)&lt;/h4&gt;
&lt;p&gt;Removing information:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Remove a book from the database
DELETE FROM books 
WHERE title = 'The Great Gatsby';
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;key-sql-concepts-every-beginner-should-know&quot;&gt;Key SQL Concepts Every Beginner Should Know&lt;/h3&gt;
&lt;h4 id=&quot;1-tables&quot;&gt;1. &lt;strong&gt;Tables&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Tables are the building blocks of databases. Each table represents a specific type of entity (like books, customers, or orders).&lt;/p&gt;
&lt;h4 id=&quot;2-primary-keys&quot;&gt;2. &lt;strong&gt;Primary Keys&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;A unique identifier for each row in a table. Like a social security number for database records.&lt;/p&gt;
&lt;h4 id=&quot;3-relationships&quot;&gt;3. &lt;strong&gt;Relationships&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Tables can be connected to each other. For example, an order table might reference a customer table.&lt;/p&gt;
&lt;h4 id=&quot;4-indexes&quot;&gt;4. &lt;strong&gt;Indexes&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Special structures that make searching faster. Like the index in a book that helps you find topics quickly.&lt;/p&gt;
&lt;h3 id=&quot;getting-started-with-sql&quot;&gt;Getting Started with SQL&lt;/h3&gt;
&lt;h4 id=&quot;step-1-choose-a-database-system&quot;&gt;Step 1: Choose a Database System&lt;/h4&gt;
&lt;p&gt;For beginners, I recommend starting with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQLite:&lt;/strong&gt; Simple, file-based database perfect for learning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MySQL:&lt;/strong&gt; Popular, free, and widely used&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL:&lt;/strong&gt; Powerful and feature-rich&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-2-install-and-set-up&quot;&gt;Step 2: Install and Set Up&lt;/h4&gt;
&lt;p&gt;Most database systems offer free community editions. You can also use online SQL playgrounds like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DB Fiddle&lt;/li&gt;
&lt;li&gt;SQLiteOnline&lt;/li&gt;
&lt;li&gt;W3Schools SQL Tryit Editor&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;step-3-practice-with-sample-data&quot;&gt;Step 3: Practice with Sample Data&lt;/h4&gt;
&lt;p&gt;Start with simple examples:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Create a simple table
CREATE TABLE students (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL,
    age INTEGER,
    grade TEXT
);

-- Add some data
INSERT INTO students (name, age, grade) VALUES
('Alice Johnson', 20, 'A'),
('Bob Smith', 19, 'B'),
('Carol Davis', 21, 'A');

-- Query the data
SELECT name, grade FROM students WHERE grade = 'A';
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;common-sql-commands-reference&quot;&gt;Common SQL Commands Reference&lt;/h3&gt;
&lt;h4 id=&quot;data-retrieval&quot;&gt;Data Retrieval&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Basic selection
SELECT column1, column2 FROM table_name;

-- With conditions
SELECT * FROM table_name WHERE condition;

-- Sorting results
SELECT * FROM table_name ORDER BY column_name;

-- Limiting results
SELECT * FROM table_name LIMIT 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;data-modification&quot;&gt;Data Modification&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Insert new record
INSERT INTO table_name (col1, col2) VALUES (value1, value2);

-- Update existing record
UPDATE table_name SET column1 = value1 WHERE condition;

-- Delete record
DELETE FROM table_name WHERE condition;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;real-world-applications-of-sql&quot;&gt;Real-World Applications of SQL&lt;/h3&gt;
&lt;h4 id=&quot;1-e-commerce-websites&quot;&gt;1. &lt;strong&gt;E-commerce Websites&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Track product inventory&lt;/li&gt;
&lt;li&gt;Process customer orders&lt;/li&gt;
&lt;li&gt;Manage user accounts&lt;/li&gt;
&lt;li&gt;Generate sales reports&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-social-media-platforms&quot;&gt;2. &lt;strong&gt;Social Media Platforms&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Store user profiles&lt;/li&gt;
&lt;li&gt;Manage posts and comments&lt;/li&gt;
&lt;li&gt;Track likes and shares&lt;/li&gt;
&lt;li&gt;Analyze user behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-banking-systems&quot;&gt;3. &lt;strong&gt;Banking Systems&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Process transactions&lt;/li&gt;
&lt;li&gt;Maintain account balances&lt;/li&gt;
&lt;li&gt;Generate financial reports&lt;/li&gt;
&lt;li&gt;Ensure data security&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-healthcare-systems&quot;&gt;4. &lt;strong&gt;Healthcare Systems&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Store patient records&lt;/li&gt;
&lt;li&gt;Track medical appointments&lt;/li&gt;
&lt;li&gt;Manage prescription data&lt;/li&gt;
&lt;li&gt;Generate health reports&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;best-practices-for-sql-beginners&quot;&gt;Best Practices for SQL Beginners&lt;/h3&gt;
&lt;h4 id=&quot;1-use-meaningful-names&quot;&gt;1. &lt;strong&gt;Use Meaningful Names&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Good
SELECT customer_name, order_date FROM orders;

-- Avoid
SELECT c, d FROM o;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-always-use-where-clauses-carefully&quot;&gt;2. &lt;strong&gt;Always Use WHERE Clauses Carefully&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- Always test your WHERE conditions
SELECT * FROM customers WHERE customer_id = 123;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-start-simple&quot;&gt;3. &lt;strong&gt;Start Simple&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Begin with basic queries before moving to complex joins and subqueries.&lt;/p&gt;
&lt;h4 id=&quot;4-practice-regularly&quot;&gt;4. &lt;strong&gt;Practice Regularly&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Like any language, SQL improves with consistent practice.&lt;/p&gt;
&lt;h3 id=&quot;common-mistakes-to-avoid&quot;&gt;Common Mistakes to Avoid&lt;/h3&gt;
&lt;h4 id=&quot;1-forgetting-where-clauses&quot;&gt;1. &lt;strong&gt;Forgetting WHERE Clauses&lt;/strong&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;-- This deletes ALL records!
DELETE FROM customers;

-- This deletes specific records
DELETE FROM customers WHERE customer_id = 123;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-not-backing-up-data&quot;&gt;2. &lt;strong&gt;Not Backing Up Data&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Always backup your database before making major changes.&lt;/p&gt;
&lt;h4 id=&quot;3-using-select-in-production-&quot;&gt;3. *&lt;em&gt;Using SELECT &lt;/em&gt; in Production**&lt;/h4&gt;
&lt;p&gt;Instead of selecting all columns, specify only the columns you need.&lt;/p&gt;
&lt;h4 id=&quot;4-ignoring-case-sensitivity&quot;&gt;4. &lt;strong&gt;Ignoring Case Sensitivity&lt;/strong&gt;&lt;/h4&gt;
&lt;p&gt;Some databases are case-sensitive. Be consistent with your naming.&lt;/p&gt;
&lt;h3 id=&quot;next-steps-in-your-sql-journey&quot;&gt;Next Steps in Your SQL Journey&lt;/h3&gt;
&lt;h4 id=&quot;1-advanced-topics-to-explore&quot;&gt;1. &lt;strong&gt;Advanced Topics to Explore&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Joins (connecting multiple tables)&lt;/li&gt;
&lt;li&gt;Indexes (improving query performance)&lt;/li&gt;
&lt;li&gt;Stored procedures (reusable code blocks)&lt;/li&gt;
&lt;li&gt;Triggers (automatic actions)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-practice-resources&quot;&gt;2. &lt;strong&gt;Practice Resources&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;HackerRank SQL challenges&lt;/li&gt;
&lt;li&gt;LeetCode database problems&lt;/li&gt;
&lt;li&gt;SQLBolt interactive tutorial&lt;/li&gt;
&lt;li&gt;Codecademy SQL course&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-build-projects&quot;&gt;3. &lt;strong&gt;Build Projects&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Create a personal expense tracker&lt;/li&gt;
&lt;li&gt;Build a simple library management system&lt;/li&gt;
&lt;li&gt;Design a blog database structure&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-learn-related-technologies&quot;&gt;4. &lt;strong&gt;Learn Related Technologies&lt;/strong&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Database design principles&lt;/li&gt;
&lt;li&gt;Data modeling&lt;/li&gt;
&lt;li&gt;Business intelligence tools&lt;/li&gt;
&lt;li&gt;Cloud database services (AWS RDS, Google Cloud SQL)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;tools-and-resources&quot;&gt;Tools and Resources&lt;/h3&gt;
&lt;h4 id=&quot;free-learning-resources&quot;&gt;Free Learning Resources&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;W3Schools SQL Tutorial:&lt;/strong&gt; Comprehensive and beginner-friendly&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLBolt:&lt;/strong&gt; Interactive SQL lessons&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;freeCodeCamp:&lt;/strong&gt; Free SQL course on YouTube&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Khan Academy:&lt;/strong&gt; Database basics&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;practice-platforms&quot;&gt;Practice Platforms&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HackerRank:&lt;/strong&gt; SQL challenges and competitions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LeetCode:&lt;/strong&gt; Database problems&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLZoo:&lt;/strong&gt; Interactive SQL tutorials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DB Fiddle:&lt;/strong&gt; Online SQL playground&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;books-for-deeper-learning&quot;&gt;Books for Deeper Learning&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&quot;Learning SQL&quot; by Alan Beaulieu&lt;/li&gt;
&lt;li&gt;&quot;SQL in 10 Minutes, Sams Teach Yourself&quot; by Ben Forta&lt;/li&gt;
&lt;li&gt;&quot;Head First SQL&quot; by Lynn Beighley&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;SQL is your gateway to the world of databases and data management. What makes SQL special is its simplicity and power – you can start writing useful queries within hours of learning the basics.&lt;/p&gt;
&lt;p&gt;Remember these key points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL is the universal language for databases&lt;/li&gt;
&lt;li&gt;It uses English-like syntax that's easy to learn&lt;/li&gt;
&lt;li&gt;The four basic operations (CRUD) form the foundation&lt;/li&gt;
&lt;li&gt;Practice with real data to build confidence&lt;/li&gt;
&lt;li&gt;Start simple and gradually tackle more complex concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The journey of learning SQL is incredibly rewarding. Whether you're interested in web development, data analysis, or just want to add a valuable skill to your toolkit, SQL is an excellent investment in your future.&lt;/p&gt;
&lt;p&gt;Don't let the technical aspects intimidate you – every SQL expert started exactly where you are now. The key is to start practicing with simple examples and gradually build your skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ready to start your SQL journey?&lt;/strong&gt; Pick one of the online SQL playgrounds mentioned above and try creating your first table today. You'll be amazed at how quickly you progress!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stay-connected-for-more-learning-content-&quot;&gt;Stay Connected for More Learning Content!&lt;/h3&gt;
&lt;p&gt;If you found this guide helpful, I'd love to have you join my community of learners! Follow me on these platforms for more programming tutorials, career advice, and tech insights:&lt;/p&gt;
&lt;h4 id=&quot;don-t-miss-these-&quot;&gt;Don't Miss These!&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Abdelhakim-Baalla&quot;&gt;Abdelhakim-Baalla&lt;/a&gt; - Browse my code and projects&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkedin.com/in/abdelhakimbaalla/&quot;&gt;abdelhakimbaalla&lt;/a&gt; - Professional insights and networking&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Twitter (X):&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/Abdelhakim99891&quot;&gt;@Abdelhakim99891&lt;/a&gt; - Daily tech tips and discussions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instagram:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.instagram.com/abdelhakim.baalla/&quot;&gt;@abdelhakim.baalla&lt;/a&gt; - Behind-the-scenes content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dev.to:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/abdelhakim_baalla&quot;&gt;abdelhakim_baalla&lt;/a&gt; - In-depth tutorials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medium:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://medium.com/@abdelhakimbaalla50&quot;&gt;@abdelhakimbaalla50&lt;/a&gt; - Detailed articles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reddit:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.reddit.com/user/Abdelhakim-Baalla430/&quot;&gt;Abdelhakim-Baalla430&lt;/a&gt; - Community discussions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://abdelhakim-baalla.vercel.app/&quot;&gt;abdelhakim-baalla.vercel.app&lt;/a&gt; - My work showcase&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Substack:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://substack.com/@abdelhakimbaalla&quot;&gt;@abdelhakimbaalla&lt;/a&gt; - Newsletter content&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hashnode:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://abdelhakimbaalla.hashnode.dev/&quot;&gt;abdelhakimbaalla.hashnode.dev&lt;/a&gt; - Technical blog&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/@Dinocrafting&quot;&gt;@Dinocrafting&lt;/a&gt; - Video tutorials&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack Overflow:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://stackoverflow.com/users/28245534/abdelhakim-baalla?tab=profile&quot;&gt;Profile&lt;/a&gt; - Q&amp;amp;A and help&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Your support means the world to me!&lt;/strong&gt;  Follow for more beginner-friendly programming content, project tutorials, and career guidance.&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/3880/what-is-sql-a-beginners-complete-guide</guid>
<pubDate>Thu, 10 Jul 2025 10:56:55 +0000</pubDate>
</item>
<item>
<title>Automating MySQL Backups on a VPS with Telegram Alerts Part 2</title>
<link>https://coderlegion.com/1629/automating-mysql-backups-on-a-vps-with-telegram-alerts-part-2</link>
<description>&lt;p&gt;Backing up MySQL databases is essential for data security and disaster recovery. In this article, we continue from our previous post and provide a script to automate MySQL database backups from a VPS to a local machine. This script ensures your backups are safe and up-to-date.&lt;/p&gt;
&lt;h3 id=&quot;prerequisites&quot;&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A Debian-based VPS&lt;/li&gt;
&lt;li&gt;MySQL installed and running&lt;/li&gt;
&lt;li&gt;SSH access to your VPS&lt;/li&gt;
&lt;li&gt;A local machine with enough storage for backups&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;mysql-backup-script&quot;&gt;MySQL Backup Script&lt;/h3&gt;
&lt;p&gt;The following script automates MySQL database backups by securely transferring them from a VPS to a local folder. It also sends notifications via Telegram when a backup succeeds or fails.&lt;/p&gt;
&lt;h4 id=&quot;script-overview&quot;&gt;Script Overview&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Uses &lt;code&gt;rsync&lt;/code&gt; to transfer backup files from the VPS.&lt;/li&gt;
&lt;li&gt;Logs all actions for debugging purposes.&lt;/li&gt;
&lt;li&gt;Sends a Telegram notification on success or failure.&lt;/li&gt;
&lt;li&gt;Cleans up old backup files on the VPS after a successful transfer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;backup-script&quot;&gt;Backup Script&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# MySQL Backup Script for VPS
# Author: Gift Balogun

# Configuration
REMOTE_HOST=&quot;your_vps_ip&quot;
REMOTE_USER=&quot;root&quot;
REMOTE_PORT=22
LOCAL_PATH=&quot;/WebsiteBackup&quot;
REMOTE_PATH=&quot;/root/backup/mysql&quot;
LOG_FILE=&quot;/var/log/mysql_backup.log&quot;

# Telegram Notification
TELEGRAM_BOT_TOKEN=&quot;your_bot_token&quot;
TELEGRAM_CHAT_ID=&quot;your_chat_id&quot;

# Logging function
log() {
    echo &quot;$(date '+%Y-%m-%d %H:%M:%S') - $1&quot; | tee -a &quot;$LOG_FILE&quot;
}

log &quot;Starting MySQL backup from $REMOTE_HOST...&quot;

# Ensure host key is added
ssh-keyscan -p $REMOTE_PORT $REMOTE_HOST &amp;gt;&amp;gt; ~/.ssh/known_hosts 2&amp;gt;/dev/null

# Perform backup
rsync -e &quot;ssh -o StrictHostKeyChecking=no -p $REMOTE_PORT&quot; -avzP &quot;$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH&quot; &quot;$LOCAL_PATH&quot; 2&amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

if [ $? -eq 0 ]; then
    log &quot;Backup completed successfully.&quot;
    curl -s -X POST &quot;https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendMessage&quot; \
         -d chat_id=&quot;$TELEGRAM_CHAT_ID&quot; \
         -d text=&quot;Backup completed successfully from $REMOTE_HOST to $LOCAL_PATH.&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    
    # Cleanup remote backup folder
    ssh &quot;$REMOTE_USER@$REMOTE_HOST&quot; -p &quot;$REMOTE_PORT&quot; &quot;rm -rf $REMOTE_PATH/*&quot; 2&amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
    log &quot;Remote files deleted successfully.&quot;
else
    log &quot;Backup failed. Check logs for details.&quot;
    curl -s -X POST &quot;https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/sendDocument&quot; \
         -F chat_id=&quot;$TELEGRAM_CHAT_ID&quot; \
         -F document=@&quot;$LOG_FILE&quot;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;explanation&quot;&gt;Explanation&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSH and rsync Setup:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The script ensures that the VPS host key is added to &lt;code&gt;known_hosts&lt;/code&gt; to prevent SSH authentication issues.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rsync&lt;/code&gt; securely transfers MySQL backups from the VPS to the local machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Logging and Notifications:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The script logs each action to &lt;code&gt;/var/log/mysql_backup.log&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the backup succeeds, a success message is sent via Telegram.&lt;/li&gt;
&lt;li&gt;If it fails, the log file is sent to Telegram for troubleshooting.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cleanup:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;After a successful backup, the remote backup folder is cleared to save space.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;running-the-script&quot;&gt;Running the Script&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Save the script as &lt;code&gt;backup.sh&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Make it executable: &lt;code&gt;chmod +x backup.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run it manually: &lt;code&gt;./backup.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Schedule it with cron: &lt;code&gt;crontab -e&lt;/code&gt;&lt;pre&gt;&lt;code&gt;0 2 * * * /path/to/backup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This runs the script every day at 2 AM.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This script simplifies MySQL database backups and ensures data safety. You can find the full script on GitHub:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/giftbalogun/localmysqlbackup&quot;&gt;https://github.com/giftbalogun/localmysqlbackup&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you haven't read Part 1, check it out here:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/1487/automate-your-vps-database-backups-like-a-pro-a-complete-mysql-backup-script&quot;&gt;https://coderlegion.com/1487/automate-your-vps-database-backups-like-a-pro-a-complete-mysql-backup-script&lt;/a&gt;&lt;/p&gt;
</description>
<category>Backup DB</category>
<guid isPermaLink="true">https://coderlegion.com/1629/automating-mysql-backups-on-a-vps-with-telegram-alerts-part-2</guid>
<pubDate>Sat, 22 Mar 2025 22:01:43 +0000</pubDate>
</item>
<item>
<title>Database Interaction using Python: Introduction to SQLite/MySQL/PostgreSQL</title>
<link>https://coderlegion.com/1586/database-interaction-using-python-introduction-to-sqlite-mysql-postgresql</link>
<description>&lt;p&gt;The article covers POSTGRESQL, MySQL, and SQLITE3—three major database management systems. They provide unique features and benefits and are essential to software development. Let's explore their features so that you can make better selections that will improve the project. Together, let's explore these essential elements in modern software engineering.&lt;/p&gt;
&lt;p&gt;As SQLite is &lt;strong&gt;lightweight&lt;/strong&gt;, it is a great choice for embedded applications and situations where simplicity is essential. On the other hand, MySQL stands out thanks to its strong&lt;strong&gt; client-server design&lt;/strong&gt;, &lt;strong&gt;scalability&lt;/strong&gt;, and widespread industry acceptance. PostgreSQL, on the other hand, is a popular option for&lt;strong&gt; enterprise-level &lt;/strong&gt;applications due to its &lt;strong&gt;versatility&lt;/strong&gt;, &lt;strong&gt;dependability&lt;/strong&gt;, and compliance with ACID short for Atomicity, Consistency, Isolation, and Durability.&lt;/p&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;SQLite Database&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;SQLite is a relational database engine that is lightweight, serverless, self-contained, and requires no configuration. Because it works directly with files, it's perfect for low-resource applications. Distribution and deployment are made easier by the fact that its self-contained databases are kept as single files. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Embedded in applications&lt;/strong&gt;: SQLite, which is frequently integrated into programmes, allows internal data administration without the need for external servers. Data management is made easier by this, especially for offline functioning.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File-based database&lt;/strong&gt;: SQLite databases are manageable and portable since they are kept as individual files on the file system.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Appropriate for small to medium-sized applications&lt;/strong&gt;: Because of its simplicity and lightweight design, SQLite is especially well-suited for small to medium-sized applications, even though it can manage huge datasets as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's go through an example of defining tables, installing SQLite in Python, and carrying out CRUD (Create, Read, Update, Delete) operations:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;conn = sqlite3.connect('example.db')&lt;br&gt;
cursor = conn.cursor()&lt;br&gt;
cursor.execute('''CREATE TABLE IF NOT EXISTS users&lt;br&gt;
                  (id INTEGER PRIMARY KEY, name TEXT, email TEXT)''')&lt;br&gt;
cursor.execute(&quot;INSERT INTO users (name, email) VALUES (?, ?)&quot;, ('Alice', '*Emails are not allowed*'))&lt;br&gt;
cursor.execute(&quot;INSERT INTO users (name, email) VALUES (?, ?)&quot;, ('Ali', '*Emails are not allowed*'))&lt;br&gt;
print(&quot;Records after initial insertion:&quot;)&lt;br&gt;
cursor.execute(&quot;SELECT DISTINCT * FROM users&quot;)&lt;br&gt;
rows = cursor.fetchall()&lt;br&gt;
for row in rows:&lt;br&gt;
    print(row)&lt;br&gt;
cursor.execute(&quot;UPDATE users SET email = ? WHERE name = ?&quot;, ('*Emails are not allowed*', 'Alice'))&lt;br&gt;
print(&quot;\nRecords after updating email of Alice:&quot;)&lt;br&gt;
cursor.execute(&quot;SELECT DISTINCT * FROM users&quot;)&lt;br&gt;
rows = cursor.fetchall()&lt;br&gt;
for row in rows:&lt;br&gt;
    print(row)&lt;br&gt;
cursor.execute(&quot;DELETE FROM users WHERE name = ?&quot;, ('Alice',))&lt;br&gt;
print(&quot;\nRecords after deleting Alice:&quot;)&lt;br&gt;
cursor.execute(&quot;SELECT DISTINCT * FROM users&quot;)&lt;br&gt;
rows = cursor.fetchall()&lt;br&gt;
for row in rows:&lt;br&gt;
    print(row)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=8245787205156792733&quot; alt=&quot;&quot; width=&quot;600&quot; height=&quot;250&quot;&gt;&lt;/p&gt;
&lt;div class=&quot;div-green&quot;&gt;&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt; Make sure the &lt;code&gt;'DISTINCT'&lt;/code&gt; keyword is applied to the right columns in SQL queries to obtain unique value combinations and prevent accidental duplicate results.&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;MySQL Database&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;The popular open-source RDBMS MySQL provides strong features and dependability for systems ranging from small-scale initiatives to enterprise-level setups. It offers a wide range of tools and features for effective structured data management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Client-server architecture:&lt;/strong&gt;MySQL has a client-server design, which enables several clients to connect to the database server at once. The management of database resources, scalability, and security are all improved by this architecture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance and scalability:&lt;/strong&gt; MySQL is designed to be scalable, meaning it can manage big databases and high transaction volumes while maintaining optimal performance. Performance is improved by its effective caching, query optimisation, and indexing methods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broad industrial adoption:&lt;/strong&gt; MySQL is widely used in a number of industries, including telecommunications, e-commerce, technology, and finance. Its widespread community support, dependability, and open-source nature have all added to its appeal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can get the MySQL Community Server package from &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.mysql.com/downloads/mysql/&quot;&gt; mysql official website&lt;/a&gt; and install MySQL by following the given steps. As an alternative, you can use package managers such as Homebrew for macOS or apt-get for Linux.&lt;/p&gt;
&lt;p&gt;Connecting to MySQL from Python:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import mysql.connector&lt;br&gt;
conn = mysql.connector.connect(user='user', password='password',&lt;br&gt;
                               host='localhost',&lt;br&gt;
                               database='test')&lt;br&gt;
cursor = conn.cursor()&lt;br&gt;
cursor.execute(&quot;SELECT * FROM table_name&quot;)&lt;br&gt;
results = cursor.fetchall()&lt;br&gt;
conn.close()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating tables and executing queries using Python's MySQL Connector:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cursor = conn.cursor()&lt;br&gt;
cursor.execute('''CREATE TABLE IF NOT EXISTS users&lt;br&gt;
                  (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))''')&lt;br&gt;
cursor.execute(&quot;INSERT INTO users (name, email) VALUES (%s, %s)&quot;, ('Alice', '*Emails are not allowed*'))&lt;br&gt;
cursor.execute(&quot;SELECT * FROM users&quot;)&lt;br&gt;
results = cursor.fetchall()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;p&gt;&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=4398450430378255567&quot; alt=&quot;&quot; width=&quot;650&quot; height=&quot;400&quot;&gt;&lt;/p&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;PostgreSQL Database&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Strong and open-source, PostgreSQL is an &lt;strong&gt;object-relational database system&lt;/strong&gt; that is well-known for its feature-rich set, dependability, and extensibility. It is a top option for both small-scale applications and large corporate systems because of its comprehensive functions that enable complicated data types, transactions, and data integrity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Features:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extensibility&lt;/strong&gt;: Because of PostgreSQL's flexible architecture, programmers are able to design unique functions, data types, and indexing techniques to meet the needs of particular applications. The smooth integration of third-party extensions and libraries is made possible by this flexibility.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACID Compliance&lt;/strong&gt;: PostgreSQL guarantees ACID (&lt;br&gt;&lt;strong&gt;Atomicity&lt;/strong&gt;: To preserve database integrity, transactions are either fully completed or entirely aborted.&lt;br&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: Assures that the database is kept intact by writing only legitimate data.&lt;br&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: Data integrity is ensured by many transactions running simultaneously without interfering.&lt;br&gt;&lt;strong&gt;Durability&lt;/strong&gt;: Even in the event of a system failure, committed transaction modifications remain intact.)  compliance, which in turn provides dependable and consistent database transactions. This functionality is crucial for ensuring consistency across the database and data integrity even in the case of system failures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Integrity and Reliability&lt;/strong&gt;: PostgreSQL uses built-in restrictions like triggers and rules, foreign keys, and concurrency management to guarantee data integrity and stability.&lt;/li&gt;
&lt;/ul&gt;
&lt;/section&gt;
&lt;p&gt;To install PostgreSQL, you can download and install the PostgreSQL package from the &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.postgresql.org/download/&quot;&gt; official website&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import psycopg2&lt;br&gt;
conn = psycopg2.connect(&lt;br&gt;
    dbname='your_database_name',&lt;br&gt;
    user='your_username',&lt;br&gt;
    password='your_password',&lt;br&gt;
    host='localhost',&lt;br&gt;
    port='5432'&lt;br&gt;
)&lt;br&gt;
if conn:&lt;br&gt;
    print(&quot;Connected to PostgreSQL&quot;)&lt;br&gt;
else:&lt;br&gt;
    print(&quot;Failed to connect to PostgreSQL&quot;)&lt;br&gt;
conn.close()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Creating tables and executing queries using Python's psycopg2 library:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;# Create a cursor object&lt;br&gt;
cursor = conn.cursor()&lt;br&gt;
cursor.execute('''CREATE TABLE IF NOT EXISTS users (&lt;br&gt;
                    id SERIAL PRIMARY KEY,&lt;br&gt;
                    name VARCHAR(255),&lt;br&gt;
                    email VARCHAR(255)&lt;br&gt;
                )''')&lt;br&gt;
cursor.execute(&quot;INSERT INTO users (name, email) VALUES (%s, %s)&quot;, ('Alice', '*Emails are not allowed*'))&lt;br&gt;
cursor.execute(&quot;SELECT * FROM users&quot;)&lt;br&gt;
results = cursor.fetchall()&lt;br&gt;
for row in results:&lt;br&gt;
    print(row)&lt;br&gt;
conn.commit()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-blue&quot;&gt;&lt;span class=&quot;alert-header&quot;&gt;Note:&lt;/span&gt; Always securely handle database credentials and avoid hardcoding them directly into your code for better security.&lt;/div&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Comparison&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;Factor&lt;/th&gt;
&lt;th&gt;SQLite&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;Good for &lt;br&gt;small to medium&lt;br&gt;-sized applications&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;Optimized for &lt;br&gt;read-heavy &lt;br&gt;workloads&lt;/td&gt;
&lt;td&gt;Optimized for &lt;br&gt;complex &lt;br&gt;queries &lt;br&gt;and large &lt;br&gt;datasets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Limited scalability &lt;br&gt;due to file-&lt;br&gt;based architecture&lt;/td&gt;
&lt;td&gt;Good scalability &lt;br&gt;with support for &lt;br&gt;clustering and &lt;br&gt;sharding&lt;/td&gt;
&lt;td&gt;High scalability &lt;br&gt;with advanced &lt;br&gt;replication and &lt;br&gt;partitioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Features&lt;/td&gt;
&lt;td&gt;Basic features with limited concurrency support&lt;/td&gt;
&lt;td&gt;Rich feature set including stored procedures, triggers, and views&lt;/td&gt;
&lt;td&gt;Comprehensive feature set including JSONB data type, &lt;br&gt;full-text search, and &lt;br&gt;advanced indexing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ease of Use&lt;/td&gt;
&lt;td&gt;Simple setup and usage, suitable for beginners&lt;/td&gt;
&lt;td&gt;Easy to use, especially &lt;br&gt;with graphical tools like MySQL Workbench&lt;/td&gt;
&lt;td&gt;Requires more &lt;br&gt;configuration but &lt;br&gt;offers powerful features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Case Suitability&lt;/td&gt;
&lt;td&gt;Ideal for &lt;br&gt;embedded &lt;br&gt;systems &lt;br&gt;and small &lt;br&gt;applications&lt;/td&gt;
&lt;td&gt;
&lt;p&gt;Well-suited for web applications &lt;br&gt;and medium-&lt;br&gt;sized databases&lt;/p&gt;
&lt;/td&gt;
&lt;td&gt;Best for large-scale applications with &lt;br&gt;complex data &lt;br&gt;requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Advanced Concepts&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Advanced ideas like indexing, optimization, and transactions are essential for improving the scalability, performance, and dependability of databases.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transactions:&lt;/strong&gt; Transactions ensure the ACID properties of database operations, handling multiple tasks as a single unit for data accuracy and reliability. All database systems provide tools for effective transaction management.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Indexing&lt;/strong&gt;: Indexes expedite data retrieval, particularly for SELECT queries, by swiftly accessing entries in database tables based on specific field values. Effective indexing reduces the need for full table scans, significantly enhancing query performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimization&lt;/strong&gt;: Database optimization strategies improve effectiveness and performance through resource usage optimization, performance tuning, query optimization, and schema design optimization. Each system provides unique features and tools to enhance productivity and address performance issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import sqlite3&lt;br&gt;
import mysql.connector&lt;br&gt;
import psycopg2&lt;br&gt;
def sqlite_transactions():&lt;br&gt;
    conn = sqlite3.connect('example.db')&lt;br&gt;
    cursor = conn.cursor()&lt;br&gt;
    try:&lt;br&gt;
        conn.execute(&quot;BEGIN&quot;)&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance - 100 WHERE id = 1&quot;)&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance + 100 WHERE id = 2&quot;)&lt;br&gt;
        conn.commit()&lt;br&gt;
        print(&quot;Transaction committed successfully&quot;)&lt;br&gt;
    except sqlite3.Error as e:&lt;br&gt;
        conn.rollback()&lt;br&gt;
        print(&quot;Transaction rolled back due to error:&quot;, e)&lt;br&gt;
    finally:&lt;br&gt;
        conn.close()&lt;br&gt;
def mysql_transactions():&lt;br&gt;
    conn = mysql.connector.connect(user='user', password='password', database='example')&lt;br&gt;
    cursor = conn.cursor()&lt;br&gt;
    try:&lt;br&gt;
        conn.start_transaction()&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance - 100 WHERE id = 1&quot;)&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance + 100 WHERE id = 2&quot;)&lt;br&gt;
        conn.commit()&lt;br&gt;
        print(&quot;Transaction committed successfully&quot;)&lt;br&gt;
    except mysql.connector.Error as e:&lt;br&gt;
        conn.rollback()&lt;br&gt;
        print(&quot;Transaction rolled back due to error:&quot;, e)&lt;br&gt;
    finally:&lt;br&gt;
        conn.close()&lt;br&gt;
def postgres_transactions():&lt;br&gt;
    conn = psycopg2.connect(dbname='example', user='user', password='password', host='localhost')&lt;br&gt;
    cursor = conn.cursor()&lt;br&gt;
    try:&lt;br&gt;
        conn.autocommit = False&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance - 100 WHERE id = 1&quot;)&lt;br&gt;
        cursor.execute(&quot;UPDATE users SET balance = balance + 100 WHERE id = 2&quot;)&lt;br&gt;
        conn.commit()&lt;br&gt;
        print(&quot;Transaction committed successfully&quot;)&lt;br&gt;
    except psycopg2.Error as e:&lt;br&gt;
        conn.rollback()&lt;br&gt;
        print(&quot;Transaction rolled back due to error:&quot;, e)&lt;br&gt;
    finally:&lt;br&gt;
        conn.close()&lt;br&gt;
sqlite_transactions()&lt;br&gt;
mysql_transactions()&lt;br&gt;
postgres_transactions()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SQLite, designed for single-user and small-scale applications, uses local file-based databases and simple transaction management with BEGIN, COMMIT, and ROLLBACK statements. &lt;br&gt;MySQL, a robust RDBMS, offers scalability and concurrency control with start_transaction(), commit(), and rollback() methods.&lt;br&gt;PostgreSQL, known for advanced features and extensibility, provides strong support for concurrency, data integrity, and advanced SQL features, managing transactions via autocommit mode and commit()/rollback() methods.&lt;/p&gt;
&lt;/section&gt;
&lt;div class=&quot;div-faq&quot;&gt;&lt;span class=&quot;faq-header&quot;&gt;FAQ&lt;/span&gt; &lt;span class=&quot;faq-body&quot;&gt;&lt;br&gt;Q: What is the difference between SQL and NoSQL databases?&lt;br&gt;A: SQL (Structured Query Language) databases, also known as relational databases, organize data into tables with predefined schemas and use SQL for querying. NoSQL databases, on the other hand, store data in flexible, non-tabular formats and do not require a fixed schema. They are designed for scalability and handling unstructured data.&lt;/span&gt;&lt;/div&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Databases are essential to software development because they allow for effective data management. MySQL scales for the web, PostgreSQL meets enterprise needs, and SQLite is ideal for&lt;strong&gt; lightweight applications&lt;/strong&gt;. When choosing a project, it is essential to comprehend their strengths. Using &lt;strong&gt;cutting-edge ideas&lt;/strong&gt; promotes creativity in digital solutions by ensuring data performance and dependability.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Reference&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;For more reference:&lt;br&gt;SQLite Official Website:&lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite Official Website&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
</description>
<category>Python Database Interaction</category>
<guid isPermaLink="true">https://coderlegion.com/1586/database-interaction-using-python-introduction-to-sqlite-mysql-postgresql</guid>
<pubDate>Wed, 19 Mar 2025 11:56:30 +0000</pubDate>
</item>
<item>
<title>Automate Your VPS Database Backups Like a Pro: A Complete MySQL Backup Script</title>
<link>https://coderlegion.com/1487/automate-your-vps-database-backups-like-a-pro-a-complete-mysql-backup-script</link>
<description>

  &lt;p&gt;Managing a VPS (Virtual Private Server) with a MySQL database? Then you know how important **regular backups** are!&lt;/p&gt;
  
  &lt;p&gt;A single accidental command, server crash, or unexpected failure can **wipe out critical data** in seconds. That’s why automating backups is a must! &lt;/p&gt;

  &lt;div class=&quot;note&quot;&gt;
    &lt;strong&gt;In this guide, you’ll learn how to:&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;✅ Automate MySQL backups on your VPS&lt;/li&gt;
      &lt;li&gt;✅ Compress backups to save space&lt;/li&gt;
      &lt;li&gt;✅ Automatically delete old backups&lt;/li&gt;
      &lt;li&gt;✅ Schedule backups using cron jobs&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;

  &lt;p&gt;By the end of this guide, you’ll have a **fully automated, hands-free backup solution** running on your server! &lt;/p&gt;

  &lt;h2&gt; Why You Need Automated MySQL Backups&lt;/h2&gt;
  &lt;p&gt;Relying on **manual backups** is risky and inefficient. Here's why automation is essential:&lt;/p&gt;
  
  &lt;ul&gt;
    &lt;li&gt;❌ **Mistakes happen** – A single 
&lt;code&gt;DROP DATABASE&lt;/code&gt; command can erase years of data.&lt;/li&gt;
    &lt;li&gt;❌ **Downtime is expensive** – Losing customer data can damage your reputation and business.&lt;/li&gt;
    &lt;li&gt;❌ **Disk failures are unpredictable** – Outdated backups won’t help if your server crashes.&lt;/li&gt;
    &lt;li&gt;✅ **Automation ensures regular backups** – Set it once, and let your VPS handle the rest!&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2&gt;️ Prerequisites&lt;/h2&gt;
  &lt;p&gt;Before we start, make sure you have:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;✔️ A VPS running **Debian/Ubuntu**&lt;/li&gt;
    &lt;li&gt;✔️ MySQL/MariaDB installed&lt;/li&gt;
    &lt;li&gt;✔️ Sudo privileges&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2&gt; Step 1: Download the MySQL Backup Script&lt;/h2&gt;
  &lt;p&gt;Clone the backup script from GitHub:&lt;/p&gt;
&lt;pre&gt;
&lt;code&gt;git clone https://github.com/giftbalogun/vpsMySQLBackup.git
cd vpsMySQLBackup
&lt;/code&gt;
&lt;/pre&gt;

  &lt;h2&gt;⚙️ Step 2: Configure the Backup Script&lt;/h2&gt;
  &lt;p&gt;Open the script and edit your MySQL credentials and backup settings:&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;nano backup_mysql.sh&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Modify these lines:&lt;/p&gt;
  &lt;pre&gt;
&lt;code&gt;MYSQL_USER=&quot;your_mysql_username&quot;
MYSQL_PASSWORD=&quot;your_mysql_password&quot;
DAYS_TO_KEEP=5        # Set how many days to keep backups (0 to keep forever)
GZIP=1                # Set to 1 for compressed backups, 0 for raw SQL
BACKUP_PATH=&quot;/root/backup/mysql&quot;
&lt;/code&gt;
&lt;/pre&gt;

  &lt;p&gt; **Tip:** Make sure your MySQL user has the correct privileges!&lt;/p&gt;

  &lt;h2&gt; Step 3: Make the Script Executable &amp;amp; Run It&lt;/h2&gt;
  &lt;p&gt;Give the script execution permission:&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;chmod +x backup_mysql.sh&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;Run the script manually to test it:&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;sudo ./backup_mysql.sh&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;✅ If everything is set up correctly, you’ll see output like this:&lt;/p&gt;
&lt;pre&gt;
&lt;code&gt;--------------------------------------------------------
      MySQL Backup Script - Copyright © 2024 Gift Balogun
--------------------------------------------------------
[INFO] Backing up database: my_database (compressed)
[SUCCESS] Backup completed: my_database
[INFO] Removing backups older than 5 days...
[INFO] Cleanup completed.
[INFO] MySQL Backup Process Completed Successfully!
&lt;/code&gt;
&lt;/pre&gt;

  &lt;h2&gt; Step 4: Automate Backups with Cron Jobs&lt;/h2&gt;
  &lt;p&gt;To ensure backups run automatically, we’ll use **cron jobs**.&lt;/p&gt;

  &lt;h3&gt;1️⃣ Open the cron job editor:&lt;/h3&gt;
  &lt;pre&gt;&lt;code&gt;crontab -e&lt;/code&gt;&lt;/pre&gt;

  &lt;h3&gt;2️⃣ Add this line at the bottom:&lt;/h3&gt;
  &lt;pre&gt;&lt;code&gt;0 2 * * * /path/to/backup_mysql.sh &amp;gt;&amp;gt; /var/log/mysql_backup.log 2&amp;gt;&amp;amp;1&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt; This schedules the backup **every day at 2 AM** and logs the output.&lt;/p&gt;

  &lt;h3&gt;3️⃣ Save and exit (CTRL + X, then Y and ENTER).&lt;/h3&gt;

  &lt;p&gt; Done! Your server now automatically backs up MySQL databases daily.&lt;/p&gt;

  &lt;h2&gt;️ Step 5: Restore a Backup (When Needed)&lt;/h2&gt;
  &lt;p&gt;In case of data loss, restoring a backup is simple:&lt;/p&gt;

  &lt;h3&gt;1️⃣ Find your backup file:&lt;/h3&gt;
  &lt;pre&gt;&lt;code&gt;ls -lh /root/backup/mysql/&lt;/code&gt;&lt;/pre&gt;

  &lt;h3&gt;2️⃣ Restore it to MySQL:&lt;/h3&gt;
  &lt;pre&gt;&lt;code&gt;mysql -u your_mysql_username -p your_database_name &amp;lt; /root/backup/mysql/your_backup.sql&lt;/code&gt;&lt;/pre&gt;

  &lt;p&gt;If your backup is compressed (.sql.gz), use:&lt;/p&gt;
  &lt;pre&gt;&lt;code&gt;gunzip -c /root/backup/mysql/your_backup.sql.gz | mysql -u your_mysql_username -p your_database_name&lt;/code&gt;&lt;/pre&gt;

  &lt;h2&gt;️ Troubleshooting&lt;/h2&gt;
  &lt;ul&gt;
    &lt;li&gt; **Permission Denied?** Run the script with 
&lt;code&gt;sudo&lt;/code&gt; or check file permissions.&lt;/li&gt;
    &lt;li&gt; **Crontab Not Running?** Ensure the script has execute permission and use the full path in 
&lt;code&gt;crontab -e&lt;/code&gt;.&lt;/li&gt;
    &lt;li&gt; **Backup Files Missing?** Double-check the 
&lt;code&gt;BACKUP_PATH&lt;/code&gt; directory and &lt;code&gt;DAYS_TO_KEEP&lt;/code&gt; settings.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h2&gt; Final Thoughts&lt;/h2&gt;
  &lt;p&gt;Automating MySQL backups **saves time**, **prevents data loss**, and ensures **peace of mind**.&lt;/p&gt;

  &lt;p&gt; Want the full script? **Get it on GitHub:**  
  &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/giftbalogun/vpsMySQLBackup&quot; target=&quot;_blank&quot;&gt; View Full Code Here&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt; Need Help or Have Questions? Drop a comment below! &lt;/p&gt;



</description>
<category>Backup DB</category>
<guid isPermaLink="true">https://coderlegion.com/1487/automate-your-vps-database-backups-like-a-pro-a-complete-mysql-backup-script</guid>
<pubDate>Sat, 15 Mar 2025 08:51:52 +0000</pubDate>
</item>
<item>
<title>Database Interaction: Connecting to Databases</title>
<link>https://coderlegion.com/390/database-interaction-connecting-to-databases</link>
<description>&lt;p&gt;In this article, we will address the key issue of database connections in software development. Large volumes of data are effectively stored and managed via databases, which form the foundation of many applications. Building scalable and reliable apps requires developers to have a solid understanding of database connections.&lt;br&gt;In software development, database connectivity is essential for maintaining &lt;strong&gt;data integrity&lt;/strong&gt;, &lt;strong&gt;scalability&lt;/strong&gt;, and &lt;strong&gt;system performance&lt;/strong&gt;. Relational databases like MySQL and PostgreSQL offer structured storage and sophisticated querying, whereas NoSQL databases like MongoDB and Redis offer flexibility for unstructured data. Comprehending these distinctions is essential for developers to make well-informed decisions.&lt;/p&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Selecting the Right Database&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;This tutorial offers information on different kinds of databases and things to think about.&lt;br&gt;Different Types of Databases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Relational Databases:&lt;/strong&gt; These databases arrange information into structured tables that have pre-established connections between them. PostgreSQL, SQL Server, and MySQL are a few examples.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NoSQL database&lt;/strong&gt;: Large amounts of unstructured or semi-structured data are easily handled by NoSQL databases because they provide a more flexible data format. Redis, Cassandra, and MongoDB are a few examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Factors to Consider When Choosing a Database:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Data Structure and Requirements&lt;/strong&gt;: NoSQL databases are better for unstructured or schema-less data, while relational databases are better for highly structured data that requires complex querying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalability&lt;/strong&gt;: Relational databases scale vertically by adding resources to a single server; NoSQL databases scale horizontally by sharing data across servers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Relational databases perform better in ACID transactions, however NoSQL databases place a higher priority on low latency and high throughput.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Requirements for Consistency and Integrity&lt;/strong&gt;: Strict consistency and transactional integrity are guaranteed for accurate data by relational databases. NoSQL databases may provide models for eventual consistency, putting high availability ahead of instant consistency.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;from pymongo import MongoClient&lt;br&gt;
client = MongoClient('mongodb://localhost:27017/')&lt;br&gt;
db = client['my_database']&lt;br&gt;
collection = db['my_collection']&lt;br&gt;
collection.insert_one({'name': 'XYZ', 'age': 30, 'email': '*Emails are not allowed*'})&lt;br&gt;
# Query documents&lt;br&gt;
result = collection.find_one({'name': 'XYZ'})&lt;br&gt;
print(result)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div class=&quot;div-green&quot;&gt;&lt;span class=&quot;alert-header&quot;&gt;Tip:&lt;/span&gt; Prototype with different databases to assess performance, scalability, and suitability for your project before making a final decision.&lt;/div&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Python's Common Database Connection Methods&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;There are numerous ways to connect to databases in Python, including:&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;1. Using built-in libraries&lt;/strong&gt;:&lt;/span&gt;&lt;/h3&gt;
&lt;/section&gt;
&lt;section&gt;Direct database connections without the need for additional dependencies are made possible by Python's built-in modules, such as sqlite3 for SQLite.&lt;br&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import sqlite3&lt;br&gt;
conn = sqlite3.connect('example.db')&lt;br&gt;
cursor = conn.cursor()&lt;br&gt;
cursor.execute('SELECT * FROM table_name')&lt;br&gt;
results = cursor.fetchall()&lt;br&gt;
for x in results:&lt;br&gt;
  print(x)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;2. Third-party libraries:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;External libraries provide additional functionality and optimizations for particular database systems, such as psycopg2 for PostgreSQL and mysql-connector-python for MySQL. These packages offer simple-to-use APIs for establishing database connections and running queries.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import mysql.connector&lt;br&gt;
mydb= mysql.connector.connect(&lt;br&gt;
    host='hostname',&lt;br&gt;
    user='username',&lt;br&gt;
    database='database_name'&lt;br&gt;
)&lt;br&gt;
mycursor = mydb.cursor()&lt;br&gt;
mycursor.execute('SELECT * FROM table_name')&lt;br&gt;
myresult = mycursor.fetchall()&lt;br&gt;
for x in myresult:&lt;br&gt;
  print(x)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=11770887932542901517&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;450&quot;&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;3. ORM (Object-Relational Mapping) frameworks:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;By abstracting database interactions, ORM frameworks such as SQLAlchemy save developers from writing raw SQL queries and enable them to work with Python objects. These frameworks offer strong capabilities for manipulating and managing databases.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;from sqlalchemy import create_engine, MetaData, Table&lt;br&gt;
engine = create_engine('postgresql://username:password@localhost/database_name')&lt;br&gt;
metadata = MetaData()&lt;br&gt;
table = Table('table_name', metadata, autoload=True, autoload_with=engine)&lt;br&gt;
results = engine.execute(table.select()).fetchall()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Connecting to Relational Database&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;There are multiple processes involved in setting up connections to widely used relational databases such as SQLite, MySQL, and PostgreSQL. Here's a thorough explanation with examples to help clarify:&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;1. Setting up connections with each database:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A. SQLite:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Data is stored in a single file with the lightweight serverless database engine SQLite. With Python's built-in sqlite3 package, connecting to SQLite is simple.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import sqlite3&lt;br&gt;
conn = sqlite3.connect('example.db')&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;img src=&quot;https://logiclair.org/?qa=blob&amp;amp;qa_blobid=14587128245889891539&quot; alt=&quot;&quot; width=&quot;750&quot; height=&quot;350&quot;&gt;
&lt;p&gt;&lt;strong&gt;B. MySQL:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The mysql-connector-python library can be used to create a connection for MySQL. Make sure pip install mysql-connector-python is installed on your computer.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import mysql.connector&lt;br&gt;
conn = mysql.connector.connect(&lt;br&gt;
    host='localhost',&lt;br&gt;
    user='username',&lt;br&gt;
    database='database_name'&lt;br&gt;
)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;C. PostgreSQL:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can use the psycopg2 library to establish a connection to PostgreSQL. Install it (pip install psycopg2), please.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import psycopg2&lt;br&gt;
conn = psycopg2.connect(&lt;br&gt;
    host='localhost',&lt;br&gt;
    user='username',&lt;br&gt;
    password='password',&lt;br&gt;
    database='database_name'&lt;br&gt;
)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;2. Handling connection errors and exceptions:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;For your application to be robust, it is imperative that you handle exceptions and connection problems in a polite manner. Try-except blocks are a useful tool for handling and catching exceptions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;try:&lt;br&gt;
    conn = psycopg2.connect(&lt;br&gt;
        host='localhost',&lt;br&gt;
        user='username',&lt;br&gt;
        database='database_name'&lt;br&gt;
    )&lt;br&gt;
except psycopg2.Error as e:&lt;br&gt;
    print(f&quot;Error connecting to PostgreSQL: {e}&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;3. Executing SQL queries and retrieving results:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Once connected, you may use cursor objects to run SQL queries and get the appropriate responses.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;cursor = conn.cursor()&lt;br&gt;
cursor.execute('SELECT * FROM table_name')&lt;br&gt;
results = cursor.fetchall()&lt;br&gt;
for row in results:&lt;br&gt;
    print(row)&lt;br&gt;
cursor.close()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Connecting to NoSQL Databases&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;In Python, connecting to well-known NoSQL databases such as MongoDB and Redis requires simple steps for database operations, error handling, and setup:&lt;/p&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;1. Setting up connections with each database:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;A. MongoDB:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To create a connection for MongoDB, use the pymongo package.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;from pymongo import MongoClient&lt;br&gt;
client = MongoClient('mongodb://localhost:27017/')&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;B. Redis:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pymongo.readthedocs.io/en/stable/&quot;&gt;Redis&lt;/a&gt; can be accessed by using the redis library.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import redis&lt;br&gt;
r = redis.Redis(host='localhost', port=6379, db=0)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;2. Handling connection errors and exceptions:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Make sure to use try-except blocks to properly handle connection problems.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;try:&lt;br&gt;
    client = MongoClient('mongodb://localhost:27017/')&lt;br&gt;
except Exception as e:&lt;br&gt;
  print(f&quot;Error connecting to MongoDB: {e}&quot;)&lt;br&gt;
// Redis&lt;br&gt;
try:&lt;br&gt;
    r = redis.Redis(host='localhost', port=6379, db=0)&lt;br&gt;
except Exception as e:&lt;br&gt;
    print(f&quot;Error connecting to Redis: {e}&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;3. Executing database Operations:&lt;/strong&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;Execute database operations like delete, update, and insert.&lt;/p&gt;
&lt;p&gt;MongoDB:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;db = client['my_database']&lt;br&gt;
collection = db['my_collection']&lt;br&gt;
collection.insert_one({'name': 'John', 'age': 30})&lt;br&gt;
collection.update_one({'name': 'John'}, {'$set': {'age': 31}})&lt;br&gt;
collection.delete_one({'name': 'John'})&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redis:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;r.set('key', 'value')&lt;br&gt;
value = r.get('key')&lt;br&gt;
r.delete('key')&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Error handling in database connections is crucial for program reliability and robustness, preventing crashes and enhancing user experience by addressing unforeseen database errors and connection issues.&lt;/p&gt;
&lt;strong&gt;Using try-except blocks:&lt;/strong&gt; Try-except blocks can be used to catch and manage exceptions that are raised during database operations. This guarantees that your program can continue running even after encountering failures.&lt;br&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import psycopg2&lt;br&gt;
try:&lt;br&gt;
    conn = psycopg2.connect(&lt;br&gt;
        host='localhost',&lt;br&gt;
        user='username',&lt;br&gt;
        database='database_name'&lt;br&gt;
    )&lt;br&gt;
except psycopg2.Error as e:&lt;br&gt;
    print(f&quot;Error connecting to PostgreSQL: {e}&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;strong&gt;Handling specific error types&lt;/strong&gt;: To improve error management precision, classify errors such as query problems or connection failures and provide customised error messages and actions.&lt;br&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import psycopg2&lt;br&gt;
try:&lt;br&gt;
    conn = psycopg2.connect(&lt;br&gt;
        host='localhost',&lt;br&gt;
        user='username',&lt;br&gt;
        password='password',&lt;br&gt;
        database='database_name'&lt;br&gt;
    )&lt;br&gt;
except psycopg2.OperationalError as e:&lt;br&gt;
    print(f&quot;Operational error connecting to PostgreSQL: {e}&quot;)&lt;br&gt;
except psycopg2.DatabaseError as e:&lt;br&gt;
    print(f&quot;Database error connecting to PostgreSQL: {e}&quot;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;strong&gt;Implementing retry mechanisms&lt;/strong&gt;: Retry techniques automatically retry database operations during temporary errors or network issues, enhancing application robustness, especially in unstable contexts.
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;import psycopg2&lt;br&gt;
import time&lt;br&gt;
max_retries = 3&lt;br&gt;
retry_delay = 1  # seconds&lt;br&gt;
for retry_count in range(max_retries):&lt;br&gt;
    try:&lt;br&gt;
        conn = psycopg2.connect(&lt;br&gt;
            host='localhost',&lt;br&gt;
            user='username',&lt;br&gt;
            database='database_name'&lt;br&gt;
        )&lt;br&gt;
        break&lt;br&gt;
    except psycopg2.Error as e:&lt;br&gt;
        print(f&quot;Error connecting to PostgreSQL: {e}&quot;)&lt;br&gt;
        if retry_count &amp;lt; max_retries - 1:&lt;br&gt;
            print(f&quot;Retrying in {retry_delay} seconds...&quot;)&lt;br&gt;
            time.sleep(retry_delay)&lt;br&gt;
        else:&lt;br&gt;
            print(&quot;Max retries exceeded, unable to connect.&quot;)&lt;br&gt;
          break&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Best Practices and Tips&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;By adhering to these best practices, Python programs may guarantee stable database connections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;To effectively manage database connections, make use of connection pooling.&lt;/li&gt;
&lt;li&gt;In order to reduce overhead and boost efficiency, reuse connections.&lt;/li&gt;
&lt;li&gt;To liberate resources, always properly close connections.&lt;/li&gt;
&lt;li&gt;Handle connection credentials securely to avoid unwanted access.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-built_in&quot;&gt;from sqlalchemy import create_engine, Pool&lt;br&gt;
# Create an engine with connection pooling&lt;br&gt;
engine = create_engine('database://user:password@host:port/db_name', pool_size=5, max_overflow=10)&lt;br&gt;
# Use the engine to connect to the database&lt;br&gt;
connection = engine.connect()&lt;br&gt;
# Perform database operations...&lt;br&gt;
# Close the connection&lt;br&gt;
connection.close()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/section&gt;
&lt;div class=&quot;div-faq&quot;&gt;&lt;span class=&quot;faq-header&quot;&gt;FAQ&lt;/span&gt; &lt;span class=&quot;faq-body&quot;&gt;&lt;br&gt;Q: Why is connection pooling important in database applications?&lt;br&gt;A: Connection pooling helps manage and reuse database connections, reducing the overhead of establishing new connections for each database operation. This improves performance and scalability, especially in applications with high traffic.&lt;/span&gt;&lt;/div&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;To sum up, this article highlights how important database connections are to software development. Comprehending the subtle differences between &lt;strong&gt;relational &lt;/strong&gt;and &lt;strong&gt;NoSQL database&lt;/strong&gt; types is essential for achieving the best possible project performance. Python provides &lt;strong&gt;flexible &lt;/strong&gt;ways to provide error handling and make database connections. Through careful consideration of database selection and adherence to industry best practices, developers can construct &lt;strong&gt;resilient systems&lt;/strong&gt; that effectively handle data and satisfy the requirements of contemporary software development.&lt;/p&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;h2&gt;&lt;span style=&quot;font-family: arial,helvetica,sans-serif;&quot;&gt;&lt;strong&gt;Reference&lt;/strong&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;For more reference:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;PyMongo &lt;br&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pymongo.readthedocs.io/en/stable/&quot;&gt;PyMongo - MongoDB Python driver&lt;/a&gt;&lt;/p&gt;
&lt;/section&gt;
</description>
<category>Data Structures</category>
<guid isPermaLink="true">https://coderlegion.com/390/database-interaction-connecting-to-databases</guid>
<pubDate>Fri, 26 Jul 2024 10:22:07 +0000</pubDate>
</item>
</channel>
</rss>