Monday, May 4, 2026

How a Forgetful MySQL Config on a Cheap VPS Turned My NestJS App 10× Slower—and I Fixed It in 30 Minutes

How a Forgetful MySQL Config on a Cheap VPS Turned My NestJS App 10× Slower—and I Fixed It in 30 Minutes

Imagine spending hours polishing a new NestJS feature, only to watch your API lag like a snail when you finally push to production. The culprit? A tiny MySQL setting you missed on a $5 VPS. In this story I’ll show you exactly how I tracked down the problem, fixed it in half an hour, and got my app back to lightning‑fast speed.

Why This Matters

If you’re building SaaS tools, internal dashboards, or any data‑driven service, performance is a make‑or‑break factor. Slow queries cost you:

  • Lost customers who can’t wait for a response.
  • Higher cloud bills because you keep scaling CPU to compensate.
  • Developer frustration and wasted time.

And the worst part? The root cause is often a single mis‑configured MySQL variable that hides behind “it works on my laptop.” Let’s dig into the exact steps to catch that bug before it hurts your users.

Step‑by‑Step Tutorial: Find & Fix the MySQL Bottleneck

  1. Reproduce the slowdown locally

    First, confirm the issue isn’t just network latency. Run a simple benchmark against both your local MySQL instance and the production VPS.

    ab -n 1000 -c 50 http://your‑api.com/users

    If the VPS version is 10× slower, you know the problem lies in the database layer.

  2. Check MySQL version and default config

    Log into the VPS and dump the current settings:

    mysql -u root -p -e "SHOW VARIABLES;" > /tmp/mysql_vars.txt

    Look especially for innodb_buffer_pool_size, query_cache_type, and max_connections.

  3. Spot the missing innodb_buffer_pool_size

    Warning: On a cheap VPS the default buffer pool is often set to 128 MB or even 64 MB, which is nowhere near enough for a production‑grade table set.

    If the value is too low (e.g., 64M), MySQL will constantly flush pages to disk, causing massive latency spikes.

  4. Resize the buffer pool safely

    Allocate 50‑70 % of your RAM to the buffer pool. For a 2 GB VPS, 1 GB is a good starting point.

    # Edit /etc/mysql/my.cnf or /etc/my.cnf
    [mysqld]
    innodb_buffer_pool_size=1G
    

    After saving, restart MySQL:

    sudo systemctl restart mysql

    Verify the new setting:

    mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
  5. Enable the Query Cache (optional but helpful)

    If your workload is read‑heavy, turn on the cache:

    [mysqld]
    query_cache_type=ON
    query_cache_size=64M
    

    Restart once more and watch the cache fill up.

  6. Test again with the same ab command

    You should now see response times drop from ~500 ms per request to under 50 ms — a genuine 10× improvement.

Code Example: NestJS Service Using TypeORM

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly userRepo: Repository<User>,
  ) {}

  async findAll(): Promise<User[]> {
    // Optimized query – selects only needed columns
    return this.userRepo.find({
      select: ['id', 'email', 'createdAt'],
      order: { createdAt: 'DESC' },
    });
  }
}

Tip: Pair the larger buffer pool with selective field queries (as shown) to keep the dataset in memory and avoid full‑table scans.

Real‑World Use Case: SaaS Dashboard

My client runs a multi‑tenant analytics dashboard built on NestJS + TypeORM. After moving to a $5 DigitalOcean droplet, the dashboard’s “Load All Users” page went from 2 seconds to 20 seconds. The culprit was the default 128 MB buffer pool. After the quick fix above:

  • Page load time: 1.8 seconds
  • CPU usage dropped by 45 %
  • Monthly VPS cost stayed at $5 — no need to upgrade.

Results / Outcome

In under 30 minutes I turned a 10× slowdown into a speed‑up that felt like a brand‑new server. The key takeaways:

  1. Never trust default MySQL configs on low‑cost servers.
  2. Allocate 50‑70 % of RAM to innodb_buffer_pool_size.
  3. Run a quick benchmark before and after each change.

Bonus Tips for Ongoing Performance

  • Enable slow query logging. Add slow_query_log=ON and long_query_time=0.5 to catch rogue queries.
  • Use connection pooling. NestJS’s TypeOrmModule.forRoot({extra: {connectionLimit: 20}}) prevents socket thrashing.
  • Monitor with Percona Monitoring. Visual graphs make it easy to spot memory pressure.
  • Consider a managed DB for growth. When you outgrow a cheap VPS, a managed Aurora or CloudSQL instance saves time and headaches.

Monetization (Optional)

If you help clients migrate from cheap VPS setups to scalable, high‑performance stacks, you can charge a one‑time audit fee ($150–$300) plus a monthly retainer for monitoring. The ROI for a $5 server turned $50‑plus per month is a compelling sales pitch.

Performance isn’t a mysterious art; it’s a series of small, verifiable tweaks. The next time your NestJS API feels sluggish, remember the buffer pool—and you’ll save time, money, and a lot of angry users.

No comments:

Post a Comment