Clustered NodeBB and plugin socket methods
-
I'm trying to debug an issue with maintaining sync for plugin settings between nodes of a NodeBB cluster running on 2 different machines. I've set
isCluster
on both instances, however theSocketPlugins
method only triggers on one instance when the hook is fired from the client. Any suggestions on where to look would be much appreciated. -
I've verified that both are using Redis for pubsub and the hook data is being received and emitted on both in
src/database/redis/pubsub.js
:self.emit(msg.event, msg.data);
Instance 1
instance 1 mongo-object:cache:del [ 'settings:customMod' ] instance 1 local:cache:del [ 'settings:customMod' ] instance 1 action:settings.set.customMod{ enableQuickReply: 'on', featuredTopic: '', shared: '88,89, 90, 91,105,', searchable: '88, 89, 90, 91,105,', articles: '1, 2, 102, 103, 104', hideSubCategories: 'off', hideCategoryLastPost: 'off' }
Instance 2
instance 2 mongo-object:cache:del [ 'settings:customMod' ] instance 2 local:cache:del [ 'settings:customMod' ] instance 2 action:settings.set.customMod{ enableQuickReply: 'on', featuredTopic: '', shared: '88,89, 90, 91,105,', searchable: '88, 89, 90, 91,105,', articles: '1, 2, 102, 103, 104', hideSubCategories: 'off', hideCategoryLastPost: 'off' }
However, only one instance of the plugin triggers from the hook
-
When you do a
socket.emit('someplugin.method')
it will only trigger the method in the instance that you are connected, this is intended. If you want to update a local settings object in your plugin you need to listen toaction:settings.et.customMod
and update you local object. -
@baris That's exactly what I am doing
{ "hook": "action:settings.set.customMod", "method": "updateSettings" }
However, only one instance of the plugin gets triggered. I am trying to follow the event path to debug the issue but I'm not clear on the pubsub architecture. I can see thatpubsub.js
uses Redis via a channel nameddb:${nconf.get('redis:database')}:pubsub_channel
, while thesocket.io
Redis adapter uses multiple channels prefixed with:db:${nconf.get('redis:database')}:adapter_key
? I can listen to thepubsub_channel
and see all the trafficredis db:11:pubsub_channel {"event":"analytics:publish","data":{"local":{"counters":{},"pageViews":0,"pageViewsRegistered":0,"pageViewsGuest":0,"pageViewsBot":0,"uniqueIPCount":0,"uniquevisitors":0}}} redis db:11:pubsub_channel {"event":"analytics:publish","data":{"local":{"counters":{"pageviews:byCid:122":1},"pageViews":1,"pageViewsRegistered":0,"pageViewsGuest":0,"pageViewsBot":1,"uniqueIPCount":0,"uniquevisitors":0}}}
Where does
pubsub.js
fit into the overall architecture? and a followup question, is there a mechanism to trigger execution of a method in all instances of a plugin? Either directly from the client, or by propagating the triggered hook on the one instance that received the hook would work. -
{ "hook": "action:settings.set.customMod", "method": "updateSettings" }
This won't work since it is not a hook, plugin.json hooks object is used for listening to plugin hooks that are fired withplugins.hooks.fire
.In this case you want to listen for messages on pubsub so you need to use.
pubsub.on('action:settings.set.customMod', handler);
in your plugin.https://github.com/NodeBB/NodeBB/blob/master/src/meta/settings.js#L95-L101
If you want to use a plugin hook it is called
action:settings.set
and that can be added to plugin.json -
So, if I understood you correctly, in order to propagate local variables across instances, I should use pubsub after receiving a hook?
Following this suggestion, I set up the flow like below.
The plugin gets triggered by the ACP save and publishes a notification for the other instances:
socketAdmin.plugins.customMod.save = async function(socket, data) { await db.set('plugins.customMod.settings', JSON.stringify(data)); // perfom required lookups and transformations to data pubsub.publish('plugins.customMod:updated', updatedData); };
And then set up a listener for applying the changes to the local variables in all instances
pubsub.on(`plugins.customMod:updated`, (async (data) => { categoryArticles = data.categoryArticles articleCategories = data.articleCategories shareCategories = data.shareCategories searchableCategories = data.searchableCategories ));
I understand why
socket.emit
should only trigger on a single instance in most cases, but perhaps it would be useful to have a way to automatically trigger on all instances?