Lecture 6: Couchbase Server & Varnish Cache

Open visualization in new tab

Learning Objectives

Prerequisites

Section 1: Deep Dive into Couchbase Server

Couchbase Server Setup and Architecture

Welcome to our exploration of Couchbase Server, a tool that often blurs the line between a traditional database and a high-performance cache. Unlike simpler key-value stores like Redis or Memcached, Couchbase is a full-featured, distributed NoSQL document database built with a "memory-first" architecture. This design makes it exceptionally fast for read and write operations, positioning it as a powerful solution for complex caching scenarios, session management, and even as a primary data store (Couchbase, Inc., 2023).

Core Architectural Concepts

To effectively use Couchbase, you must understand its fundamental components. It’s more than just a key-value store; it’s a distributed system designed for scalability and high availability.

Hardware Requirements

Couchbase is more resource-intensive than lightweight caches due to its powerful feature set and Java-based architecture. For a small, on-premise installation focused on session caching for a handful of users, the minimum requirements are manageable but notable (Jainandunsing, 2025).

The 4 GB RAM requirement is critical. Couchbase's performance stems from its ability to hold the "working set" of data in memory. The operating system also requires a significant amount of memory to function smoothly, so allocating at least 2 GB for Couchbase itself and 2 GB for the OS is a safe starting point.

Installation and Configuration on Ubuntu

Let's walk through the process of setting up Couchbase Server Community Edition on an Ubuntu server. These steps prepare your system and install the software from Couchbase's official repository.

  1. Update System and Install Dependencies: First, ensure your system is up-to-date and has the necessary tools like `curl` and `gnupg2`.
    sudo apt update && sudo apt upgrade -y
    sudo apt install curl gnupg2 lsb-release -y
  2. Add the Couchbase Repository: You need to add Couchbase's package repository to your system's sources to install the server. This involves downloading their GPG key to verify the package authenticity and then adding the repository URL.
    wget https://packages.couchbase.com/ubuntu/couchbase.key
    sudo apt-key add couchbase.key
    CODENAME=$(lsb_release -cs)
    echo "deb https://packages.couchbase.com/ubuntu ${CODENAME} main" | sudo tee /etc/apt/sources.list.d/couchbase.list
  3. Install Couchbase Server: With the repository added, you can now update your package list again and install the `couchbase-server-community` package.
    sudo apt update
    sudo apt install couchbase-server-community -y

After installation, the Couchbase service starts automatically. The next steps involve configuring the cluster through its user-friendly web console.

Initial Setup via Web Console

Access the Web UI in your browser at http://<your-server-ip>:8091. You'll be greeted by a setup wizard.

  1. Cluster Setup: Create a new cluster. Give it a name (e.g., `SessionCluster`), and create an administrator account with a strong password.
  2. Services: For a caching-focused setup, you only need the Data service. You can also enable the Query and Index services if you plan to query the session data using N1QL, but they can be skipped to minimize RAM usage. Uncheck Search, Analytics, and Eventing.
  3. RAM Quotas: This is a crucial step. You'll set the total RAM available to the Data service for this node. For our minimum spec machine with 4 GB total RAM, a conservative value is 1024 MB (1 GB), leaving plenty for the OS and other services (Jainandunsing, 2025).
  4. Create a Bucket: This will be the container for your session data.
    • Name: `user_sessions`
    • Type: Couchbase (provides persistence options) or Ephemeral (in-memory only, faster, ideal for pure caching). Let's choose Couchbase for flexibility.
    • Bucket RAM Quota: Allocate a portion of the service's RAM to this bucket, for example, 512 MB.
    • Eviction Policy: This determines what happens when the bucket's RAM is full. Value Ejection evicts only the value of a key-value pair, keeping the key and metadata in memory. This is faster for lookups where the item is not found. Full Ejection evicts the key, value, and all metadata, freeing up more memory but making lookups for non-existent items slower. For session caching, Value Ejection is often preferred.
    • Enable TTL: This is vital. Check this box to enable Time-To-Live on documents. You can set a default TTL for the entire bucket, for example, 3600 seconds (1 hour). This ensures old sessions are automatically purged.

Optimizing for Caching Behavior

While Couchbase has a persistent storage engine, you can configure it to behave like a pure in-memory cache. With an Ephemeral bucket, this is the default. With a Couchbase bucket, the primary strategy is to rely on aggressive TTLs and the memory manager. By setting a short TTL on all session documents, you ensure they expire and are removed from memory without ever needing to be written to disk in a meaningful way. In the bucket settings, you can also set the number of replicas to 0 if you are running a single-node cluster and don't need data redundancy, further reducing overhead.

Example: Connecting and Caching a Session with Python

Once Couchbase is running, your application connects via an SDK. Here's a Python example demonstrating how to connect to the `user_sessions` bucket and store a session object with a 1-hour TTL.

from couchbase.cluster import Cluster, ClusterOptions
from couchbase.auth import PasswordAuthenticator
from couchbase.exceptions import DocumentNotFoundException
from datetime import timedelta

# --- Connection Configuration ---
# Connect to the cluster, authenticating with the admin credentials you created.
cluster = Cluster("couchbase://localhost", ClusterOptions(
  PasswordAuthenticator("admin", "your_strong_password")))

# Open a specific bucket.
bucket = cluster.bucket("user_sessions")

# Get a handle to the default collection within the bucket.
collection = bucket.default_collection()

# --- Caching a Session ---
session_id = "user:12345"
session_data = {
    "username": "jdoe",
    "last_login": "2023-10-27T10:00:00Z",
    "permissions": ["read", "write"]
}

# Use upsert to create or replace the document.
# Set a specific TTL for this document of 1 hour.
collection.upsert(session_id, session_data, ttl=timedelta(hours=1))
print(f"Session '{session_id}' cached successfully.")

# --- Retrieving a Session ---
try:
    result = collection.get(session_id)
    print(f"Retrieved session data: {result.content_as[dict]}")
except DocumentNotFoundException:
    print(f"Session '{session_id}' not found in cache. It may have expired.")

Did You Know?

Couchbase was formed from the merger of two different NoSQL projects: Membase and CouchDB. It inherited the high-performance, in-memory architecture and Memcached compatibility from Membase, and the document-oriented, JSON-native storage model from CouchDB. This unique combination is what gives Couchbase its hybrid power as both a fast cache and a persistent document database.

Section 1 Summary

Reflective Questions

  1. Given its higher resource requirements, in what specific scenarios would you justify choosing Couchbase over a lighter cache like Redis or Memcached for session management?
  2. What are the security implications of exposing the Couchbase Web UI (port 8091) to the public internet? What steps should be taken to secure it?
  3. How would the choice between "Value Ejection" and "Full Ejection" eviction policies impact your application's performance if your cache hit rate was only 50%?

Section 2: Configuring Varnish Cache

Varnish Cache: The HTTP Accelerator

Shifting gears dramatically, we now turn to Varnish Cache. If Couchbase is a versatile database that can cache, Varnish is a highly specialized tool built for one purpose: to make your web applications fly. Varnish is a reverse proxy HTTP accelerator. It sits in front of your web server (like Apache or NGINX) and caches the responses it generates. The next time the same request comes in, Varnish serves the cached copy directly from memory without ever bothering your backend server. This can result in performance improvements of several orders of magnitude (Kamp, 2010).

It's critical to understand that Varnish works at the HTTP layer. It caches entire HTTP responses—headers and body included. It is not a general-purpose object cache. As Jainandunsing (2025) notes, it's "best used for caching HTTP content, API responses, or web pages — not typically for user session data." We'll see how to make it *aware* of sessions, but it won't *store* them.

Varnish Architecture

The VCL State Machine

Understanding the request lifecycle in Varnish is crucial. A request flows through a series of VCL subroutines, which you can customize. The most important ones are:

  1. `vcl_recv` (Receive): This is the first stop for a new request. It's where you decide what to do. You can modify the request, look for cookies, and decide whether to serve from cache (`hash`), fetch from the backend (`pass`), or throw an error (`error`).
  2. `vcl_hash` (Hash): If `vcl_recv` returns `hash` (the default), this subroutine creates a hash key based on the request URL and host. This hash is used to look up the object in the cache.
  3. `vcl_hit` / `vcl_miss` (Hit/Miss): If the object is found in cache, `vcl_hit` is called. If not, `vcl_miss` is called. In `vcl_miss`, the request is typically sent to the backend.
  4. `vcl_backend_fetch` (Fetch): This is called just before sending the request to the backend server. You can modify the request being sent to the backend here.
  5. `vcl_backend_response` (Backend Response): After the backend sends a response, this subroutine is executed. Here you can modify the response from the backend before it's stored in the cache. This is a great place to set the TTL of the object and strip any user-specific cookies.
  6. `vcl_deliver` (Deliver): This is the last stop before the response is sent to the client. You can modify the final response headers here (e.g., add debugging headers).

Hardware Requirements

Varnish is exceptionally lightweight, a direct result of its focused design and C implementation.

This minimal footprint makes Varnish suitable for running on small virtual machines or even alongside your web server on the same machine for smaller applications (Jainandunsing, 2025).

Installation and Configuration on Ubuntu

Setting up Varnish is straightforward.

  1. Install Varnish: The package is available in the default Ubuntu repositories.
    sudo apt update
    sudo apt install varnish -y
  2. Configure the Service Port: By default, Varnish listens on port 6081. For a live website, you want it to handle traffic on port 80. You must edit the systemd service file to change this.
    sudo nano /lib/systemd/system/varnish.service
    Find the `ExecStart` line and modify the `-a` parameter. This line also controls how much memory is allocated to the cache via the `-s` parameter (storage engine). Here, we allocate 256 MB.
    ExecStart=/usr/sbin/varnishd \
        -a :80 \
        -T localhost:6082 \
        -f /etc/varnish/default.vcl \
        -s malloc,256m
    After saving the file, you must reload the systemd daemon for the changes to take effect.
    sudo systemctl daemon-reload
  3. Configure the Backend: You need to tell Varnish where your actual web application is running. This is done in the main VCL file.
    sudo nano /etc/varnish/default.vcl
    At the top of the file, define your backend. Let's assume your application server is running on the same machine on port 8080.
    backend default {
        .host = "127.0.0.1";
        .port = "8080";
    }
  4. Handle User Sessions (Bypass Cache): This is the most critical part for dynamic sites. Varnish should not cache content for logged-in users. The standard way to achieve this is to check for a session cookie in `vcl_recv`. If the cookie exists, we `return (pass)`, which tells Varnish to bypass the cache and send the request directly to the backend.
    sub vcl_recv {
        # Don't cache requests for logged-in users
        if (req.http.Cookie ~ "session_id=") {
            return (pass);
        }
        # Don't cache backend admin areas
        if (req.url ~ "^/admin") {
            return (pass);
        }
        return (hash); # Proceed to cache lookup
    }
  5. Restart and Enable Varnish: Finally, restart the service to apply all your changes and enable it to start on boot.
    sudo systemctl restart varnish
    sudo systemctl enable varnish
    You can verify it's working by using `curl -I http://your-server-ip` and looking for headers like `X-Varnish` and `Age`.

Example: Complete `default.vcl` for a Typical Web App

This VCL file demonstrates a basic but robust configuration for a typical website. It defines the backend, bypasses the cache for logged-in users, and cleans up cookies on cacheable assets.

# VCL version 4.1
vcl 4.1;

# Define the backend application server.
backend default {
    .host = "127.0.0.1";
    .port = "8080";
}

# Code executed when a request is received.
sub vcl_recv {
    # Pass requests with session cookies directly to the backend.
    if (req.http.Cookie ~ "session_id=" || req.http.Authorization) {
        return (pass);
    }

    # Normalize query strings for better cache hit rate.
    if (req.url ~ "(\?|&)(utm_source|gclid)=") {
        set req.url = regsub(req.url, "(\?|&)(utm_source|gclid)=[^&]+", "");
        set req.url = regsub(req.url, "\?$", "");
    }
    
    return (hash);
}

# Code executed after fetching a response from the backend.
sub vcl_backend_response {
    # If the backend sends a response that sets a cookie,
    # but the page is cacheable, we might want to strip the cookie.
    # Be careful with this logic, it depends on your application.
    if (beresp.ttl > 0s) {
        unset beresp.http.Set-Cookie;
    }
    
    # Set a default cache TTL of 1 hour for cacheable responses.
    set beresp.ttl = 1h;
    
    return (deliver);
}

# Code executed just before delivering the response to the client.
sub vcl_deliver {
    # Add a debug header to see if the object was a cache hit or miss.
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT";
    } else {
        set resp.http.X-Cache = "MISS";
    }
    return (deliver);
}

Did You Know?

Varnish was created by Poul-Henning Kamp, a core developer of the FreeBSD operating system. His design philosophy for VCL was deliberate: instead of using a simple declarative format like XML or YAML, he created a language that gives developers full programmatic control over the HTTP request flow. By compiling VCL to highly optimized C code at runtime, Varnish avoids the performance overhead of interpreters, making it one of the fastest software components in any web stack.

Section 2 Summary

Reflective Questions

  1. Why is VCL a compiled language rather than a simple interpreted configuration file like those used by NGINX or Apache? What are the pros and cons of this approach?
  2. What is the most significant security risk of misconfiguring VCL? Provide an example of a rule that could accidentally expose private user data.
  3. In what scenarios might you place Varnish *behind* another reverse proxy like NGINX, instead of directly facing the internet? (Hint: Think about SSL/TLS termination).

Section 3: Comparative Use Cases and Architectures

Choosing the Right Tool for the Job

We've explored two powerful but fundamentally different caching technologies. Couchbase Server is a data-centric, application-layer cache and database. Varnish Cache is a network-centric, HTTP-layer cache. Understanding their distinct strengths and weaknesses is crucial for designing an efficient and scalable system architecture. Trying to use one for the other's job often leads to complex, inefficient, and brittle solutions.

Direct Comparison: Couchbase vs. Varnish

Aspect Couchbase Server Varnish Cache
Cache Type General-purpose, distributed object/document cache Reverse Proxy HTTP Accelerator
Data Type JSON documents, binary data, key-value pairs Full HTTP responses (headers and body)
Primary Use Case Application-level caching, user session stores, user profiles, real-time analytics, primary database Accelerating websites/APIs, full-page caching, reducing load on backend web servers
Position in Stack Accessed by application code via SDKs (sits behind the application) Sits in front of the entire web application stack (edge cache)
Hardware Footprint Heavier (min. 2 cores, 4 GB RAM), as it's a distributed database system (Jainandunsing, 2025) Extremely lightweight (min. 1 core, 512 MB RAM) (Jainandunsing, 2025)
Cache Invalidation Directly controlled by the application (e.g., delete a key) or via TTL Via HTTP methods like PURGE, BANS, or by setting short TTLs

Use Case 1: User Session Management

Use Case 2: Caching Public REST API Responses

Use Case 3: A Hybrid Architecture for a High-Traffic E-commerce Site

The most powerful systems often use both tools together, creating a multi-layered caching strategy that leverages the strengths of each.

  1. The Edge Layer (Varnish): Varnish sits at the public-facing edge. Its job is to handle the massive influx of traffic from anonymous users.
    • It performs Full Page Caching (FPC) for product detail pages, category listings, and the homepage. These pages are identical for all anonymous users and can be served from memory at lightning speed.
    • It caches all static assets: images, CSS, and JavaScript files, with very long TTLs.
    • It immediately bypasses the cache for any user with a session cookie or for requests to `/cart`, `/checkout`, or `/account` pages.
  2. The Application Layer (Python/Java/PHP App Server): This layer only receives requests that Varnish couldn't handle—typically from logged-in users or for dynamic actions.
  3. The Data Cache Layer (Couchbase): The application servers are stateless; all state is managed in Couchbase.
    • Session Management: When a user logs in, their entire session object (including cart contents) is stored in Couchbase.
    • Object Caching: The application caches frequently accessed database query results, such as user profiles, product inventory lookups, or configuration settings. This reduces the load on the primary relational database.
    • Real-time Data: Couchbase can even handle real-time inventory updates or personalized recommendations, providing sub-millisecond latency that a traditional database cannot match.

This hybrid model is incredibly effective. Varnish absorbs the bulk of the traffic (often 80-90%), protecting the application servers. The application servers then rely on Couchbase for high-speed data access for the remaining dynamic requests, protecting the slower, persistent database. This is a classic example of using the right tool for each specific problem in the stack.

Diagram: Hybrid Caching Architecture Flow

Consider the request flow for a user viewing a product page in our hybrid e-commerce site:

Scenario A: Anonymous User (Cache HIT)

Client Request for /product/widget-pro
     │
     ▼
[ Varnish Cache ] ───> Is /product/widget-pro in cache? YES.
     │
     ▼
Client receives cached page instantly.
(Application Server and Couchbase are never touched)
            

Scenario B: Logged-in User (Cache MISS/PASS)

Client Request for /product/widget-pro (with session_id cookie)
     │
     ▼
[ Varnish Cache ] ───> Request has session cookie? YES. return(pass).
     │
     ▼
[ Application Server ] ───> Renders page. Needs session data.
     │
     ▼
[ Couchbase Server ] ───> Fetches session data for user.
     │
     ▼
[ Application Server ] ───> Returns personalized page (e.g., "Hello, Jane").
     │
     ▼
[ Varnish Cache ] ───> Forwards response to Client. Does not cache it.
     │
     ▼
Client receives personalized page.
            

Did You Know?

Some of the world's highest-traffic websites rely on Varnish to stay online. During major news events, sites like The New York Times and The Guardian have used Varnish to serve billions of requests from cache, allowing their backend infrastructure to handle the essential task of content creation rather than being overwhelmed by reader traffic. Its ability to serve "stale" content during a backend failure is another key feature for maintaining high availability.

Section 3 Summary

Reflective Questions

  1. Design a caching strategy for a high-traffic blog. Which tool (Couchbase, Varnish, or both) would you use for caching published articles, user comments, and user login information? Justify your choices.
  2. Cache invalidation is a famously hard problem. Describe how you would invalidate a cached article page in Varnish when the blog author publishes an update. (Hint: Research Varnish `PURGE` or `BAN`).
  3. How might Couchbase's Cross-Datacenter Replication (XDCR) feature fundamentally change its role in a global application compared to Varnish? Could Varnish be used in a globally distributed manner as well?

Glossary

Bucket
In Couchbase, a logical container for data, similar to a database in a relational system.
Eviction Policy
A rule that determines what data is removed from a cache when it runs out of memory.
HTTP Accelerator
A system, like Varnish, that sits in front of a web server to reduce response time and server load by caching content.
Look-aside Cache
A caching pattern where the application code is responsible for checking the cache before fetching data from the primary data source.
N1QL (Non-first Normal Form Query Language)
Couchbase's SQL-like query language for querying JSON documents.
Reverse Proxy
A server that sits in front of one or more web servers, forwarding client requests to the appropriate server. Varnish is a type of reverse proxy.
TTL (Time-To-Live)
A mechanism that limits the lifespan of data in a cache. When the TTL expires, the data is automatically removed.
vBucket (Virtual Bucket)
In Couchbase, a logical partition of data used to shard and distribute data across nodes in a cluster.
VCL (Varnish Configuration Language)
A domain-specific language used to configure Varnish's request handling logic.

References

Brown, A. (2022). Designing Data-Intensive Applications. O'Reilly Media.

Couchbase, Inc. (2023). Couchbase Server 7.1 Architecture Guide. Couchbase Press.

Jainandunsing, K. (2025). Caching servers hardware requirements & software configurations.

Kamp, P. H. (2010). VCL - The Varnish Configuration Language. ACM Queue, 8(9), 30-38.

Vogels, W. (2007). Eventually consistent. ACM Queue, 5(3), 14-19.


Back to Course Index