FiveM Server Performance Optimization

Learn proven techniques to optimize your FiveM server performance, reduce lag, and provide a smooth gaming experience for your players.

📅 January 10, 2024⏱️ 12 min read🏷️ Performance

Server performance is crucial for maintaining a healthy FiveM community. Poor performance leads to player frustration, increased disconnections, and ultimately, server abandonment. This guide covers comprehensive optimization strategies to keep your server running smoothly.

⚠️ Warning: Poor server performance can cause significant player churn. Studies show that servers with FPS drops below 30 experience 40% higher player disconnection rates.

1. Server Resources and Hardware

Recommended Server Specifications

Small Server (16-32 players)

  • CPU: 4-6 cores @ 3.0+ GHz
  • RAM: 8-16 GB DDR4
  • Storage: SSD 100+ GB
  • Network: 1 Gbps connection

Large Server (64+ players)

  • CPU: 8-12 cores @ 3.5+ GHz
  • RAM: 32-64 GB DDR4
  • Storage: NVMe SSD 500+ GB
  • Network: 10 Gbps connection

Operating System Optimization

# Linux server optimization (Ubuntu/Debian)

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install performance monitoring tools
sudo apt install htop iotop nethogs -y

# Configure kernel parameters for better performance
echo 'net.core.rmem_max = 134217728' | sudo tee -a /etc/sysctl.conf
echo 'net.core.wmem_max = 134217728' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_rmem = 4096 87380 134217728' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_wmem = 4096 65536 134217728' | sudo tee -a /etc/sysctl.conf

# Apply changes
sudo sysctl -p

2. Script Optimization Techniques

Thread Optimization

❌ Bad Practice: Using Citizen.Wait(0) in server scripts causes unnecessary CPU usage.

✅ Good Practice: Use appropriate wait times based on the operation frequency needed.

-- ❌ Inefficient server script
Citizen.CreateThread(function()
    while true do
        -- Heavy operation running every frame
        ProcessAllPlayers()
        Citizen.Wait(0) -- Too frequent for server
    end
end)

-- ✅ Optimized server script
Citizen.CreateThread(function()
    while true do
        -- Process players every 5 seconds
        ProcessAllPlayers()
        Citizen.Wait(5000) -- Appropriate interval
    end
end)

-- ✅ Client UI script (can use Wait(0))
Citizen.CreateThread(function()
    while true do
        -- Update UI elements
        DrawHUD()
        Citizen.Wait(0) -- OK for client UI
    end
end)

Event Optimization

-- ❌ Inefficient event handling
RegisterNetEvent('updatePlayerPosition')
AddEventHandler('updatePlayerPosition', function(x, y, z)
    -- Update database every position change
    MySQL.Async.execute('UPDATE players SET x=@x, y=@y, z=@z WHERE id=@id', {
        ['@x'] = x, ['@y'] = y, ['@z'] = z, ['@id'] = source
    })
end)

-- ✅ Optimized with batching
local positionCache = {}
local lastSave = {}

RegisterNetEvent('updatePlayerPosition')
AddEventHandler('updatePlayerPosition', function(x, y, z)
    local playerId = source
    positionCache[playerId] = {x = x, y = y, z = z}
    lastSave[playerId] = GetGameTimer()
end)

-- Save positions every 30 seconds
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(30000)
        for playerId, pos in pairs(positionCache) do
            if GetGameTimer() - lastSave[playerId] > 30000 then
                MySQL.Async.execute('UPDATE players SET x=@x, y=@y, z=@z WHERE id=@id', {
                    ['@x'] = pos.x, ['@y'] = pos.y, ['@z'] = pos.z, ['@id'] = playerId
                })
                positionCache[playerId] = nil
            end
        end
    end
end)

Memory Management

  • Use local variables: Always use local for variables to avoid global scope pollution
  • Clean up resources: Remove unused tables and variables when no longer needed
  • Avoid memory leaks: Properly clean up event handlers and threads
  • Limit cache sizes: Implement cache expiration for large data structures
-- Memory-efficient player data management
local PlayerData = {}

-- Clean up disconnected players
AddEventHandler('playerDropped', function()
    local playerId = source
    if PlayerData[playerId] then
        -- Save data before cleanup
        SavePlayerData(playerId)
        PlayerData[playerId] = nil
    end
end)

-- Periodic cleanup of unused data
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(300000) -- 5 minutes
        for playerId, data in pairs(PlayerData) do
            if not GetPlayerPing(playerId) then
                -- Player is no longer connected
                PlayerData[playerId] = nil
            end
        end
    end
end)

3. Database Performance

MySQL Optimization

-- MySQL configuration optimization
[mysqld]
# Performance settings
innodb_buffer_pool_size = 2G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
query_cache_size = 64M
query_cache_type = 1
max_connections = 200
thread_cache_size = 16
table_open_cache = 4000

# Connection settings
wait_timeout = 28800
interactive_timeout = 28800
max_allowed_packet = 64M

Query Optimization

❌ Slow Query: SELECT * FROM players WHERE name LIKE '%john%' - No index on name column.

✅ Optimized Query: SELECT id, name, money FROM players WHERE identifier = 'steam:123' - Uses primary key index.

-- Efficient database operations
local function GetPlayerData(identifier)
    -- Use prepared statements for better performance
    local result = MySQL.Sync.fetchAll(
        'SELECT id, name, money, level FROM players WHERE identifier = @identifier LIMIT 1',
        { ['@identifier'] = identifier }
    )
    return result[1]
end

-- Batch operations for better performance
local function UpdateMultiplePlayers(playerData)
    local queries = {}
    for playerId, data in pairs(playerData) do
        table.insert(queries, {
            query = 'UPDATE players SET money = @money WHERE id = @id',
            values = { ['@money'] = data.money, ['@id'] = playerId }
        })
    end
    
    -- Execute all updates in a single transaction
    MySQL.Async.transaction(queries, function(result)
        print("Updated " .. #queries .. " players")
    end)
end

Connection Pooling

-- oxmysql configuration with connection pooling
-- config.lua
Config.Database = {
    connectionLimit = 10,
    queueTimeout = 5000,
    acquireTimeout = 60000,
    timeout = 60000
}

-- Usage with connection pooling
local function SafeDatabaseOperation(callback)
    MySQL.ready(function()
        callback()
    end)
end

-- Example usage
SafeDatabaseOperation(function()
    local result = MySQL.Sync.fetchAll('SELECT * FROM players')
    -- Process result
end)

4. Resource Management

Resource Loading Order

-- server.cfg - Optimal resource loading order
# Core resources (load first)
ensure mapmanager
ensure chat
ensure spawnmanager
ensure sessionmanager
ensure basic-gamemode
ensure hardcap
ensure rconlog

# Database and essential systems
ensure oxmysql
ensure es_extended  # or your framework

# Utility resources
ensure adminmenu
ensure scoreboard

# Gameplay resources (load last)
ensure vehicle-system
ensure job-system
ensure inventory-system

Resource Monitoring

-- Resource performance monitoring script
local resourceStats = {}

local function MonitorResourcePerformance()
    local resources = GetNumResources()
    
    for i = 0, resources - 1 do
        local resourceName = GetResourceByFindIndex(i)
        local resourceState = GetResourceState(resourceName)
        
        if resourceState == 'started' then
            -- Monitor resource memory usage
            local memoryUsage = GetResourceKvpString(resourceName .. '_memory') or '0'
            resourceStats[resourceName] = {
                memory = tonumber(memoryUsage),
                lastCheck = GetGameTimer()
            }
        end
    end
    
    -- Log performance issues
    for resourceName, stats in pairs(resourceStats) do
        if stats.memory > 100000000 then -- 100MB threshold
            print("WARNING: " .. resourceName .. " using " .. stats.memory .. " bytes")
        end
    end
end

-- Run monitoring every 5 minutes
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(300000)
        MonitorResourcePerformance()
    end
end)

5. Performance Monitoring

Key Metrics to Monitor

Server Metrics

  • CPU Usage: Keep below 80%
  • RAM Usage: Monitor for leaks
  • Disk I/O: Watch for bottlenecks
  • Network Latency: Track ping times

Game Metrics

  • Server FPS: Maintain 60+ FPS
  • Player Count: Monitor capacity
  • Disconnection Rate: Track stability
  • Response Times: Database queries

Monitoring Tools

-- Simple performance monitoring script
local PerformanceMonitor = {}

function PerformanceMonitor.GetServerStats()
    local stats = {
        playerCount = GetNumPlayerIndices(),
        serverFPS = GetServerTickRate(),
        memoryUsage = collectgarbage('count'),
        uptime = GetGameTimer()
    }
    return stats
end

function PerformanceMonitor.LogPerformance()
    local stats = PerformanceMonitor.GetServerStats()
    
    -- Log to file or database
    local logEntry = string.format(
        "[%s] Players: %d, FPS: %.1f, Memory: %dKB",
        os.date('%Y-%m-%d %H:%M:%S'),
        stats.playerCount,
        stats.serverFPS,
        stats.memoryUsage
    )
    
    print(logEntry)
    
    -- Alert if performance is poor
    if stats.serverFPS < 45 then
        print("WARNING: Server FPS below 45!")
    end
    
    if stats.playerCount > 0 and stats.memoryUsage > 500000 then
        print("WARNING: High memory usage detected!")
    end
end

-- Monitor every 30 seconds
Citizen.CreateThread(function()
    while true do
        Citizen.Wait(30000)
        PerformanceMonitor.LogPerformance()
    end
end)

6. Advanced Optimization

Load Balancing

For large communities, consider implementing load balancing across multiple server instances.

Caching Strategies

-- Redis caching implementation
local redis = require('redis')
local cache = redis.connect('127.0.0.1', 6379)

local CacheManager = {}

function CacheManager.GetPlayerData(playerId)
    local cacheKey = 'player:' .. playerId
    
    -- Try cache first
    local cached = cache:get(cacheKey)
    if cached then
        return json.decode(cached)
    end
    
    -- Fallback to database
    local result = MySQL.Sync.fetchAll('SELECT * FROM players WHERE id = @id', {
        ['@id'] = playerId
    })
    
    if result[1] then
        -- Cache for 5 minutes
        cache:setex(cacheKey, 300, json.encode(result[1]))
        return result[1]
    end
    
    return nil
end

function CacheManager.InvalidatePlayer(playerId)
    local cacheKey = 'player:' .. playerId
    cache:del(cacheKey)
end

Async Operations

-- Non-blocking database operations
local function AsyncPlayerSave(playerId, data, callback)
    Citizen.CreateThread(function()
        -- Simulate heavy database operation
        MySQL.Async.execute('UPDATE players SET data = @data WHERE id = @id', {
            ['@data'] = json.encode(data),
            ['@id'] = playerId
        }, function(affectedRows)
            if callback then
                callback(affectedRows > 0)
            end
        end)
    end)
end

-- Usage
AsyncPlayerSave(playerId, playerData, function(success)
    if success then
        print("Player data saved successfully")
    else
        print("Failed to save player data")
    end
end)

Conclusion

Optimizing FiveM server performance is an ongoing process that requires monitoring, testing, and continuous improvement. By implementing these techniques, you can provide a smooth, lag-free experience for your players and maintain a thriving server community.

💡 Pro Tip: Start with basic optimizations and gradually implement advanced techniques. Monitor your server's performance regularly and adjust your optimization strategy based on actual usage patterns.

Need Help with Server Optimization?

Our AI script generator can help you create optimized scripts that follow performance best practices. Generate professional FiveM scripts in seconds.

Try AI Generator