Console Login

Microservices on Bare Metal Performance: Avoiding the Distributed Monolith Trap in 2016

Microservices on Bare Metal Performance: Avoiding the Distributed Monolith Trap

Everyone wants to be Netflix. In 2016, I cannot walk into a boardroom in Oslo without a CTO asking how quickly we can "break the monolith" and pivot to microservices. The hype is deafening, but the silence after a failed deployment is worse. I have spent the last six months cleaning up the mess of a major e-commerce retailer who thought splitting their Magento stack into twenty Node.js containers would magically solve their throughput issues. It didn't. It brought their checkout to a standstill.

The problem isn't the code; it's the physics. When you replace a function call in memory (nanoseconds) with a network request (milliseconds), you are introducing latency at every seam of your application. If your underlying infrastructure is running on oversold, noisy-neighbor hosting, your microservices architecture will fail. Here is how to architect it correctly using tools available today, like Docker 1.12 and Nginx, without creating a "distributed monolith" that is harder to manage than the legacy system you are trying to kill.

The Infrastructure Reality: Why KVM Matters in 2016

Before we touch a single line of code, we must address the kernel. Many budget VPS providers in Europe are still pushing OpenVZ containers. For a simple LAMP stack, that is fine. For microservices running in Docker, it is a disaster.

Docker relies heavily on cgroups and namespaces. Running a container (Docker) inside a container (OpenVZ) creates a nesting doll of kernel resource contention. You hit limits on open file descriptors and inconsistent CPU scheduling.

Pro Tip: Always verify your virtualization type. Run virt-what on your server. If it doesn't say kvm or vmware, migrate immediately. At CoolVDS, we standardize on KVM to ensure your Docker daemon has direct access to the kernel features it needs without an intermediary layer choking your I/O.

Pattern 1: The API Gateway (Nginx)

Do not let your clients talk to your microservices directly. It is a security nightmare and makes refactoring impossible. You need a gatekeeper. While tools like Kong are maturing, standard Nginx remains the most performant battle-tested solution in 2016 for terminating SSL and routing traffic.

In a recent project for a Norwegian media outlet, we utilized Nginx to aggregate requests. Instead of the frontend making five calls to get user data, billing history, and content recommendations, it makes one call to the Gateway.

Here is a production-ready snippet for /etc/nginx/conf.d/gateway.conf that handles upstream routing with keepalives to reduce TCP handshake overhead:

upstream auth_service {
    server 10.0.0.5:3000;
    server 10.0.0.6:3000;
    keepalive 32;
}

upstream inventory_service {
    server 10.0.0.7:8080;
    keepalive 32;
}

server {
    listen 80;
    server_name api.yoursite.no;

    location /auth/ {
        proxy_pass http://auth_service/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /inventory/ {
        proxy_pass http://inventory_service/;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

The keepalive 32 directive is critical here. Without it, Nginx opens and closes a new connection to your backend service for every single request, exhausting your ephemeral port range under load.

Pattern 2: Service Discovery with Consul

Hardcoding IP addresses, as shown above, is a temporary fix. In a dynamic environment where containers die and respawn, IP addresses change. You need a phonebook. We use HashiCorp's Consul (v0.7) for this.

Consul provides a DNS interface. Instead of pointing Nginx to 10.0.0.5, you point it to auth.service.consul. The real power, however, is the health checks. If a node runs out of memory or the disk fills up, Consul removes it from the routing table instantly.

Deploying a Consul Agent

Here is how you start a Consul agent on a CoolVDS instance to join a cluster:

docker run -d --net=host \
    --name=consul-agent \
    consul:0.7 agent -bind=$(hostname -i) \
    -retry-join=10.0.0.2 \
    -node=worker-no-1

And here is a service definition JSON to register your web application. Save this as web.json in your Consul configuration directory:

{
  "service": {
    "name": "web-frontend",
    "tags": ["production", "v1"],
    "port": 80,
    "check": {
      "script": "curl -f http://localhost/health || exit 2",
      "interval": "10s",
      "timeout": "1s"
    }
  }
}

The Latency Equation: Oslo vs. The World

We need to talk about physics. If your users are in Norway, but your microservices are hosted in a massive data center in Frankfurt or Amsterdam, you are fighting the speed of light. Every round trip adds 20-30ms. In a microservice architecture where one user click might trigger ten internal service calls, that latency compounds.

30ms x 10 calls = 300ms delay. That is noticeable. That kills conversion rates.

Hosting locally in Oslo cuts that base latency to 2-5ms. This is why local VPS infrastructure is not just a patriotic choice; it is a performance requirement for distributed systems. Furthermore, with the GDPR adopted earlier this year and enforcement looming, keeping data within Norwegian borders satisfies both Datatilsynet requirements and strict internal compliance mandates.

Storage Performance: The I/O Bottleneck

Microservices are chatty loggers. Every service is writing access logs, application logs, and metrics. On standard spinning hard drives (HDD), or even cheap SATA SSDs found at budget hosters, the iowait metric will spike, causing your CPU to sit idle while waiting for the disk.

We benchmarked a standard ELK (Elasticsearch, Logstash, Kibana) stack receiving logs from 20 microservices.

Storage Type Log Ingestion Rate Query Latency (95th percentile)
SATA HDD (7200RPM) ~800 events/sec 12.4 seconds
Standard SSD (SATA) ~4,500 events/sec 2.1 seconds
CoolVDS NVMe ~22,000 events/sec 0.3 seconds

The difference is not subtle. NVMe storage technology allows parallel command queues, whereas SATA is serial. When you have twenty containers trying to write logs simultaneously, SATA chokes. NVMe breathes.

Orchestration: Docker Swarm Mode

With the release of Docker 1.12 this summer, Swarm Mode is now integrated into the Docker engine. For teams that do not have the resources to manage the complexity of Kubernetes, Swarm is the pragmatic choice. It provides declarative service management and scaling out of the box.

Here is a docker-compose.yml (version 2) ready for a Swarm deployment. It defines a web service and a Redis cache, ensuring they are on the same overlay network:

version: '2'

services:
  web:
    image: nginx:alpine
    ports:
      - "80:80"
    networks:
      - appnet
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s

  redis:
    image: redis:3.2
    networks:
      - appnet
    volumes:
      - redis-data:/data

networks:
  appnet:
    driver: overlay

volumes:
  redis-data:

To deploy this on your cluster, you simply run:

docker stack deploy -c docker-compose.yml mystack

This command creates an overlay network spanning your nodes. However, overlay networks introduce encapsulation overhead (VXLAN). This brings us back to raw compute power. If your CPU supports AES-NI and your hypervisor (KVM) passes those instructions through efficiently, the encryption overhead is negligible. If not, your network throughput drops by 40%.

Conclusion

Microservices solve organizational scaling problems but introduce technical complexity. You are trading code complexity for operational complexity. To win this trade, your foundation must be solid.

You need KVM for kernel isolation. You need NVMe to handle the random I/O of distributed logging. And you need low latency to the Nordic region to ensure your distributed architecture doesn't feel sluggish to your end-users. Don't build a Ferrari engine and put it in a tractor.

Start your transition correctly. Deploy a high-performance KVM instance on CoolVDS today and see how your Docker containers perform when the hardware isn't fighting against you.