perf: buffer mod_vnc_lastactivity DB writes and flush periodically
The store :set() calls go through mod_storage_sql (LuaDBI), a blocking C client. These hooks fire on the presence/auth hot path, so inline writes stall the event loop under presence storms. Buffer writes in memory and flush every 5 seconds (configurable via vnc_lastactivity_flush_interval). The IQ handlers already read from in-memory map/remote_act_cache first, so read consistency is preserved. Final flush on server-stopping and host-deactivating prevents data loss on graceful shutdown. Part-of: <http://gitlab.vnc.biz/uxf/vnctalk-prosody/-/merge_requests/3>
This commit is contained in:
@@ -26,6 +26,44 @@ local activity_store = module:open_store("activity", "map");
|
||||
local remote_activity_store = module:open_store("remote_activity", "map");
|
||||
local remote_avatar_update_store = module:open_store("remote_avatar", "map");
|
||||
|
||||
-- The store :set() calls go through mod_storage_sql (LuaDBI), which is a
|
||||
-- blocking C client -- every write blocks the event loop for a DB round-trip.
|
||||
-- These hooks fire on the presence/auth hot path, so doing the write inline
|
||||
-- stalls the whole server under presence storms. Instead we buffer the
|
||||
-- last-value-wins writes in memory (the IQ handlers already read from the
|
||||
-- in-memory map/caches) and flush them periodically from a timer.
|
||||
local FLUSH_INTERVAL = module:get_option_number("vnc_lastactivity_flush_interval", 5);
|
||||
|
||||
-- Pending writes, coalesced by user/contact. Only the most recent value per
|
||||
-- key is kept, matching the "last value wins" semantics of the stores.
|
||||
local pending_activity = {}; -- [username] = stamp (store key "last")
|
||||
local pending_remote_activity = {}; -- [contact_bare] = stamp (store key "last")
|
||||
local pending_remote_avatar = {}; -- [contact_bare] = stamp (store key "avatar")
|
||||
|
||||
local function flush_pending_writes()
|
||||
for username, value in pairs(pending_activity) do
|
||||
local ok, err = activity_store:set(username, "last", value);
|
||||
if not ok then
|
||||
module:log("warn", "flush activity_store:set(%s) failed: %s", username, tostring(err));
|
||||
end
|
||||
end
|
||||
for contact_bare, value in pairs(pending_remote_activity) do
|
||||
local ok, err = remote_activity_store:set(contact_bare, "last", value);
|
||||
if not ok then
|
||||
module:log("warn", "flush remote_activity_store:set(%s) failed: %s", contact_bare, tostring(err));
|
||||
end
|
||||
end
|
||||
for contact_bare, value in pairs(pending_remote_avatar) do
|
||||
local ok, err = remote_avatar_update_store:set(contact_bare, "avatar", value);
|
||||
if not ok then
|
||||
module:log("warn", "flush remote_avatar_update_store:set(%s) failed: %s", contact_bare, tostring(err));
|
||||
end
|
||||
end
|
||||
for k in pairs(pending_activity) do pending_activity[k] = nil; end
|
||||
for k in pairs(pending_remote_activity) do pending_remote_activity[k] = nil; end
|
||||
for k in pairs(pending_remote_avatar) do pending_remote_avatar[k] = nil; end
|
||||
end
|
||||
|
||||
local function getTableLng(tbl)
|
||||
local getN = 0
|
||||
for n in pairs(tbl) do
|
||||
@@ -107,8 +145,8 @@ local map = {};
|
||||
module:hook("authentication-success", function(event)
|
||||
local session = event.session;
|
||||
if session.username then
|
||||
local pers = activity_store:set(session.username, "last", 0);
|
||||
end
|
||||
pending_activity[session.username] = 0;
|
||||
end
|
||||
end, 10);
|
||||
|
||||
|
||||
@@ -139,7 +177,7 @@ module:hook("presence/bare", function(event)
|
||||
remote_act_sessions[contact_bare] = nil;
|
||||
remote_act_cache[contact_bare] = t;
|
||||
-- module:log("info", "remote_act_cache for %s is now %s", contact_bare, remote_act_cache[contact_bare]);
|
||||
remote_activity_store:set(contact_bare, "last", t);
|
||||
pending_remote_activity[contact_bare] = t;
|
||||
end
|
||||
end
|
||||
else
|
||||
@@ -161,7 +199,7 @@ module:hook("presence/bare", function(event)
|
||||
if (x_photo_update) then
|
||||
-- module:log("info", "avatarupdate - need to store %s update for %s", x_photo_update, contact_bare);
|
||||
local t = os.time();
|
||||
remote_avatar_update_store:set(contact_bare, "avatar", t);
|
||||
pending_remote_avatar[contact_bare] = t;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -188,7 +226,7 @@ module:hook("pre-presence/bare", function(event)
|
||||
end
|
||||
if lastsession then
|
||||
-- module:log("info", "last session gone - storing to activity_store for %s => %s", event.origin.username, s);
|
||||
local pers = activity_store:set(event.origin.username, "last", s);
|
||||
pending_activity[event.origin.username] = s;
|
||||
end
|
||||
end
|
||||
end, 10);
|
||||
@@ -413,3 +451,20 @@ end
|
||||
module.restore = function(data)
|
||||
map = data.map or {};
|
||||
end
|
||||
|
||||
-- Flush buffered writes on a fixed cadence instead of on every presence/auth
|
||||
-- event. Returning FLUSH_INTERVAL reschedules the timer. The DB I/O still
|
||||
-- blocks the event loop while it runs, but once per interval rather than per
|
||||
-- stanza.
|
||||
module:add_timer(FLUSH_INTERVAL, function ()
|
||||
flush_pending_writes();
|
||||
return FLUSH_INTERVAL;
|
||||
end);
|
||||
|
||||
-- Best-effort final flush so buffered writes are not lost on stop/reload.
|
||||
module:hook_global("server-stopping", function ()
|
||||
flush_pending_writes();
|
||||
end);
|
||||
module:hook("host-deactivating", function ()
|
||||
flush_pending_writes();
|
||||
end);
|
||||
|
||||
Reference in New Issue
Block a user