Skip to content

Performance Optimization

Artmines edited this page Nov 3, 2025 · 1 revision

Performance Optimization

Guide for optimizing QC-AdvancedMedic for production servers with high player counts.


Client-Side Performance

Thread Load Analysis

Thread Interval Impact Status
Damage Detection 100ms ~0.01ms ✅ Optimal
Bleeding Damage 30s ~0.001ms ✅ Optimal
Medical Progression 2min ~0.01ms ✅ Optimal
Free-look Death Cam 16ms ~0.12ms ⚠️ HEAVY
Health Sync 1s (alive) / 5s (dead) ~0.001ms ✅ Optimal
Fall Detection 1s ~0.005ms ✅ Optimal

Critical Performance Issue: Death Camera

Warning

Major Performance Impact

The free-look death camera (Config.DeadMoveCam = true) runs at 62 FPS (16ms interval) while dead. This adds ~0.12ms per tick to EVERY dead player.

Impact Example:

  • 1 dead player = 0.12ms
  • 10 dead players = 1.2ms
  • 50 dead players = 6.0ms (significant lag)

Recommendation: Set Config.DeadMoveCam = false for servers with 50+ concurrent players.

In config.lua:

Config.DeadMoveCam = false  -- Disable for production

Server-Side Performance

Database Operations

Operation Frequency Query Type Optimization
Wound save Every 5 min + disconnect UPSERT ✅ Batched
Profile load On connect Stored procedure ✅ Single call
History log On events INSERT ✅ Async
Data cleanup Daily/hourly Stored procedure ✅ Scheduled

Server-Side Caching

The server maintains PlayerMedicalData[source] cache which reduces database queries by ~90%:

Without Cache:

  • Every wound update = 1-2 database queries
  • Every treatment = 1-2 database queries
  • Every inspection = 5-10 database queries
  • ~50 queries/minute per active player

With Cache:

  • Most operations = 0 database queries (cached)
  • Periodic sync = 1 batch query per player
  • ~5 queries/minute per active player

Optimization Strategies

1. Use Stored Procedures

-- ❌ BAD (4 separate queries)
local wounds = MySQL.query.await('SELECT * FROM player_wounds WHERE citizenid = ?', {cid})
local treatments = MySQL.query.await('SELECT * FROM medical_treatments WHERE citizenid = ?', {cid})
local infections = MySQL.query.await('SELECT * FROM player_infections WHERE citizenid = ?', {cid})
local fractures = MySQL.query.await('SELECT * FROM player_fractures WHERE citizenid = ?', {cid})

-- ✅ GOOD (1 stored procedure call)
local profile = MySQL.query.await('CALL GetCompleteMedicalProfile(?)', {cid})

2. Schedule Database Cleanup

Linux Cron Job (daily at 3 AM):

0 3 * * * mysql -u user -ppassword database -e "CALL CleanupExpiredMedicalData();"

Windows Task Scheduler:

# Create scheduled task
schtasks /create /tn "CleanupMedicalData" /tr "mysql -u user -ppassword database -e \"CALL CleanupExpiredMedicalData();\"" /sc daily /st 03:00

Or via FXServer:

-- In server/server.lua
CreateThread(function()
    while true do
        Wait(86400000) -- 24 hours
        MySQL.execute('CALL CleanupExpiredMedicalData()')
        print('[QC-AdvancedMedic] Database cleanup completed')
    end
end)

3. Optimize Network Traffic

Broadcasting Limits:

  • Wound updates only broadcast to medics within 10m radius
  • Prevents unnecessary network traffic

To adjust broadcast radius:

-- In server/medical_events.lua (around line 50)
local BROADCAST_RADIUS = 10.0  -- Reduce to 5.0 for less traffic

4. Reduce Logging Verbosity

Disable debug logging in production:

-- In config.lua
Config.WoundHealing.debugging = {
    enabled = false,                 -- Disable all debug logs
    showRequirementChecks = false,
    showHealingProgress = false
}

5. Optimize Medical Progression Intervals

Current intervals (in config.lua):

Config.WoundProgression = {
    bleedingProgressionInterval = 1,     -- Minutes
    painProgressionInterval = 1,
    painNaturalHealingInterval = 5
}

For high-pop servers (reduce tick frequency):

Config.WoundProgression = {
    bleedingProgressionInterval = 2,     -- Reduce to 2 minutes
    painProgressionInterval = 2,         -- Reduce to 2 minutes
    painNaturalHealingInterval = 10      -- Reduce to 10 minutes
}

Impact: Reduces client-side processing by 50%


Database Optimization

Index Optimization

The schema includes optimized indexes, but verify they exist:

-- Check indexes
SHOW INDEX FROM player_wounds;
SHOW INDEX FROM medical_treatments;
SHOW INDEX FROM player_infections;
SHOW INDEX FROM player_fractures;
SHOW INDEX FROM medical_history;

Expected indexes:

  • idx_citizenid on all tables
  • idx_citizen_active on treatments/infections
  • idx_expiration on treatments
  • idx_scar on wounds
  • unique_wound, unique_infection, unique_fracture constraints

Query Performance Monitoring

-- Enable slow query log
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.5;  -- Log queries > 0.5 seconds

-- Check slow queries
SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10;

Regular OPTIMIZE

# Weekly optimization
0 4 * * 0 mysql -u user -ppassword -e "OPTIMIZE TABLE player_wounds, medical_treatments, player_infections, player_fractures, medical_history;"

Network Traffic Optimization

Average Bandwidth

  • Per player (idle with wounds): ~1-2 KB/min
  • During combat: ~5-10 KB/min (frequent wound updates)
  • Medic inspections: ~5 KB per inspection (one-time)

Optimization Tips

1. Reduce sync frequency for inactive players:

-- In client/wound_system.lua
local SYNC_INTERVAL_ACTIVE = 300000  -- 5 minutes (default)
local SYNC_INTERVAL_INACTIVE = 600000 -- 10 minutes (if no wounds)

CreateThread(function()
    while true do
        local interval = (HasActiveWounds() and SYNC_INTERVAL_ACTIVE or SYNC_INTERVAL_INACTIVE)
        Wait(interval)
        SyncMedicalData()
    end
end)

2. Batch database writes:

Already implemented via server-side cache. Data syncs every 5 minutes instead of immediately.


Resource Monitoring

Check Resource Usage

resmon

Look for:

  • QC-AdvancedMedic - Should be < 0.5ms average
  • Spikes > 2ms indicate performance issues

Common Performance Issues

Symptom Cause Solution
High ms while dead Death camera enabled Disable Config.DeadMoveCam
Database lag spikes No cleanup running Schedule CleanupExpiredMedicalData()
High network traffic Too many medics Reduce broadcast radius
Client lag spikes Too many wounds Clear old scars from database

Production Server Recommendations

Small Servers (< 32 players)

Config.DeadMoveCam = true           -- Can keep enabled
Config.WoundProgression = {
    bleedingProgressionInterval = 1, -- Keep default
    painProgressionInterval = 1,
    painNaturalHealingInterval = 5
}

Medium Servers (32-64 players)

Config.DeadMoveCam = false          -- Disable for performance
Config.WoundProgression = {
    bleedingProgressionInterval = 2, -- Reduce frequency
    painProgressionInterval = 2,
    painNaturalHealingInterval = 10
}

Large Servers (64+ players)

Config.DeadMoveCam = false          -- Definitely disable
Config.WoundProgression = {
    bleedingProgressionInterval = 3, -- Further reduction
    painProgressionInterval = 3,
    painNaturalHealingInterval = 15
}

Additional for large servers:

  • Reduce broadcast radius to 5m
  • Schedule daily database cleanup
  • Monitor resource usage weekly
  • Clear scars older than 7 days

Database Maintenance Schedule

Daily (3 AM)

# Cleanup expired data
0 3 * * * mysql -u user -ppassword database -e "CALL CleanupExpiredMedicalData();"

Weekly (Sunday 4 AM)

# Optimize tables
0 4 * * 0 mysql -u user -ppassword database -e "OPTIMIZE TABLE player_wounds, medical_treatments, player_infections, player_fractures, medical_history;"

Monthly (1st of month, 5 AM)

# Delete very old scars (optional)
0 5 1 * * mysql -u user -ppassword database -e "DELETE FROM player_wounds WHERE is_scar = TRUE AND scar_time < DATE_SUB(NOW(), INTERVAL 30 DAY);"

Monitoring Tools

Server-Side Monitoring

-- In server/server.lua
RegisterCommand('medstats', function(source)
    local playerCount = #RSGCore.Functions.GetPlayers()
    local cacheSize = 0
    for _ in pairs(PlayerMedicalData) do
        cacheSize = cacheSize + 1
    end

    print(string.format('[QC-AdvancedMedic] Players: %d | Cached: %d', playerCount, cacheSize))

    -- Check database size
    local result = MySQL.query.await([[
        SELECT
            (SELECT COUNT(*) FROM player_wounds) AS wounds,
            (SELECT COUNT(*) FROM medical_treatments) AS treatments,
            (SELECT COUNT(*) FROM player_infections) AS infections,
            (SELECT COUNT(*) FROM player_fractures) AS fractures,
            (SELECT COUNT(*) FROM medical_history) AS history
    ]])

    print(json.encode(result[1], {indent = true}))
end, true)

← Extending the System | Next: Known Issues →

📖 QC-AdvancedMedic

🏠 Home


📚 Documentation

  1. Architecture
  2. Client Systems
  3. Server Systems
  4. Database Schema

⚙️ Configuration

  1. Configuration
  2. Translation System
  3. API Reference

🛠️ Development

  1. Extending the System
  2. Performance Optimization

⚠️ Support

  1. Known Issues

🔗 Links


v0.3.1-alpha

Clone this wiki locally