Optimize OpenResty performance with advanced caching strategies and Lua optimization

Advanced 45 min May 20, 2026 568 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Configure high-performance caching with Redis integration, implement microcaching strategies, and optimize Lua scripts for memory management in production OpenResty deployments.

Prerequisites

  • Root or sudo access
  • Basic nginx/OpenResty knowledge
  • Redis server
  • Understanding of Lua scripting

What this solves

OpenResty combines nginx with LuaJIT to create a powerful web platform, but default configurations leave significant performance on the table. This tutorial shows you how to implement Redis-backed caching, configure advanced HTTP microcaching, and optimize Lua scripts for memory efficiency in high-traffic production environments.

Step-by-step configuration

Install OpenResty with performance modules

Install OpenResty with Redis and caching modules enabled for maximum performance capabilities.

wget -qO - https://openresty.org/package/pubkey.gpg | sudo apt-key add -
echo "deb http://openresty.org/package/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list
sudo apt update
sudo apt install -y openresty openresty-opm redis-server luarocks
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://openresty.org/package/centos/openresty.repo
sudo dnf install -y openresty openresty-opm redis luarocks

Install Lua Redis and caching libraries

Install essential Lua libraries for Redis connectivity and advanced caching functionality.

sudo /usr/local/openresty/bin/opm get ledgetech/lua-resty-redis-connector
sudo /usr/local/openresty/bin/opm get openresty/lua-resty-redis
sudo /usr/local/openresty/bin/opm get openresty/lua-resty-lrucache
sudo /usr/local/openresty/bin/opm get bungle/lua-resty-template
sudo luarocks install lua-resty-http

Configure Redis for OpenResty caching

Optimize Redis configuration for high-performance caching with OpenResty integration.

maxmemory 1gb
maxmemory-policy allkeys-lru
save ""
tcp-keepalive 300
timeout 0
tcp-backlog 511
maxclients 10000
notify-keyspace-events Ex
sudo systemctl restart redis-server
sudo systemctl enable redis-server

Create optimized OpenResty main configuration

Configure OpenResty with performance-tuned worker processes, connection pooling, and shared memory zones.

worker_processes auto;
worker_rlimit_nofile 65535;
worker_cpu_affinity auto;

error_log /var/log/openresty/error.log warn;
pid /var/run/openresty.pid;

events {
    worker_connections 8192;
    use epoll;
    multi_accept on;
}

http {
    include mime.types;
    default_type application/octet-stream;
    
    # Lua shared memory zones
    lua_shared_dict cache_dict 100m;
    lua_shared_dict locks 10m;
    lua_shared_dict stats 10m;
    
    # Redis connection pool
    lua_socket_pool_size 100;
    lua_socket_keepalive_timeout 60s;
    
    # Performance optimizations
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 30;
    keepalive_requests 1000;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/x-font-ttf font/opentype image/svg+xml;
    
    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
    
    include /usr/local/openresty/nginx/conf/conf.d/*.conf;
}

Create Lua Redis connection module

Build a reusable Lua module for efficient Redis connections with connection pooling and error handling.

sudo mkdir -p /usr/local/openresty/nginx/lua
local redis = require "resty.redis"
local cjson = require "cjson"

local _M = {}

-- Redis connection configuration
local redis_config = {
    host = "127.0.0.1",
    port = 6379,
    timeout = 5000,
    pool_size = 100,
    keepalive = 60000
}

function _M.connect()
    local red = redis:new()
    red:set_timeout(redis_config.timeout)
    
    local ok, err = red:connect(redis_config.host, redis_config.port)
    if not ok then
        ngx.log(ngx.ERR, "Failed to connect to Redis: ", err)
        return nil, err
    end
    
    return red
end

function _M.set_keepalive(red)
    if not red then
        return
    end
    
    local ok, err = red:set_keepalive(redis_config.keepalive, redis_config.pool_size)
    if not ok then
        ngx.log(ngx.ERR, "Failed to set Redis keepalive: ", err)
    end
end

function _M.get_cached(key)
    local red, err = _M.connect()
    if not red then
        return nil, err
    end
    
    local res, err = red:get(key)
    _M.set_keepalive(red)
    
    if not res or res == ngx.null then
        return nil, "cache miss"
    end
    
    local data = cjson.decode(res)
    return data, nil
end

function _M.set_cached(key, value, ttl)
    local red, err = _M.connect()
    if not red then
        return false, err
    end
    
    ttl = ttl or 3600
    local json_value = cjson.encode(value)
    
    local ok, err = red:setex(key, ttl, json_value)
    _M.set_keepalive(red)
    
    if not ok then
        ngx.log(ngx.ERR, "Failed to cache data: ", err)
        return false, err
    end
    
    return true, nil
end

function _M.invalidate(pattern)
    local red, err = _M.connect()
    if not red then
        return false, err
    end
    
    local keys, err = red:keys(pattern)
    if keys and #keys > 0 then
        red:del(unpack(keys))
    end
    
    _M.set_keepalive(red)
    return true, nil
end

return _M

Implement advanced microcaching strategy

Create a multi-layer caching system with shared memory, Redis, and intelligent cache warming.

local lrucache = require "resty.lrucache"
local redis_connector = require "redis_connector"
local cjson = require "cjson"

local _M = {}

-- L1 cache (in-memory LRU)
local l1_cache = lrucache.new(1000)

-- Cache configuration
local cache_config = {
    l1_ttl = 60,        -- 1 minute L1 cache
    l2_ttl = 3600,      -- 1 hour Redis cache
    l3_ttl = 86400,     -- 24 hour fallback cache
    warm_threshold = 300 -- Warm cache when TTL < 5 minutes
}

function _M.generate_key(uri, args)
    local key_parts = {"page", uri}
    
    if args then
        for k, v in pairs(args) do
            if k ~= "_" then  -- Skip cache buster
                table.insert(key_parts, k .. "=" .. v)
            end
        end
    end
    
    return table.concat(key_parts, ":")
end

function _M.get_content(key)
    -- Try L1 cache first
    local l1_data = l1_cache:get(key)
    if l1_data then
        ngx.header["X-Cache-Status"] = "HIT-L1"
        return l1_data.content, l1_data.headers
    end
    
    -- Try Redis cache
    local l2_data, err = redis_connector.get_cached(key)
    if l2_data then
        -- Store in L1 cache
        l1_cache:set(key, l2_data, cache_config.l1_ttl)
        ngx.header["X-Cache-Status"] = "HIT-L2"
        
        -- Check if we need to warm the cache
        if l2_data.expires and (l2_data.expires - ngx.time()) < cache_config.warm_threshold then
            _M.warm_cache_async(key)
        end
        
        return l2_data.content, l2_data.headers
    end
    
    ngx.header["X-Cache-Status"] = "MISS"
    return nil, nil
end

function _M.store_content(key, content, headers, ttl)
    ttl = ttl or cache_config.l2_ttl
    
    local cache_data = {
        content = content,
        headers = headers or {},
        created = ngx.time(),
        expires = ngx.time() + ttl
    }
    
    -- Store in L1 cache
    l1_cache:set(key, cache_data, cache_config.l1_ttl)
    
    -- Store in Redis
    redis_connector.set_cached(key, cache_data, ttl)
end

function _M.warm_cache_async(key)
    -- Background cache warming using ngx.timer
    local ok, err = ngx.timer.at(0, function()
        ngx.log(ngx.INFO, "Warming cache for key: ", key)
        -- Here you would regenerate the content
        -- This is a placeholder for your content generation logic
    end)
    
    if not ok then
        ngx.log(ngx.ERR, "Failed to create cache warming timer: ", err)
    end
end

function _M.invalidate_pattern(pattern)
    -- Clear L1 cache (simple approach - clear all)
    l1_cache:flush_all()
    
    -- Clear Redis pattern
    return redis_connector.invalidate(pattern)
end

function _M.get_stats()
    local stats_key = "cache_stats"
    local stats = ngx.shared.stats:get(stats_key) or {
        hits = 0,
        misses = 0,
        stores = 0
    }
    
    return stats
end

function _M.increment_stat(stat_name)
    local stats_key = "cache_stats"
    local stats = _M.get_stats()
    stats[stat_name] = (stats[stat_name] or 0) + 1
    ngx.shared.stats:set(stats_key, stats)
end

return _M

Configure virtual host with advanced caching

Set up a virtual host that implements the caching strategy with content-specific TTL and intelligent invalidation.

server {
    listen 80;
    server_name example.com;
    
    access_log /var/log/openresty/access.log;
    error_log /var/log/openresty/error.log;
    
    # Rate limiting
    limit_req zone=api burst=20 nodelay;
    
    # Security headers
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    
    location / {
        # Lua caching logic
        access_by_lua_block {
            local cache_manager = require "cache_manager"
            local key = cache_manager.generate_key(ngx.var.uri, ngx.req.get_uri_args())
            
            local content, headers = cache_manager.get_content(key)
            if content then
                -- Set cached headers
                if headers then
                    for k, v in pairs(headers) do
                        ngx.header[k] = v
                    end
                end
                
                cache_manager.increment_stat("hits")
                ngx.say(content)
                ngx.exit(200)
            else
                cache_manager.increment_stat("misses")
                ngx.ctx.cache_key = key
            end
        }
        
        # Your application backend
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Cache response from backend
        header_filter_by_lua_block {
            if ngx.status == 200 then
                ngx.ctx.should_cache = true
                ngx.ctx.response_headers = ngx.resp.get_headers()
            end
        }
        
        body_filter_by_lua_block {
            if ngx.ctx.should_cache and not ngx.ctx.cached then
                local cache_manager = require "cache_manager"
                local body = ngx.arg[1]
                
                if ngx.arg[2] then  -- Last chunk
                    ngx.ctx.cached = true
                    
                    -- Determine TTL based on content type
                    local ttl = 3600  -- default 1 hour
                    local content_type = ngx.header.content_type or ""
                    
                    if string.match(content_type, "application/json") then
                        ttl = 300  -- 5 minutes for API responses
                    elseif string.match(content_type, "text/html") then
                        ttl = 1800  -- 30 minutes for HTML
                    elseif string.match(content_type, "image/") then
                        ttl = 86400  -- 24 hours for images
                    end
                    
                    cache_manager.store_content(ngx.ctx.cache_key, body, ngx.ctx.response_headers, ttl)
                    cache_manager.increment_stat("stores")
                end
            end
        }
    }
    
    # Cache invalidation endpoint
    location ~ ^/admin/cache/invalidate/(.+)$ {
        access_by_lua_block {
            -- Simple IP-based access control
            local allowed_ips = {"127.0.0.1", "203.0.113.10"}
            local client_ip = ngx.var.remote_addr
            local allowed = false
            
            for _, ip in ipairs(allowed_ips) do
                if ip == client_ip then
                    allowed = true
                    break
                end
            end
            
            if not allowed then
                ngx.status = 403
                ngx.say("Forbidden")
                ngx.exit(403)
            end
            
            local cache_manager = require "cache_manager"
            local pattern = ngx.var[1]
            
            local ok, err = cache_manager.invalidate_pattern(pattern)
            if ok then
                ngx.say('Cache invalidated for pattern: ' .. pattern)
            else
                ngx.status = 500
                ngx.say('Error invalidating cache: ' .. (err or 'unknown error'))
            end
            ngx.exit(ngx.status)
        }
    }
    
    # Cache statistics endpoint
    location /admin/cache/stats {
        access_by_lua_block {
            local cache_manager = require "cache_manager"
            local stats = cache_manager.get_stats()
            
            ngx.header.content_type = "application/json"
            local cjson = require "cjson"
            ngx.say(cjson.encode(stats))
            ngx.exit(200)
        }
    }
}

Optimize Lua memory management

Configure LuaJIT for optimal memory usage and garbage collection tuning for high-performance applications.

local _M = {}

-- Memory optimization settings
local gc_config = {
    gc_step_size = 200,
    gc_threshold = 1024 * 1024,  -- 1MB
    max_string_cache = 10000
}

function _M.init_worker()
    -- Set LuaJIT memory limits
    if jit then
        jit.opt.start("maxtrace=10000", "maxrecord=20000", "minstitch=3")
    end
    
    -- Initialize periodic GC
    local ok, err = ngx.timer.every(30, _M.periodic_gc)
    if not ok then
        ngx.log(ngx.ERR, "Failed to create GC timer: ", err)
    end
end

function _M.periodic_gc()
    local before = collectgarbage("count")
    collectgarbage("step", gc_config.gc_step_size)
    local after = collectgarbage("count")
    
    if before > gc_config.gc_threshold then
        collectgarbage("collect")
        ngx.log(ngx.INFO, "Full GC executed, freed: ", (before - after), "KB")
    end
end

function _M.optimize_table(t)
    -- Pre-allocate table if we know the size
    if type(t) == "table" then
        local count = 0
        for _ in pairs(t) do
            count = count + 1
        end
        
        if count > 100 then
            -- For large tables, consider using weak references
            local mt = getmetatable(t) or {}
            mt.__mode = "v"  -- Weak values
            setmetatable(t, mt)
        end
    end
    return t
end

function _M.string_intern(str)
    -- Simple string interning to reduce memory usage
    local cache = ngx.shared.cache_dict
    local cached_str = cache:get("str_" .. str)
    
    if cached_str then
        return cached_str
    end
    
    -- Only cache small strings to avoid memory bloat
    if #str < 1000 then
        cache:set("str_" .. str, str, 3600)
    end
    
    return str
end

function _M.pool_objects()
    -- Object pooling for frequently created/destroyed objects
    local pool = {}
    
    return {
        get = function()
            local obj = table.remove(pool)
            if not obj then
                obj = {}
            end
            return obj
        end,
        
        put = function(obj)
            -- Clear object data
            for k in pairs(obj) do
                obj[k] = nil
            end
            
            -- Return to pool if not too large
            if #pool < 100 then
                table.insert(pool, obj)
            end
        end
    }
end

return _M

Create initialization script

Set up the init_worker_by_lua block to initialize memory optimization and connection pools.

-- Initialize memory optimizer
local memory_optimizer = require "memory_optimizer"
memory_optimizer.init_worker()

-- Pre-compile templates if using lua-resty-template
local template = require "resty.template"
template.caching(true)
template.load_lua_template = true

-- Initialize shared dictionaries
local cache_dict = ngx.shared.cache_dict
local stats = ngx.shared.stats

-- Set up global error handling
_G.handle_error = function(err)
    ngx.log(ngx.ERR, "Lua error: ", err)
    ngx.status = 500
    ngx.say("Internal Server Error")
    ngx.exit(500)
end

-- Initialize Redis connection pool
local redis_connector = require "redis_connector"
local red = redis_connector.connect()
if red then
    redis_connector.set_keepalive(red)
    ngx.log(ngx.INFO, "Redis connection pool initialized")
else
    ngx.log(ngx.WARN, "Failed to initialize Redis connection pool")
end

Update main nginx configuration

Add the initialization script and performance directives to the main configuration.

worker_processes auto;
worker_rlimit_nofile 65535;
worker_cpu_affinity auto;

# Add to existing config
init_worker_by_lua_file /usr/local/openresty/nginx/conf/init.lua;

# Lua package path
lua_package_path '/usr/local/openresty/nginx/lua/?.lua;;';

# Lua code cache (disable only in development)
lua_code_cache on;

# DNS resolver for dynamic backends
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

</code><h2><code>Rest of existing configuration...</code></h2></pre>
</div>

<div class="step">
### Enable and start services
<p>Start OpenResty and Redis with the optimized configuration.</p>
<pre class="terminal"><code>sudo systemctl enable openresty
sudo systemctl restart openresty
sudo systemctl status openresty

Verify your setup

Test the caching system and performance optimizations to ensure everything works correctly.

# Test basic functionality
curl -H "Host: example.com" http://localhost/

# Check cache headers
curl -I -H "Host: example.com" http://localhost/

# Test cache statistics
curl http://localhost/admin/cache/stats

# Test cache invalidation
curl http://localhost/a

Automated install script

Run this to automate the entire setup

Prefere não gerir isto sozinho?

Gerimos a infraestrutura de empresas que dependem do tempo de atividade. Totalmente gerida, com um contacto fixo que conhece o seu ambiente.

Tem um contacto fixo que conhece o seu ambiente

Roterdão 02:24 · acessível por mensagem, sem formulário de tickets