-
Notifications
You must be signed in to change notification settings - Fork 9
Performance Optimization
Guide for optimizing QC-AdvancedMedic for production servers with high player counts.
| 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 | |
| Health Sync | 1s (alive) / 5s (dead) | ~0.001ms | ✅ Optimal |
| Fall Detection | 1s | ~0.005ms | ✅ Optimal |
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| 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 |
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
-- ❌ 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})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:00Or 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)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 trafficDisable debug logging in production:
-- In config.lua
Config.WoundHealing.debugging = {
enabled = false, -- Disable all debug logs
showRequirementChecks = false,
showHealingProgress = false
}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%
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_citizenidon all tables -
idx_citizen_activeon treatments/infections -
idx_expirationon treatments -
idx_scaron wounds -
unique_wound,unique_infection,unique_fractureconstraints
-- 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;# Weekly optimization
0 4 * * 0 mysql -u user -ppassword -e "OPTIMIZE TABLE player_wounds, medical_treatments, player_infections, player_fractures, medical_history;"- 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)
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.
resmon
Look for:
- QC-AdvancedMedic - Should be < 0.5ms average
- Spikes > 2ms indicate 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 |
Config.DeadMoveCam = true -- Can keep enabled
Config.WoundProgression = {
bleedingProgressionInterval = 1, -- Keep default
painProgressionInterval = 1,
painNaturalHealingInterval = 5
}Config.DeadMoveCam = false -- Disable for performance
Config.WoundProgression = {
bleedingProgressionInterval = 2, -- Reduce frequency
painProgressionInterval = 2,
painNaturalHealingInterval = 10
}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
# Cleanup expired data
0 3 * * * mysql -u user -ppassword database -e "CALL CleanupExpiredMedicalData();"# Optimize tables
0 4 * * 0 mysql -u user -ppassword database -e "OPTIMIZE TABLE player_wounds, medical_treatments, player_infections, player_fractures, medical_history;"# 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);"-- 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)v0.3.1-alpha