Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: targeted events #1385

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft

perf: targeted events #1385

wants to merge 2 commits into from

Conversation

arnautov-anton
Copy link
Contributor

@arnautov-anton arnautov-anton commented Oct 30, 2024

The Why

Targeted events are a runtime extension of event types - by default we provide a limited set of types which we can hook onto but say you have 1000 instances of a Thread BFF which have and manage their own state by hooking onto these events. Each of these instances registers around 10 event listeners but incoming events are usually only meant for one specific instance (based on messageId) so that's 1000 instances listening times two (two of those 10 listeners per instance are of the same type) - that's 2k function calls out of which only two will actually process the incoming data because the data was meant for them, the rest will exit early - this approach seems wasteful and at larger scale might slow applications down a quite a bit.

The Solution

We can reduce the amount of calls by directly targeting what handlers to call by constructing a special type runtime:

fig.1:

// current approach
"message.new": [L1, L2, L3... L2000]
// targeted event approach
"message.new-<parentMessageId>": [L1, L2]

In Thread class we know, that for certain handlers we'd only like to run them if the event payload matches parentMessageId (new incoming reply), we don't care about the rest.

So knowing the parentMessageId at the Thread construction time, we can register our handlers with our pre-defined type:

client.on(`message.new-${parentMessageId}`, (event) => {
    // handle event payload
})

At the same time we should register the factory function which instructs the dispatcher how to construct the special type based on the incoming event and store it for the specific type. These factories should be either static methods or simply live outside class scope so their signatures are the same across instances as their outputs should only depend on the input event argument and pre-defined factors (such as event types).

// lives outside class
const messageNewFactory: TargetFactory<DefaultGenerics, 'message.new'> = (event) => {
  return event.parent_id ? `message.new-${event.parent_id}` : null;
};

// register mechanism uses Set under the hood so only one factory-per-signature is accounted for
this.client.registerTargetFactory('message.new', messageNewFactory)

Now when it comes to dispatching the handlers, the dispatcher pulls in registered factories by incoming event type, runs them all and after looks for handlers stored by type (now using custom types) and since only two of such event handlers were registered for that type, it runs only two (refer to fig.1). Total of 3 function calls (including the factory call) instead of 2000.

Copy link
Contributor

github-actions bot commented Oct 30, 2024

Size Change: +1.21 kB (+0.26%)

Total Size: 459 kB

Filename Size Change
dist/browser.es.js 100 kB +248 B (+0.25%)
dist/browser.full-bundle.min.js 56.8 kB +204 B (+0.36%)
dist/browser.js 101 kB +256 B (+0.25%)
dist/index.es.js 100 kB +247 B (+0.25%)
dist/index.js 101 kB +258 B (+0.26%)

compressed-size-action

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant