<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://mafalda.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mafalda.io/" rel="alternate" type="text/html" /><updated>2026-03-14T12:47:08+00:00</updated><id>https://mafalda.io/feed.xml</id><title type="html">Mafalda SFU</title><subtitle>Massively parallel vertical and horizontal scalable SFU (Stream Forwarding
Unit) built on top of Mediasoup
</subtitle><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><entry xml:lang="en"><title type="html">Mafalda SFU receives “Best Scalable Real-Time Media Platform 2026” at the Spanish Business Awards</title><link href="https://mafalda.io/2026/02/05/Mafalda-SFU-receives-Best-Scalable-Real-Time-Media-Platform-2026-at-the-Spanish-Business-Awards.html" rel="alternate" type="text/html" title="Mafalda SFU receives “Best Scalable Real-Time Media Platform 2026” at the Spanish Business Awards" /><published>2026-02-05T00:00:00+00:00</published><updated>2026-02-05T00:00:00+00:00</updated><id>https://mafalda.io/2026/02/05/Mafalda-SFU-receives-%E2%80%9CBest-Scalable-Real-Time-Media-Platform-2026%E2%80%9D-at-the-Spanish-Business-Awards</id><content type="html" xml:base="https://mafalda.io/2026/02/05/Mafalda-SFU-receives-Best-Scalable-Real-Time-Media-Platform-2026-at-the-Spanish-Business-Awards.html">&lt;p&gt;We are happy to share that &lt;strong&gt;Mafalda SFU&lt;/strong&gt; has been recognised with the award &lt;strong&gt;“Best Scalable Real-Time Media Platform 2026”&lt;/strong&gt; at the Spanish Business Awards organised by EU Business News.&lt;/p&gt;

&lt;p&gt;The official listing can be found here:
&lt;a href=&quot;https://www.eubusinessnews.com/winners/mafalda-sfu/&quot;&gt;https://www.eubusinessnews.com/winners/mafalda-sfu/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While Mafalda SFU started as an experimental project exploring scalable real-time media architectures, it has gradually evolved into a platform that demonstrates how modern infrastructure can support &lt;strong&gt;high-performance, distributed, real-time communications&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;what-mafalda-sfu-is-about&quot;&gt;What Mafalda SFU is about&lt;/h2&gt;

&lt;p&gt;Mafalda SFU is a &lt;strong&gt;Selective Forwarding Unit (SFU)&lt;/strong&gt; designed to support scalable real-time media applications built on technologies such as WebRTC.&lt;/p&gt;

&lt;p&gt;Instead of mixing or transcoding streams centrally, an SFU forwards media packets between participants, allowing applications to scale efficiently while keeping latency extremely low.&lt;/p&gt;

&lt;p&gt;The project focuses on several key ideas:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Horizontal scalability&lt;/strong&gt; for large multi-participant sessions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Low-latency media routing&lt;/strong&gt; for interactive experiences&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Modular architecture&lt;/strong&gt; that allows integration with different signaling systems&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Efficient bandwidth usage&lt;/strong&gt; through selective forwarding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These characteristics make SFU architectures particularly suitable for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;video conferencing platforms&lt;/li&gt;
  &lt;li&gt;real-time collaboration tools&lt;/li&gt;
  &lt;li&gt;live interactive streaming&lt;/li&gt;
  &lt;li&gt;multiplayer experiences and virtual events&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;why-scalability-matters-in-real-time-media&quot;&gt;Why scalability matters in real-time media&lt;/h2&gt;

&lt;p&gt;Real-time media systems are fundamentally different from traditional web applications.&lt;/p&gt;

&lt;p&gt;They must deal with constraints such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;strict latency requirements&lt;/li&gt;
  &lt;li&gt;packet loss and network variability&lt;/li&gt;
  &lt;li&gt;adaptive bitrate and congestion control&lt;/li&gt;
  &lt;li&gt;large numbers of concurrent participants&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional MCU-based architectures (multipoint control units) often struggle to scale efficiently because they require expensive media mixing and transcoding.&lt;/p&gt;

&lt;p&gt;SFU architectures instead focus on &lt;strong&gt;intelligent packet routing&lt;/strong&gt;, enabling platforms to handle many participants with significantly lower compute requirements.&lt;/p&gt;

&lt;p&gt;Mafalda SFU explores how this model can be implemented in a &lt;strong&gt;modern, distributed infrastructure&lt;/strong&gt; while keeping the system lightweight and flexible.&lt;/p&gt;

&lt;h2 id=&quot;an-experimental-platform-exploring-real-time-media-architectures&quot;&gt;An experimental platform exploring real-time media architectures&lt;/h2&gt;

&lt;p&gt;From the beginning, Mafalda SFU was conceived less as a commercial product and more as a &lt;strong&gt;technical exploration of scalable media systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The project investigates questions such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;how to design efficient SFU routing layers&lt;/li&gt;
  &lt;li&gt;how signaling systems interact with media routing&lt;/li&gt;
  &lt;li&gt;how distributed deployments can support large sessions&lt;/li&gt;
  &lt;li&gt;how WebRTC infrastructures can be simplified while remaining scalable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These explorations are increasingly relevant as real-time media becomes a core component of modern platforms, from communication tools to collaborative and immersive applications.&lt;/p&gt;

&lt;h2 id=&quot;recognition-from-the-spanish-business-awards&quot;&gt;Recognition from the Spanish Business Awards&lt;/h2&gt;

&lt;p&gt;Receiving the &lt;strong&gt;Best Scalable Real-Time Media Platform 2026&lt;/strong&gt; award highlights the growing importance of scalable communication infrastructure in today’s digital ecosystem.&lt;/p&gt;

&lt;p&gt;As remote collaboration, live media and real-time interaction continue to expand, architectures like SFUs play a crucial role in enabling new kinds of online experiences.&lt;/p&gt;

&lt;p&gt;This recognition reinforces the relevance of continued experimentation and research in this area.&lt;/p&gt;

&lt;h2 id=&quot;looking-forward&quot;&gt;Looking forward&lt;/h2&gt;

&lt;p&gt;Real-time media infrastructure is still evolving rapidly.&lt;/p&gt;

&lt;p&gt;Projects like Mafalda SFU contribute to exploring new architectural approaches that can help shape the next generation of communication platforms.&lt;/p&gt;

&lt;p&gt;While the project remains experimental, the ideas behind it of &lt;strong&gt;scalability, efficiency and modularity in real-time media systems&lt;/strong&gt; will continue to influence how modern communication platforms are designed.&lt;/p&gt;

&lt;h2 id=&quot;learn-more&quot;&gt;Learn more&lt;/h2&gt;

&lt;p&gt;More information about the award can be found here:
&lt;a href=&quot;https://www.eubusinessnews.com/winners/mafalda-sfu/&quot;&gt;https://www.eubusinessnews.com/winners/mafalda-sfu/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are interested in real-time media architectures, SFUs, or scalable WebRTC infrastructure, stay tuned for future updates and technical articles.&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">We are happy to share that Mafalda SFU has been recognised with the award “Best Scalable Real-Time Media Platform 2026” at the Spanish Business Awards organised by EU Business News.</summary></entry><entry xml:lang="en"><title type="html">How to scale EduMeet (or any Mediasoup application) with Mafalda SFU</title><link href="https://mafalda.io/2025/03/31/How-to-scale-EduMeet-(or-any-Mediasoup-application)-with-Mafalda-SFU.html" rel="alternate" type="text/html" title="How to scale EduMeet (or any Mediasoup application) with Mafalda SFU" /><published>2025-03-31T00:00:00+00:00</published><updated>2025-03-31T00:00:00+00:00</updated><id>https://mafalda.io/2025/03/31/How-to-scale-EduMeet-(or-any-Mediasoup-application)-with-Mafalda-SFU</id><content type="html" xml:base="https://mafalda.io/2025/03/31/How-to-scale-EduMeet-(or-any-Mediasoup-application)-with-Mafalda-SFU.html">&lt;p&gt;A &lt;a href=&quot;/FAQ&quot;&gt;frequently asked question&lt;/a&gt; we get is &lt;em&gt;how to scale an already existing
&lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt;-based application with Mafalda SFU&lt;/em&gt;. From an
application or user perspective, the scalability is transparent, because Mafalda
SFU handle all the complexity of scaling the application; and from the developer
perspective, the API is the same as with a single Mediasoup instance, so there’s
no need to learn anything new. The only difference is that the application has
to use any of the Mafalda SFU packages instead of directly using the
&lt;a href=&quot;https://www.npmjs.com/package/mediasoup&quot;&gt;Mediasoup package&lt;/a&gt;. This is a pretty
simple change, and it can be done for any Mediasoup-based project in a few lines
of code, as can be seen in the example of our
&lt;a href=&quot;https://github.com/Mafalda-SFU/Remote-Mediasoup-client-mock/&quot;&gt;Remote Mediasoup client mock&lt;/a&gt;
repository. But to make things easier, we’ll have a few examples in this
post about porting &lt;a href=&quot;https://github.com/edumeet/edumeet/tree/develop&quot;&gt;EduMeet&lt;/a&gt; to
Mafalda SFU about &lt;em&gt;how to use and combine the different Mafalda SFU packages to
scale your application&lt;/em&gt;. You can also check the code changes explained in this
post already applied to EduMeet for each different use case in our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/branches/all&quot;&gt;EduMeet fork&lt;/a&gt;,
with some additional fixes and updates.&lt;/p&gt;

&lt;h2 id=&quot;prepare-your-application&quot;&gt;Prepare your application&lt;/h2&gt;

&lt;p&gt;Although Mafalda SFU is designed to be 100% API compatible with Mediasoup, they
have a different architecture: meanwhile Mediasoup directly export its functions
at module level, being effectively like a singleton object, Mafalda SFU modules
export a &lt;em&gt;manager&lt;/em&gt; class, that is responsible of implementing the actual
functionality of each Mafalda SFU module, and later they provide a &lt;em&gt;Mediasoup
compatible&lt;/em&gt; object instance, that’s the one that actually replaces the Mediasoup
module.&lt;/p&gt;

&lt;p&gt;This means, the first thing you need to do in your project code is to replace
the Mediasoup module import, and require it as a function argument, allowing to
do &lt;em&gt;dependency injection&lt;/em&gt;, so later we can pass the Mediasoup compatible object
instance instead. This is a pretty simple change, and it can be done in a few
lines of code, being the only difficulty in case the Mediasoup module is being
imported from multiple files. In that case, you need to change them to be
imported from a single file, and pass the Mediasoup object instance to the
different files, like in this example:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;import mediasoup from &apos;mediasoup&apos;;
&lt;/span&gt;
- async function run()
&lt;span class=&quot;gi&quot;&gt;+ async function run(mediasoup)
&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
  const worker = await mediasoup.createWorker()

  // ...
&lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;gd&quot;&gt;- run()
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+ run(mediasoup)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see a real world example at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/5f52f6a31a4bbf5060c139bc7107f84f431cbba0&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have your application ready to use dependency injection, next step is
to replace the Mediasoup object instance with the Mediasoup-compatible one from
the Mafalda SFU package you are using.&lt;/p&gt;

&lt;h2 id=&quot;the-basics-remote-mediasoup-client&quot;&gt;The basics: Remote Mediasoup client&lt;/h2&gt;

&lt;p&gt;The most simple way to scale a Mediasoup based application using Mafalda SFU is
to use the &lt;a href=&quot;/Remote-Mediasoup-client&quot;&gt;Remote Mediasoup client&lt;/a&gt; package. This
package allow to connect to a
&lt;a href=&quot;/Remote-Mediasoup-server&quot;&gt;Remote Mediasoup server&lt;/a&gt; instance, that can be
running on a different (and potentially more performant) server. This is the
recommended way to use Mafalda SFU, because it’s the most flexible alternative
allowing to fully abstract your application from the underlying Mediasoup
implementation, and depending on the application needs, you can increase the
media servers capacity independently of the application servers, specially when
connecting to &lt;a href=&quot;/Mediasoup-cluster&quot;&gt;Mediasoup cluster&lt;/a&gt; or
&lt;a href=&quot;/Mediasoup-vertical-cluster&quot;&gt;Mediasoup vertical cluster&lt;/a&gt; servers. This way, you
can have a single application server that can make use and handle multiple
Mediasoup servers at the same time and scale them independently.&lt;/p&gt;

&lt;p&gt;To replace Mediasoup with the Remote Mediasoup client, you only need to install
the &lt;a href=&quot;/Remote-Mediasoup-client&quot;&gt;Remote Mediasoup client&lt;/a&gt; package, and then
replace the Mediasoup object instance with the Remote Mediasoup client and its
Mediasoup-compatible object instance. If you have the Remote Mediasoup client
package tarfile at the root of your project, you can replace the Mediasoup
package with it with the following commands:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm uninstall mediasoup

npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-remote-mediasoup-client-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, you can replace the Mediasoup object instance with the Remote
Mediasoup client and the Mediasoup compatible object instance:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+ import { once } from &apos;node:events&apos;;
&lt;/span&gt;
- import mediasoup from &apos;mediasoup&apos;;
&lt;span class=&quot;gi&quot;&gt;+ import RemoteMediasoupClient from &apos;@mafalda-sfu/remote-mediasoup-client&apos;;
+
+ const client = new RemoteMediasoupClient(&apos;ws://example.com&apos;);
&lt;/span&gt;
+ // Destroy the client when the process is terminated, to do a clean exit
&lt;span class=&quot;gi&quot;&gt;+ const onProcessSignal = client.destroy.bind(client);
+
+ process.once(&apos;SIGINT&apos;, onProcessSignal);
+ process.once(&apos;SIGTERM&apos;, onProcessSignal);
&lt;/span&gt;
+ function onConnectionFailure(error)
&lt;span class=&quot;gi&quot;&gt;+ {
+   console.error(&apos;Error connecting to Remote Mediasoup server:&apos;, error);
+
+   process.exit(1);
+ }
+
+ // Wait for the connection to be established, or notify connection error
+ await once(client, &apos;mediasoup&apos;).catch(onConnectionFailure);
&lt;/span&gt;
+ // The connection is established, we can use the Mediasoup compatible object
&lt;span class=&quot;gi&quot;&gt;+ // instance, and listen for errors
+ const { mediasoup } = client.on(&apos;error&apos;, console.error);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(Sorry if you find the diff hard to read, GitHub don’t support code syntax
highlighting for diffs. I’ve split each part with white lines to make it easier
to spot and follow them.)&lt;/p&gt;

&lt;p&gt;You can see a real world example at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/6679c210ad7aa6814f2c67f886bcd7e3319762ca&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&quot;remote-mediasoup-client-mock&quot;&gt;Remote Mediasoup client mock&lt;/h3&gt;

&lt;p&gt;If you just want to to check how much difficult is to port an existing
Mediasoup based application to Mafalda SFU, you can use the
&lt;a href=&quot;https://github.com/Mafalda-SFU/Remote-Mediasoup-client-mock/&quot;&gt;Remote Mediasoup client mock&lt;/a&gt;
package. This package allows to use the same API as the Remote Mediasoup client,
but instead it’s implemented as a thin wrapper around the Mediasoup package, so
you can use it to validate that Mafalda SFU is a good fit for you, or test your
application in local during development without having to install and configure
a separate server.&lt;/p&gt;

&lt;p&gt;To use it, it’s pretty similar to the Remote Mediasoup client, you just need to
replace the Mediasoup package with the Remote Mediasoup client mock one, and
having the same API on purpose, the code changes would be the same:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm uninstall mediasoup

npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; @mafalda-sfu/remote-mediasoup-client-mock
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+import { once } from &apos;node:events&apos;;
&lt;/span&gt;
- import mediasoup from &apos;mediasoup&apos;;
&lt;span class=&quot;gi&quot;&gt;+ import RemoteMediasoupClientMock from &apos;@mafalda-sfu/remote-mediasoup-client-mock&apos;;
&lt;/span&gt;
+ function onConnectionFailure(error)
&lt;span class=&quot;gi&quot;&gt;+ {
+   console.error(&apos;Error connecting to Remote Mediasoup server:&apos;, error);
+
+   process.exit(1);
+ }
&lt;/span&gt;
+ // URL is not really used, but it&apos;s required for compatibility
&lt;span class=&quot;gi&quot;&gt;+ const client = new RemoteMediasoupClientMock(&apos;ws://example.com&apos;);
+
+ const onProcessSignal = client.destroy.bind(client);
+
+ process.once(&apos;SIGINT&apos;, onProcessSignal);
+ process.once(&apos;SIGTERM&apos;, onProcessSignal);
+
+ await once(client, &apos;mediasoup&apos;).catch(onConnectionFailure);;
+
+ const { mediasoup } = client.on(&apos;error&apos;, console.error);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can see a real world example at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/2216faa3be327a66401306565bb9a518bf0891ef&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;scale-up-mediasoup-vertical&quot;&gt;Scale up: Mediasoup Vertical&lt;/h2&gt;

&lt;p&gt;If your scalability needs are not that high, and you don’t need to scale your
application over several servers, you can scale it to use all the CPUs cores at
once, using the &lt;a href=&quot;/Mediasoup-Vertical&quot;&gt;Mediasoup Vertical&lt;/a&gt; package. This package
aggregates the performance of all the CPUs cores of a server as if they were a
single one, allowing to have sessions beyond the participants capacity of each
CPU core.&lt;/p&gt;

&lt;p&gt;Using it is similar to the Remote Mediasoup client, just only you need to
provide a Mediasoup Manager object as argument. In its simplest form, it’s just
an object with a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mediasoup&lt;/code&gt; property, that is the Mediasoup object instance,
and a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getStats&lt;/code&gt; method, that returns the global and system stats where the
Mediasoup instance is running. In case you want to run Mediasoup Vertical in
local on top of an instance of the original Mediasoup package, you can use the
function provided by the
&lt;a href=&quot;https://www.npmjs.com/package/@mafalda-sfu/mediasoup-getstats-factory&quot;&gt;Mediasoup getStats factory&lt;/a&gt;
package:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# We don&apos;t need to uninstall the Mediasoup package, since we&apos;ll use it&lt;/span&gt;

npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; @mafalda-sfu/mediasoup-getstats-factory
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-mediasoup-vertical-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then, you can use it like this:&lt;/p&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gd&quot;&gt;- import mediasoup from &apos;mediasoup&apos;;
&lt;/span&gt;&lt;span class=&quot;gi&quot;&gt;+ import getStatsFactory from &apos;@mafalda-sfu/mediasoup-getstats-factory&apos;;
+ import MediasoupVertical from &apos;@mafalda-sfu/mediasoup-vertical&apos;;
+ import mediasoupOrig from &apos;mediasoup&apos;;
&lt;/span&gt;
+ const { close, getStats } = await getStatsFactory(mediasoupOrig);
&lt;span class=&quot;gi&quot;&gt;+
+ const mediasoupVertical = new MediasoupVertical(
+   { getStats, mediasoup: mediasoupOrig }
+ )
+   .on(&apos;error&apos;, console.error);
+
+ function onProcessSignal()
+ {
+   mediasoupVertical.destroy();
+   close();
+ }
+
+ process.once(&apos;SIGINT&apos;, onProcessSignal);
+ process.once(&apos;SIGTERM&apos;, onProcessSignal);
+
+ // No need to wait for the connection to be established, original Mediasoup
+ // package object is always available
+
+ const { mediasoup } = mediasoupVertical;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The same as before, you can see a full real world example at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/a232d550e2e9247d95e566c0b14c946007b3f129&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;scale-out-mediasoup-horizontal&quot;&gt;Scale out: Mediasoup Horizontal&lt;/h2&gt;

&lt;p&gt;The same way that &lt;a href=&quot;#going-up-mediasoup-vertical&quot;&gt;Mediasoup Vertical&lt;/a&gt; works as a
wrapper around a Mediasoup Manager to aggregate its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Worker&lt;/code&gt;s performance, the
&lt;a href=&quot;/Mediasoup-Horizontal&quot;&gt;Mediasoup Horizontal&lt;/a&gt; package can be seen as a wrapper
around (multiple) &lt;a href=&quot;#the-basics-remote-mediasoup-client&quot;&gt;Remote Mediasoup client&lt;/a&gt;
instances, where it manages their connection status and the media routing
between their &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Worker&lt;/code&gt; instances, as if all of them were in a single Mediasoup
instance.&lt;/p&gt;

&lt;p&gt;In this case, we create multiple Remote Mediasoup client instances, each one to
connect to a different Remote Mediasoup server, and later we provide them to
Mediasoup Horizontal to do its management:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm uninstall mediasoup

npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-mediasoup-horizontal-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-remote-mediasoup-client-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+ import { once } from &apos;node:events&apos;;
&lt;/span&gt;
- import mediasoup from &apos;mediasoup&apos;;
&lt;span class=&quot;gi&quot;&gt;+ import MediasoupHorizontal from &apos;@mafalda-sfu/mediasoup-horizontal&apos;;
+ import RemoteMediasoupClient from &apos;@mafalda-sfu/remote-mediasoup-client&apos;;
&lt;/span&gt;
+ function newClient(url)
&lt;span class=&quot;gi&quot;&gt;+ {
+    // We disable `listenWorkerDied` to prevent duplicated notifications
+   return new RemoteMediasoupClient(url, { listenWorkerDied: false });
+ }
+
+ function onConnectionFailure(error)
+ {
+   console.error(&apos;Error connecting to Remote Mediasoup servers:&apos;, error);
+
+   process.exit(1);
+ }
+
+ function onProcessSignal()
+ {
+   // We first remove (and destroy) the clients, to do proper cleanup tasks
+   for (const client of clients) client.destroy();
+
+   mediasoupHorizontal.destroy();
+ }
&lt;/span&gt;
+ const urls = [
&lt;span class=&quot;gi&quot;&gt;+   &apos;ws://example1.com&apos;,
+   &apos;ws://example2.com&apos;,
+   &apos;ws://example3.com&apos;
+ ];
&lt;/span&gt;
+ const clients = urls.map(newClient);
&lt;span class=&quot;gi&quot;&gt;+
+ const mediasoupHorizontal = new MediasoupHorizontal(clients);
+
+ process.once(&apos;SIGINT&apos;, onProcessSignal);
+ process.once(&apos;SIGTERM&apos;, onProcessSignal);
+
+ await once(mediasoupHorizontal, &apos;mediasoup&apos;).catch(onConnectionFailure);
+
+ const { mediasoup } = mediasoupHorizontal.on(&apos;error&apos;, console.error);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can find the full example at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/a53ed1368d9a96f9c98edc27038460054ee8fbde&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;full-scale-combined-scalability&quot;&gt;Full scale: combined scalability&lt;/h2&gt;

&lt;p&gt;Finally, the most versatil (not counting on using
&lt;a href=&quot;/Mediasoup-vertical-cluster&quot;&gt;Mediasoup vertical cluster&lt;/a&gt; package) and complex
use case is to combine and use both
&lt;a href=&quot;#going-up-mediasoup-vertical&quot;&gt;Mediasoup Vertical&lt;/a&gt; and
&lt;a href=&quot;#going-out-mediasoup-horizontal&quot;&gt;Mediasoup Horizontal&lt;/a&gt; packages at the same
time. This allows you to scale your streaming sessions over multiple CPU cores
running on several servers, having your capacity only limited by how many
servers you have available. But at the same time, it’s easy to implement it
thanks to the Mafalda SFU packages, because they are designed to be compatible
and combinable between them.&lt;/p&gt;

&lt;p&gt;Similar to the Mediasoup Horizontal use case, you need to create multiple
Remote Mediasoup client instances and pass them to the Mediasoup Horizontal
instance, but in this case, you also need to create a Mediasoup Vertical
that will be initialized with the Mediasoup Horizontal instance, stacking the
Mediasoup Vertical instance on top of the Mediasoup Horizontal one. This way,
the Mediasoup Horizontal instance will be responsible of collecting the Workers
from the multiple Remote Mediasoup servers, and the Mediasoup Vertical one will
aggregate them to make them work as a single one:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm uninstall mediasoup

npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-mediasoup-horizontal-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-mediasoup-vertical-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; ./mafalda-sfu-remote-mediasoup-client-&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.tgz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-diff highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gi&quot;&gt;+ import { once } from &apos;node:events&apos;;
&lt;/span&gt;
- import mediasoup from &apos;mediasoup&apos;;
&lt;span class=&quot;gi&quot;&gt;+ import MediasoupHorizontal from &apos;@mafalda-sfu/mediasoup-horizontal&apos;;
+ import MediasoupVertical from &apos;@mafalda-sfu/mediasoup-vertical&apos;;
+ import RemoteMediasoupClient from &apos;@mafalda-sfu/remote-mediasoup-client&apos;;
&lt;/span&gt;
+ // We disable `listenWorkerDied` to prevent duplicated notifications from both
&lt;span class=&quot;gi&quot;&gt;+ // Mediasoup Horizontal and the Remote Mediasoup clients
+ const options = { listenWorkerDied: false };
+
+ function newClient(url)
+ {
+   return new RemoteMediasoupClient(url, options);
+ }
+
+ function onConnectionFailure(error)
+ {
+   console.error(&apos;Error connecting to Remote Mediasoup servers:&apos;, error);
+
+   process.exit(1);
+ }
+
+ function onProcessSignal()
+ {
+   for (const client of clients) client.destroy();
+
+   // No need to destroy the Mediasoup Vertical instance, it will be destroyed
+   // automatically when the Mediasoup Horizontal instance is destroyed
+   mediasoupHorizontal.destroy();
+ }
&lt;/span&gt;
+ const urls = [
&lt;span class=&quot;gi&quot;&gt;+   &apos;ws://example1.com&apos;,
+   &apos;ws://example2.com&apos;,
+   &apos;ws://example3.com&apos;
+ ];
&lt;/span&gt;
+ const clients = urls.map(newClient);
&lt;span class=&quot;gi&quot;&gt;+
+ const mediasoupHorizontal = new MediasoupHorizontal(clients, options);
+ const mediasoupVertical = new MediasoupVertical(mediasoupHorizontal);
+
+ process.once(&apos;SIGINT&apos;, onProcessSignal);
+ process.once(&apos;SIGTERM&apos;, onProcessSignal);
+
+ await once(mediasoupVertical, &apos;mediasoup&apos;).catch(onConnectionFailure);
+
+ const { mediasoup } = mediasoupVertical.on(&apos;error&apos;, console.error);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The final full example can be found at our
&lt;a href=&quot;https://github.com/Mafalda-SFU/EduMeet-Mafalda/commit/1048f473855729403f562bdee3504ebc67fb74c6&quot;&gt;EduMeet fork&lt;/a&gt;.&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">A frequently asked question we get is how to scale an already existing Mediasoup-based application with Mafalda SFU. From an application or user perspective, the scalability is transparent, because Mafalda SFU handle all the complexity of scaling the application; and from the developer perspective, the API is the same as with a single Mediasoup instance, so there’s no need to learn anything new. The only difference is that the application has to use any of the Mafalda SFU packages instead of directly using the Mediasoup package. This is a pretty simple change, and it can be done for any Mediasoup-based project in a few lines of code, as can be seen in the example of our Remote Mediasoup client mock repository. But to make things easier, we’ll have a few examples in this post about porting EduMeet to Mafalda SFU about how to use and combine the different Mafalda SFU packages to scale your application. You can also check the code changes explained in this post already applied to EduMeet for each different use case in our EduMeet fork, with some additional fixes and updates.</summary></entry><entry xml:lang="en"><title type="html">Mafalda SFU perpetual beta (AKA 1.0)</title><link href="https://mafalda.io/2024/04/01/Mafalda-SFU-perpetual-beta-(AKA-1.0).html" rel="alternate" type="text/html" title="Mafalda SFU perpetual beta (AKA 1.0)" /><published>2024-04-01T00:00:00+00:00</published><updated>2024-04-01T00:00:00+00:00</updated><id>https://mafalda.io/2024/04/01/Mafalda-SFU-perpetual-beta-(AKA-1.0)</id><content type="html" xml:base="https://mafalda.io/2024/04/01/Mafalda-SFU-perpetual-beta-(AKA-1.0).html">&lt;p&gt;After exactly
&lt;a href=&quot;https://twitter.com/el_piranna/status/1396126065677021193&quot;&gt;three years of development&lt;/a&gt;,
lots of awake nights (and sunshines), and uncountable hours of debugging and
testing, we are proud to announce today, April 1st 2024, that we have completed
&lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt; &lt;em&gt;aggregated scalability&lt;/em&gt; and stability
tests, and we are ready to publicly release the first feature complete version
of &lt;a href=&quot;https://mafalda.io&quot;&gt;Mafalda SFU&lt;/a&gt;. In other words, at last &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; has got “1.0 version” status 🥳&lt;/p&gt;

&lt;p&gt;This has been a huge milestone, so between highs and lows during these last
three years (including some hiatus periods due to professional or personal
reasons), we have finally reached the point where we can say that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt;
is ready for any kind of production use case, being that by having lots of small
sized rooms each one under a single CPU capacity limits where
&lt;em&gt;horizontal scalability&lt;/em&gt; is the best fit, one or several medium sized rooms
making use of several CPUs from a single server (&lt;em&gt;vertical scalability&lt;/em&gt;), up to
having one or several big sized rooms extending over multiple servers where
&lt;em&gt;aggregated scalability&lt;/em&gt; shines. Also, the high modularity of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; (at
this moment we have &lt;a href=&quot;https://mafalda.io/projects/&quot;&gt;11 main projects&lt;/a&gt;, and
&lt;a href=&quot;https://github.com/Mafalda-SFU&quot;&gt;73 repositories on GitHub&lt;/a&gt; between public and
privates ones) allows us to adapt to any kind of use case and architecture, and
to evolve over time with your future requirements.&lt;/p&gt;

&lt;p&gt;Now you would be asking,
&lt;em&gt;“why are you calling it ‘&lt;a href=&quot;https://en.wikipedia.org/wiki/Perpetual_beta&quot;&gt;perpetual beta&lt;/a&gt;’ if you are releasing a 1.0 version?”&lt;/em&gt;
Well, there are three reasons for that (&lt;em&gt;three is a magic number&lt;/em&gt; 🎶):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;First of all, as part of our commitment to be 100% API compatible with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt;, we automatically check three times a day for new
&lt;a href=&quot;https://github.com/versatica/mediasoup/releases&quot;&gt;Mediasoup releases&lt;/a&gt; and
pass all our project tests against them (and also, we pass all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt;
projects against
&lt;a href=&quot;https://www.npmjs.com/package/@mafalda-sfu/mediasoup-node-tests&quot;&gt;Mediasoup tests themselves&lt;/a&gt;)
for each new version, so instead of following semantic versioning, we make
and publish a new release of all &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; projects from their git &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;main&lt;/code&gt;
branches every time a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt; version is released, with all both
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; features and bug fixes up to that date. This
way, the version number warrants the compatibility of each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt;
package with the exact same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt; version.&lt;/li&gt;
  &lt;li&gt;We are not going to stop developing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt;: we have a lot of ideas not
only about how to improve &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; reliability and performance (and
they are already pretty high!), but also about new features and projects in
mind that we want to implement, like monitoring tools, offering
&lt;em&gt;Mafalda/Mediasoup as a Service&lt;/em&gt; (&lt;strong&gt;MaaS&lt;/strong&gt;) in addition to our current
on-premises approach, or even decentralized P2P streaming on top of
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt; over multiple regions in the world.&lt;/li&gt;
  &lt;li&gt;And finally, we are going to keep the “perpetual beta” status as a reminder
that we are always looking for feedback, and we are always open to new ideas
and improvements, so if you have any suggestion, idea, or you want to
collaborate with us, please don’t hesitate to &lt;a href=&quot;/contact&quot;&gt;contact us&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And if you are questioning yourself: no, this is not an April Fools’ joke, we
are really releasing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt; 1.0 today, we just wanted to make you think
it 😉 So, if you want to know more about &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda SFU&lt;/code&gt;, please visit our
&lt;a href=&quot;https://mafalda.io&quot;&gt;website&lt;/a&gt;, or if you want to &lt;a href=&quot;/contact&quot;&gt;contact us&lt;/a&gt;, please
write us to &lt;a href=&quot;mailto:info@mafalda.io&quot;&gt;info@mafalda.io&lt;/a&gt;.&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">After exactly three years of development, lots of awake nights (and sunshines), and uncountable hours of debugging and testing, we are proud to announce today, April 1st 2024, that we have completed Mediasoup aggregated scalability and stability tests, and we are ready to publicly release the first feature complete version of Mafalda SFU. In other words, at last Mafalda SFU has got “1.0 version” status 🥳</summary></entry><entry xml:lang="en"><title type="html">How to migrate from Jest to node:test</title><link href="https://mafalda.io/2023/04/24/How-to-migrate-from-Jest-to-node-test.html" rel="alternate" type="text/html" title="How to migrate from Jest to node:test" /><published>2023-04-24T00:00:00+00:00</published><updated>2023-04-24T00:00:00+00:00</updated><id>https://mafalda.io/2023/04/24/How-to-migrate-from-Jest-to-node:test</id><content type="html" xml:base="https://mafalda.io/2023/04/24/How-to-migrate-from-Jest-to-node-test.html">&lt;p&gt;&lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; is one of the most populars testing frameworks for
Javascript and Node.js. Originally developed by Facebook, it’s a one-stop-shop
with testing, assertions, code coverage… but this implies some critics, like
requiring more than 50mb of dependencies. Also, somewhat recently was shown to
&lt;a href=&quot;https://twitter.com/mafalda_sfu/status/1488600128680341517&quot;&gt;be maintained mostly by a single person&lt;/a&gt;,
being that the reason why updates and maintenance was so much slow, so they
&lt;a href=&quot;https://engineering.fb.com/2022/05/11/open-source/jest-openjs-foundation/&quot;&gt;decided to transfer it to OpenJS foundation&lt;/a&gt;.
Also there has been several long standing critics about not providing a pure
environment, or the fact that Jest parses the code, leading to some complexities
when needing to configure transpilation. That has lead to several people looking
for alternatives, and having now a built-in test runner in Node.js, I decided to
see myself how to migrate to it.&lt;/p&gt;

&lt;p&gt;First of all, it’s to identify the replacements for the different Jest features.
As already shown, &lt;a href=&quot;https://nodejs.org/api/test.html&quot;&gt;node:test&lt;/a&gt; module can the
used as the test runner and for mocking, meanwhile
&lt;a href=&quot;https://nodejs.org/api/assert.html&quot;&gt;assert&lt;/a&gt; module can mostly be used for
assertions. A tough topic are inline
&lt;a href=&quot;https://jestjs.io/docs/snapshot-testing&quot;&gt;snapshots&lt;/a&gt;, since it’s a feature I use
a lot. Luckily Jest snapshot engine is provided as an
&lt;a href=&quot;https://www.npmjs.com/package/jest-snapshot&quot;&gt;independent module&lt;/a&gt;, but it don’t
have documentation at all and it’s very tightly coupled with Jest itself. There
are some modules that wrap it and add support for
&lt;a href=&quot;https://www.chaijs.com/&quot;&gt;chai&lt;/a&gt; like
&lt;a href=&quot;https://www.npmjs.com/package/chai-jest-snapshot&quot;&gt;chai-jest-snapshot&lt;/a&gt; and
&lt;a href=&quot;https://www.npmjs.com/package/mocha-chai-jest-snapshot&quot;&gt;mocha-chai-jest-snapshot&lt;/a&gt;,
so it can works as a solution, but not only that would add an extra assertions
library, but also there’s no support for inline snapshots, just only regular
ones. I tried to create my own wrapper without making use of any extra
dependency and adding the support for inline snapshots, but lack of
documentation makes it difficult. Maybe I’ll try again in the future (anybody
interested on sponsors me? :-) ).&lt;/p&gt;

&lt;p&gt;Having now the elements, doing the migration is surprisingly pretty straighforward:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chai&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chai-jest-snapshot&lt;/code&gt; as dev dependencies, and remove any Jest
related dependency and configuration.&lt;/li&gt;
  &lt;li&gt;Rename &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;__tests__&lt;/code&gt; folder(s) to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Replace &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jest&lt;/code&gt; command with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node --test&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Add the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node:test&lt;/code&gt; functions that previously were set as globals by Jest:&lt;/p&gt;

    &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;afterEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;beforeEach&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;throws&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;node:test&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Enable the snapshot support, and the replacement of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect()&lt;/code&gt; function:&lt;/p&gt;

    &lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chai&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;chaiJestSnapshot&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;chai-jest-snapshot&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;


&lt;span class=&quot;nx&quot;&gt;chai&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;use&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chaiJestSnapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;chaiJestSnapshot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;resetSnapshotRegistry&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node:test&lt;/code&gt; run each test in a different process, we need to add the
snapshot support in each test file.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Comment or remove calls to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect.assertions()&lt;/code&gt;, since there’s no equivalent
in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;node:test&lt;/code&gt;. This was possible on Jest since assertions were integrated in
the tests runner.&lt;/li&gt;
  &lt;li&gt;Await the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Promise&lt;/code&gt;s instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect()&lt;/code&gt; on them (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(await promise)&lt;/code&gt;
instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;await expect(promise).resolves&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Replace Jest idioms:
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(func).toThrowErrorMatchingInlineSnapshot(error)&lt;/code&gt; with
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;throws(func, error)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(value).toBe(expected)&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;deepStrictEqual(value, expected)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(value).toBeInstanceOf(class)&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ok(value instanceof class)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(value).toBeUndefined()&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ifError(value)&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(value).toMatchSnapshot()&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;expect(value).to.matchSnapshot()&lt;/code&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Missing points as already commented, would be the support for inline snapshots,
and maybe create a &lt;a href=&quot;https://github.com/skovhus/jest-codemods&quot;&gt;codemods&lt;/a&gt; to
automate the process :-)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2023/04/24/How-to-migrate-from-Jest-to-node-test/&quot;&gt;https://piranna.github.io/2023/04/24/How-to-migrate-from-Jest-to-node-test/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">Jest is one of the most populars testing frameworks for Javascript and Node.js. Originally developed by Facebook, it’s a one-stop-shop with testing, assertions, code coverage… but this implies some critics, like requiring more than 50mb of dependencies. Also, somewhat recently was shown to be maintained mostly by a single person, being that the reason why updates and maintenance was so much slow, so they decided to transfer it to OpenJS foundation. Also there has been several long standing critics about not providing a pure environment, or the fact that Jest parses the code, leading to some complexities when needing to configure transpilation. That has lead to several people looking for alternatives, and having now a built-in test runner in Node.js, I decided to see myself how to migrate to it.</summary></entry><entry xml:lang="en"><title type="html">Profiling `npm install` times</title><link href="https://mafalda.io/2023/02/05/Profiling-npm-install-times.html" rel="alternate" type="text/html" title="Profiling `npm install` times" /><published>2023-02-05T00:00:00+00:00</published><updated>2023-02-05T00:00:00+00:00</updated><id>https://mafalda.io/2023/02/05/Profiling-npm-install-times</id><content type="html" xml:base="https://mafalda.io/2023/02/05/Profiling-npm-install-times.html">&lt;p&gt;When installing Mafalda SFU packets, a problem I’ve suffered several times are
install times, specially since I’m using git dependencies. I tried to reduce
times by &lt;a href=&quot;https://www.npmjs.com/org/mafalda&quot;&gt;publishing&lt;/a&gt; some of the most common
packages to npm, so removing need to install and compile development
dependencies like Typescript, but still install times were huge for no reason,
so I needed some way to measure the install time of &lt;em&gt;each one&lt;/em&gt; of the
dependencies. This lead out options like
&lt;a href=&quot;https://man7.org/linux/man-pages/man1/time.1.html&quot;&gt;UNIX time command&lt;/a&gt; or tools
like &lt;a href=&quot;https://www.npmjs.com/package/slow-deps&quot;&gt;slow-deps&lt;/a&gt;, so just by change, I
&lt;a href=&quot;https://stackoverflow.com/a/39991677/586382&quot;&gt;found on StackOverflow&lt;/a&gt; a
reference to &lt;a href=&quot;https://github.com/paypal/gnomon&quot;&gt;gnomon&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnomon&lt;/code&gt; just only prepend timestamp information to the standard output of
another command, so it’s a bit crude for my use case, but with some tweaking can
provide the needed info. In this case, by running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt; in verbose mode,
we can get detailed info of each one of the steps it follows, so we can measure
its time delay. Just only, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm install&lt;/code&gt; provides that info in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stderr&lt;/code&gt; (non
sense, what is using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdout&lt;/code&gt; for?), so we need to redirect it first so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnomon&lt;/code&gt;
can get it on its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stdin&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;npm &lt;span class=&quot;nb&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt; 2&amp;gt;&amp;amp;1 | gnomon
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will show the output in two columns, being the timestamps in the left one.
On average, it looks like 90% takes less than 1 second and 99% less than 10
seconds… but there’s a 1% of outliers that get up to 1 minute or more.
Focusing on that ones, we find two of them (output is truncated for brevity):&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;12.8811&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npmjs.org/@jscpd%2fhtml-reporter 2117ms (cache revalidated)&lt;/span&gt;
   &lt;span class=&quot;mf&quot;&gt;0.2557&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npmjs.org/ws 258ms (cache revalidated)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;59.4045&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//codeload.github.com/Mafalda-SFU/on-change/tar.gz/718eda3ad6c777f7f8df08908603aab8e9f1082e 1325ms (cache revalidated)&lt;/span&gt;
   &lt;span class=&quot;mf&quot;&gt;1.1857&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.1&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node_modules&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;bcrypt&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;node&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;pre&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;gyp&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;install&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;fallback&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;to&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;build&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnomon&lt;/code&gt; show the time each line has been the last one shown in the terminal, so
that time correspond to the generation of &lt;em&gt;the next line&lt;/em&gt;. This is particularly
useful when showing “starting…” lines, to know in what task this time has been
employed so much, but when showing only the conclusions (like it’s the case), we
need to focus on the next line instead.&lt;/p&gt;

&lt;p&gt;Total time, 206.7054s, whom 12 are spent on downloading
&lt;a href=&quot;https://github.com/websockets/ws&quot;&gt;ws&lt;/a&gt; package, and a full minute compiling
&lt;a href=&quot;https://www.npmjs.com/package/bcrypt&quot;&gt;bcrypt&lt;/a&gt; instead of using a prebuild
image. It’s a bit too much, but since the total time is just a bit over 3
minutes, and the problem is probably due to use Node.js v19.4.0 instead of a LTS
version, I would not worry too much.&lt;/p&gt;

&lt;p&gt;Another project I have been having more important problems with install time has
been the Remote Mediasoup integration tests, where install of &lt;em&gt;all&lt;/em&gt; Mafalda SFU
subprojects is involved, and in fact Github Actions cancel the job step after
about 10-12 minutes (does it has a timeout, or it thinks I’m mining
cryptocurrencies?). So checking with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnomon&lt;/code&gt;, the timings percentages seems
similar, just only we found the outliers got rampage:&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;34.4881&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;verb&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;logfile&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;home&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;piranna&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;_logs&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2023&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;02&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;05&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;T11_55_57_787Z&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;
   &lt;span class=&quot;mf&quot;&gt;0.2060&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npmjs.org/@mafalda%2feslint-config 2345ms (cache revalidated)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
  &lt;span class=&quot;mf&quot;&gt;13.4045&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npmjs.org/@piranna%2frpc 1680ms (cache revalidated)&lt;/span&gt;
   &lt;span class=&quot;mf&quot;&gt;0.1576&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//registry.npmjs.org/@xmldom%2fxmldom 227ms (cache revalidated)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
 &lt;span class=&quot;mf&quot;&gt;743.5718&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;http&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;fetch&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;GET&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;200&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;c1&quot;&gt;//codeload.github.com/Mafalda-SFU/multi-map/tar.gz/08f018c637bd1261558fecaf3998e099298dca14 1340ms (cache revalidated)&lt;/span&gt;
   &lt;span class=&quot;mf&quot;&gt;0.0409&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;mafalda&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/mediasoup@3.11.8 install node_modules/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;mafalda&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/mediasoup node npm-scripts.js instal&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;l
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;...&lt;/span&gt;
 &lt;span class=&quot;mf&quot;&gt;188.7822&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;mafalda&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/rope-client@0.0.0 postinstall { code: 0, signal: null &lt;/span&gt;&lt;span class=&quot;err&quot;&gt;}
&lt;/span&gt;   &lt;span class=&quot;mf&quot;&gt;0.1286&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;s&lt;/span&gt;   &lt;span class=&quot;nx&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;info&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;run&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;mediasoup&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;10.5&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;postinstall&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;nl&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;signal&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Total time, 1059.6964s (18 minutes), whom of them 34 seconds are spent on
downloading Mafalda SFU
&lt;a href=&quot;https://github.com/Mafalda-SFU/eslint-config&quot;&gt;eslint-config&lt;/a&gt; package (hum? Why
so much?), and 15 minutes compiling both Mafalda SFU custom Mediasoup prebuilds
and the regular one
(&lt;a href=&quot;https://github.com/versatica/mediasoup/issues/982&quot;&gt;there are some incompatibilities starting on version 3.10.6&lt;/a&gt;
that prevent using the latest one with Mafalda SFU, working on fixing them at
this moment).&lt;/p&gt;

&lt;p&gt;Mediasoup, &lt;em&gt;what else?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt; by design is compiled on destination
platform at install time, so it can take a lot of time to install with all CPU
cores at 100% and the machine totally unresponsive (and I have a laptop with 20
CPU cores…). That’s why I’ve
&lt;a href=&quot;https://github.com/dyte-in/mediasoup/pkgs/npm/mediasoup&quot;&gt;tried in the past&lt;/a&gt; and
&lt;a href=&quot;https://www.npmjs.com/package/@mafalda/mediasoup&quot;&gt;now again&lt;/a&gt; to generate and
publish automated prebuilds of Mediasoup with a
&lt;a href=&quot;https://github.com/Mafalda-SFU/mediasoup/blob/v3/.github/workflows/nightly.yaml&quot;&gt;nightly&lt;/a&gt;
Github Action. I hope to make them work properly with newer versions of the
prebuild images.&lt;/p&gt;

&lt;p&gt;In any case, my internet connection is not very fast or stable, so it’s not
surprising that it takes so long to download all the packages or there are so
much differences between them, and results are not conclusive but provides a
good guidance. Probably with a better internet connection, it could show the
bottleneck is somewhere else, probably at the compilation of packages. In
addition to that, a good improvement for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gnomon&lt;/code&gt; or a similar tool would be to
add statistics about CPU load, to see what are the installed packages that put
more pressure on the install process.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2023/02/05/Profiling-npm-install-times/&quot;&gt;https://piranna.github.io/2023/02/05/Profiling-npm-install-times/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">When installing Mafalda SFU packets, a problem I’ve suffered several times are install times, specially since I’m using git dependencies. I tried to reduce times by publishing some of the most common packages to npm, so removing need to install and compile development dependencies like Typescript, but still install times were huge for no reason, so I needed some way to measure the install time of each one of the dependencies. This lead out options like UNIX time command or tools like slow-deps, so just by change, I found on StackOverflow a reference to gnomon.</summary></entry><entry xml:lang="en"><title type="html">How to use private repositories as npm git dependencies on Github Actions</title><link href="https://mafalda.io/2022/12/25/How-to-use-private-repositories-as-npm-git-dependencies-on-Github-Actions.html" rel="alternate" type="text/html" title="How to use private repositories as npm git dependencies on Github Actions" /><published>2022-12-25T00:00:00+00:00</published><updated>2022-12-25T00:00:00+00:00</updated><id>https://mafalda.io/2022/12/25/How-to-use-private-repositories-as-npm-git-dependencies-on-Github-Actions</id><content type="html" xml:base="https://mafalda.io/2022/12/25/How-to-use-private-repositories-as-npm-git-dependencies-on-Github-Actions.html">&lt;p&gt;I’m advocate of automatization, and that includes not only CI/CD pipelines, but
also I wanted to do it for documentation publishing.
&lt;a href=&quot;https://mafalda.io/&quot;&gt;Mafalda SFU&lt;/a&gt; is split in a lot of packages (currently more
than 30!), so I wanted to have a single place where to publish the documentation
of all of them. &lt;a href=&quot;https://pages.github.com/&quot;&gt;Github Pages&lt;/a&gt; allows to host a
website for your organization or username by free (this blog and personal site
&lt;a href=&quot;https://github.com/piranna/piranna.github.io&quot;&gt;already makes use of it&lt;/a&gt;), and it
can also host automatically a website for each repository as sub-paths of your
username/organization main website. Problem is, that it only works for open
source repositories or for paid plans, and most of the Mafalda SFU repositories
are private ones. So since the Mafalda SFU project website is already hosted on
Github Pages as a
&lt;a href=&quot;https://github.com/Mafalda-SFU/Mafalda-SFU.github.io&quot;&gt;public repository&lt;/a&gt;, I
decided to store and serve from it all the other repositories documentation as
well… doing it in an automated way :-)&lt;/p&gt;

&lt;p&gt;The task can be splitted in two halves:
&lt;a href=&quot;#generate-the-documentation&quot;&gt;generate the documentation&lt;/a&gt;, and
&lt;a href=&quot;#publish-the-documentation&quot;&gt;publish it&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;tldr&quot;&gt;TL;DR&lt;/h2&gt;

&lt;p&gt;This is the Github Actions workflow I’m using to tests the different Mafalda
SFU sub-projects, generate their documentation, and publish it to the Mafalda
SFU project website:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;test, build documentation and publish&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;pull_request&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;publish&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v3&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/setup-node@v3&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;webfactory/ssh-agent@v0.7.0&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;ssh-private-key&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm ci&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm test&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;npm run docs&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;cpina/github-action-push-to-another-repository@v1.5.1&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;SSH_DEPLOY_KEY&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;$&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;destination-github-username&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Mafalda-SFU&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;destination-repository-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Mafalda-SFU.github.io&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;source-directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docs&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;target-directory&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;docs/$&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;user-email&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bot@mafalda.io&lt;/span&gt;
          &lt;span class=&quot;na&quot;&gt;user-name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Mafalda bot&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;generate-the-documentation&quot;&gt;Generate the documentation&lt;/h2&gt;

&lt;p&gt;To automatically generate the documentation, we have two aproaches: generate it
as a &lt;a href=&quot;https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks&quot;&gt;git hook&lt;/a&gt;, or do
it on the CI servers. I use to prefer the first option, specially for complex
projects where people of different levels are working on the code (like Juniors
&amp;amp; Seniors, or new employees), or for the projects where the documentation is
going to be directly published on Github Pages (like open source projects) so
the documentation is close to the code as reference, and also it’s possible to
see how it has evolved during the time. But since it’s not possible to publish
it as I’ve already said it before (if not, I would not be writing this post
:-) ), it has had more weight my policy of not having generated code or
artifacts stored as part of the source code of the git repository itself, so I
decided to generate the documentation on the CI servers.&lt;/p&gt;

&lt;p&gt;To generate the documentation, I’m using the
&lt;a href=&quot;https://github.com/jsdoc2md/jsdoc-to-markdown&quot;&gt;jsdoc-to-markdown&lt;/a&gt; package. This
imply to need to install the project dependencies on the CI servers, and this is
where the problem arises: since I have not published any of the Mafalda SFU
dependencies as a npm package, I need to use them as git dependencies, and being
private ones, the Github Actions CI servers needs permissions to access to them.
In normal conditions where only it’s accessed the repo itself, or when using
private packages from the
&lt;a href=&quot;https://github.com/features/packages&quot;&gt;Github Packages Registry&lt;/a&gt;, it’s enough
with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GITHUB_TOKEN&lt;/code&gt; secret or with a
&lt;a href=&quot;https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token&quot;&gt;Personal Access Token&lt;/a&gt;,
but since we’re using git dependencies, it’s not possible to use them to
authenticate the git requests, only possible is with git credentials and/or an
SSH key pair.&lt;/p&gt;

&lt;p&gt;The first thing I’ve done is to create a new
&lt;a href=&quot;https://docs.github.com/en/developers/overview/managing-deploy-keys#machine-users&quot;&gt;machine user&lt;/a&gt;
account to authenticate and operate from Github Actions. This is for security, I
could have used my own account, but then all operations would have been done in
my name. This way, it’s possible to have a separate user with the lowest needed
permissions (in this case just only read access to the private repositories,
that’s the one set when adding an user as one of the organization members by
default), and also it’s possible to revoke the access to the repositories if
needed. After that, I’ve created a new SSH key pair, and added the public key to
the Github &lt;em&gt;machine user&lt;/em&gt; account I’ve just created before, so it can operate on
the repositories&lt;/p&gt;

&lt;p&gt;After that, what we need to do is to add the private key to the Github Actions,
so it can be available when trying to install the git dependencies. This can be
done with the &lt;a href=&quot;https://github.com/webfactory/ssh-agent&quot;&gt;ssh-agent&lt;/a&gt; action, that
registers globally the provided private key. To do so, we need to set the
private key as one of the repository secrets (it’s not possible to add it as an
organization level secret on free plans), and then use it in the config of the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-agent&lt;/code&gt; action in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh-private-key&lt;/code&gt; parameter. Just with that, we can be
able to install the npm git dependencies from Github Actions CI servers, and
generate our documentation.&lt;/p&gt;

&lt;h2 id=&quot;publish-the-documentation&quot;&gt;Publish the documentation&lt;/h2&gt;

&lt;p&gt;Once that we have the documentation generated, it’s time to copy it to the main
repository. This is done with the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/&quot;&gt;github-action-push-to-another-repository&lt;/a&gt;
action, that can push the contents of a directory to another repository
different of the one where the action is running on. The config of the action is
pretty straighforward, just setting the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#source-directory&quot;&gt;source directory&lt;/a&gt;
from where to copy the files, the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#destination-repository-name&quot;&gt;destination repository name&lt;/a&gt;,
the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#destination-github-username&quot;&gt;Github username/organization&lt;/a&gt;
that owns the destination repository, and the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#target-directory-optional&quot;&gt;target directory&lt;/a&gt;
where to copy them (this is important, by default it fully wipes the target
repository). Just by config that parameters it would work, but commits would be
mostly annonimous, so it’s better to also set the
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#user-name-optional&quot;&gt;user name&lt;/a&gt;
and
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/configuration.html#user-email-optional&quot;&gt;user email&lt;/a&gt;
that will be used to commit the changes.&lt;/p&gt;

&lt;p&gt;The only tricky part of configuring &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;github-action-push-to-another-repository&lt;/code&gt;
is the access to the repository itself, since we need to provide another
credentials with write access. The most safe way to do it is to
&lt;a href=&quot;https://cpina.github.io/push-to-another-repository-docs/setup-using-ssh-deploy-keys.html#setup-ssh-deploy-keys&quot;&gt;create a new SSH deploy key&lt;/a&gt;
that will be used to provide write access &lt;em&gt;only&lt;/em&gt; on the destination repository,
in this case the main documentation one. This is done by creating a new SSH key
pair, and adding the public key to the destination repository as a deploy key
with write access. Then, we need to add the private key as one of the repository
secrets, and use it in the config of the action in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH_DEPLOY_KEY&lt;/code&gt;. After
that, each time that the action is executed, it will push the generated
documentation to the main repository, creating a new commit with the user email
and name that we provided, and a commit message pointing to the original commit
in the source repository where we generated the documentation from, having this
way a two-ways cross-reference.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2022/12/25/How-to-use-private-repositories-as-npm-git-dependencies-on-Github-Actions/&quot;&gt;https://piranna.github.io/2022/12/25/How-to-use-private-repositories-as-npm-git-dependencies-on-Github-Actions/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">I’m advocate of automatization, and that includes not only CI/CD pipelines, but also I wanted to do it for documentation publishing. Mafalda SFU is split in a lot of packages (currently more than 30!), so I wanted to have a single place where to publish the documentation of all of them. Github Pages allows to host a website for your organization or username by free (this blog and personal site already makes use of it), and it can also host automatically a website for each repository as sub-paths of your username/organization main website. Problem is, that it only works for open source repositories or for paid plans, and most of the Mafalda SFU repositories are private ones. So since the Mafalda SFU project website is already hosted on Github Pages as a public repository, I decided to store and serve from it all the other repositories documentation as well… doing it in an automated way :-)</summary></entry><entry xml:lang="en"><title type="html">Presenting Mediasoup Horizontal</title><link href="https://mafalda.io/2022/01/02/Presenting-Mediasoup-Horizontal.html" rel="alternate" type="text/html" title="Presenting Mediasoup Horizontal" /><published>2022-01-02T00:00:00+00:00</published><updated>2022-01-02T00:00:00+00:00</updated><id>https://mafalda.io/2022/01/02/Presenting-Mediasoup-Horizontal</id><content type="html" xml:base="https://mafalda.io/2022/01/02/Presenting-Mediasoup-Horizontal.html">&lt;p&gt;Although &lt;a href=&quot;https://mafalda.io&quot;&gt;Mafalda SFU&lt;/a&gt; is mainly focused on vertical scaling
of &lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt; and the WebRTC stack, the main problem
I’ve found companies are facing is about how to easily implement Medisoup
&lt;a href=&quot;https://en.wikipedia.org/wiki/Scalability#Horizontal_or_scale_out&quot;&gt;horizontal&lt;/a&gt;
scaling. I’ve been working on a solution for this problem for a while on, and
since Mafalda SFU is build on top of Mediasoup, it’s also needed to help it to
provide transparent vertical and horizontal scaling, so let’s see how it works.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup-Horizontal&lt;/code&gt; is a manager that allows to remotely control multiple
instances of Mediasoup, providing a simple and easy to use API based on the one
from the Javascript
&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set&quot;&gt;Set&lt;/a&gt;
object. It allows to add and remove Remote Mediasoup &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt; objects and
manage them, although is not directly tied to them so you can provide your own
objects following that same API, for example to also control a local Mediasoup
instance in addition to the remote ones.&lt;/p&gt;

&lt;p&gt;When adding a Remote Mediasoup &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt; object to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup-Horizontal&lt;/code&gt;
instance, it checks that all the Remote Mediasoup instances are compatible
between them, so you don’t have to worry about in what instance your Mediasoup
objects are being created. After that, it monitors the creation of new objects,
and also does it with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt; current objects in case they are later
destroyed.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup-Horizontal&lt;/code&gt; also provides a Mediasoup compatible API, so by using it
you can &lt;em&gt;auto-magically&lt;/em&gt; enable your application to scale horizontally without
needing to change your current code. The “magic” happens by using objects that
provides the same API of Mediasoup
&lt;a href=&quot;https://mediasoup.org/documentation/v3/mediasoup/api/#worker&quot;&gt;Worker&lt;/a&gt; and
&lt;a href=&quot;https://mediasoup.org/documentation/v3/mediasoup/api/#router&quot;&gt;Router&lt;/a&gt; classes,
but internally proxying the method calls to their internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Connection&lt;/code&gt;
objects, shared by all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Client&lt;/code&gt; instances currently connected to the same
server. This is done this way to be transparent to the actual objects
references being done at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup-Horizontal&lt;/code&gt; level.&lt;/p&gt;

&lt;p&gt;After that, implementation of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pipeToRouter()&lt;/code&gt; method is fairly trivial. Code is
an optimized version of original Mediasoup one, just only with fine-grain errors
management and with some performance optimizations to reduce delay of events
propagation, so general behaviour is the same, although there’s space for
improvements. In fact, I proposed to
&lt;a href=&quot;https://github.com/versatica/mediasoup/issues/705&quot;&gt;move it out officially&lt;/a&gt; to a
separate library so it can be reused by other projects like this one, but the
proposal was rejected.&lt;/p&gt;

&lt;p&gt;And that’s it, that’s how &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup-Horizontal&lt;/code&gt; works. Next steps I’m planning
about are to improve performance and resilience, allowing it to better recover
from network re-connections, and also works towards implementing a tool for
monitoring in real time the status of all the Remote Mediasoup instances.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2022/01/02/Presenting-Mediasoup-Horizontal/&quot;&gt;https://piranna.github.io/2022/01/02/Presenting-Mediasoup-Horizontal/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">Although Mafalda SFU is mainly focused on vertical scaling of Mediasoup and the WebRTC stack, the main problem I’ve found companies are facing is about how to easily implement Medisoup horizontal scaling. I’ve been working on a solution for this problem for a while on, and since Mafalda SFU is build on top of Mediasoup, it’s also needed to help it to provide transparent vertical and horizontal scaling, so let’s see how it works.</summary></entry><entry xml:lang="en"><title type="html">WebRTC horizontal scaling</title><link href="https://mafalda.io/2021/09/26/WebRTC-horizontal-scaling.html" rel="alternate" type="text/html" title="WebRTC horizontal scaling" /><published>2021-09-26T00:00:00+00:00</published><updated>2021-09-26T00:00:00+00:00</updated><id>https://mafalda.io/2021/09/26/WebRTC-horizontal-scaling</id><content type="html" xml:base="https://mafalda.io/2021/09/26/WebRTC-horizontal-scaling.html">&lt;p&gt;When approaching the horizontal scaling of WebRTC servers, we have two main
approaches: decentralized P2P, and using a central server. Each one has its own
drawbacks and advantages, and I had difficulties to identify what approach was
the best, since I usually have a personal preference for pure P2P architectures,
but they are not the most simple nor always the more efficient ones. So when
deciding how to approach Mafalda horizontal scaling, I needed to consider the
pros and cons of each use case I would need, and here we have my conclusions.&lt;/p&gt;

&lt;h2 id=&quot;p2p&quot;&gt;P2P&lt;/h2&gt;

&lt;p&gt;The first one needs to maintain its own list of servers where to ask for extra
resources. This list can be provided when starting the server, but if the extra
servers can be added and removed dynamically, this list will need to be updated
later, so we need a way to update the list. In addition to that, when querying
for another server with enought free resources to connect to, since we don’t
have a central place to store the info, we need to ask to all the servers to get
their updated state so we can apply the heuristics to decide which one to use,
or the servers would need to send their state info to the other ones at
intervals. In both cases, if the number of servers is large, the bandwidth
needed to send the state info to all the servers will be high. One solution to
this would be to use a DHT or a database, but in both cases we would need anyway
to download the info to do the heuristics, and the second one leads us to a
centralized solution.&lt;/p&gt;

&lt;h2 id=&quot;central-server&quot;&gt;Central server&lt;/h2&gt;

&lt;p&gt;So instead of having a central database where to store the state of the servers,
and need to query it to get the state of the servers, we can have instead
central server to do the queries for us. This central server would became a
single point of failure in case it gets down, so we can have mutiple instances
and store the state info of the servers in a database. Seems a bit like walking
in circles, but this one can be close to that query servers, so there would not
be so much bandwidth issues if they are located in the same place.&lt;/p&gt;

&lt;p&gt;This central server (or servers) would just receive a request from the WebRTC
servers asking to connect to another empty enought server to increase their own
resources, and do all the heuristics to decide which one to use on its own, or
also decide to automatically spin-up a new server if there’s no available ones
and it’s configured to do it, so all the logic would be centralized in a single
place and being transparent to the other ones. It would do the request for the
state of the WebRTC servers and cache it, or receive the updates from them at
intervals, probably using an unreliable WebSocket connection or any other
similar one since the info is ephimeral and there’s no need to keep it in sync
all the time.&lt;/p&gt;

&lt;p&gt;In conclussion, between these two approaches, it seems that the central server
is the one that best fit this particular use case, since it’s the one that best
balance CPU and bandwidth usage, and also allow to easily change the heuristics
without needing to spin-down the WebRTC servers.&lt;/p&gt;

&lt;h2 id=&quot;update-mafalda-sfu-aproach&quot;&gt;Update: Mafalda SFU aproach&lt;/h2&gt;

&lt;p&gt;Based on these two traditional network architectures, I decided to apply my own
solutions for each one of the use cases, the centralized and decentralized one.&lt;/p&gt;

&lt;h3 id=&quot;mafalda-horizontal&quot;&gt;Mafalda-horizontal&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda-horizontal&lt;/code&gt; package is build on top of &lt;em&gt;Remote Mafalda&lt;/em&gt;, and the same
than this one or the original &lt;em&gt;Mafalda&lt;/em&gt; one, it follows the same Mafalda API, so
it’s intercambiable with them and allow easily to upgrade from the vertical
scalability aproach to the horizontal one without modifying the code. It works
as a client to multiple &lt;em&gt;Remote Mafalda&lt;/em&gt; instances, managing the balancing of
the resources, and also the routing between the servers. Also, since it follows
the Mafalda API, it can be used as a &lt;em&gt;Remote Mafalda&lt;/em&gt; instance, so it’s possible
to implement an hierarchical architecture with multiple layers of servers, all
of them accesible from a single endpoint, while at the same time not wasting
resources to propagate the clients media between the servers since these ones
gets connected directly to the instances of Mediasoup.&lt;/p&gt;

&lt;h3 id=&quot;mafalda-swarm&quot;&gt;Mafalda-swarm&lt;/h3&gt;

&lt;p&gt;On the other hand, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda-swarm&lt;/code&gt; package implements a decentralized federated
P2P aproach, and in fact this is the one I originally envisioned to implement,
being &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda-horizontal&lt;/code&gt; just an intermediate step to help me to develop the
remote versions of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mediasoup&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda&lt;/code&gt; modules… and have quickly an
horizontal scalling solution that can be of commercial interest while I develop
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda-swarm&lt;/code&gt;, that’s the really interesting and funny one to implement :-P&lt;/p&gt;

&lt;p&gt;In Mediasoup, the most important info to consume a stream is its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Producer&lt;/code&gt; ID.
These ones are unique UUIDs, and they can be explicitly set by hand. In fact,
they are the only ones Mediasoup objects whom constructor allow it, and the
Mediasoup method &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Router.pipeToRouter()&lt;/code&gt; sets the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Producer&lt;/code&gt; in the destination
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Router&lt;/code&gt; with the ID in the original one, so it can be considered a good
practice. Based on that, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mafalda-swarm&lt;/code&gt; makes use of a DHT to map the
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Producer&lt;/code&gt; IDs to the servers where’s located the original one, so it’s possible
to directly ask to it and decrease lags and delays. The original producer server
can be busy, so it will delegate the request to another server from the ones
that are already consuming that stream, so this way they can balance themselves
without external administration, just adding new servers to the swarm.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2021/09/26/WebRTC-horizontal-scaling/&quot;&gt;https://piranna.github.io/2021/09/26/WebRTC-horizontal-scaling/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">When approaching the horizontal scaling of WebRTC servers, we have two main approaches: decentralized P2P, and using a central server. Each one has its own drawbacks and advantages, and I had difficulties to identify what approach was the best, since I usually have a personal preference for pure P2P architectures, but they are not the most simple nor always the more efficient ones. So when deciding how to approach Mafalda horizontal scaling, I needed to consider the pros and cons of each use case I would need, and here we have my conclusions.</summary></entry><entry xml:lang="en"><title type="html">Presenting Mafalda SFU</title><link href="https://mafalda.io/2021/07/22/Presenting-Mafalda-SFU.html" rel="alternate" type="text/html" title="Presenting Mafalda SFU" /><published>2021-07-22T00:00:00+00:00</published><updated>2021-07-22T00:00:00+00:00</updated><id>https://mafalda.io/2021/07/22/Presenting-Mafalda-SFU</id><content type="html" xml:base="https://mafalda.io/2021/07/22/Presenting-Mafalda-SFU.html">&lt;p&gt;&lt;a href=&quot;https://github.com/Mafalda-SFU&quot;&gt;Mafalda SFU&lt;/a&gt; is a massively vertical and
horizontal scalable SFU built on top of &lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt;.
This allow to have (theorically) unlimited sized WebRTC-based video room calls.&lt;/p&gt;

&lt;p&gt;Mediasoup is one of the most important SFUs: open source, performant, and easy
to use, but it’s too much low level and RTP streams oriented, so it’s main issue
is its lack of off-the-shelf scalability. Due to that, Mediasoup &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Router&lt;/code&gt;
instances are limited to a single CPU core and a reduced number of participants.&lt;/p&gt;

&lt;p&gt;During to the Covid pandemic, I’ve been working extensively in WebRTC projects
in the last year and a half, and one of the topics that most times surfaced was
how to use Mediasoup with big video rooms in both an easy to use and performant
way. So, after finding some issues in the way other projects were aproaching
this problem, I started to think about my own solution.&lt;/p&gt;

&lt;p&gt;Mafalda aproach is simple: just create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MafaldaRouter&lt;/code&gt; instance, and it will
scale itself to use all the server CPU cores and create the connections to other
servers. From both developers and users PoV, it’s like using a single Mediasoup
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Router&lt;/code&gt; instance, but running in a server with an humongous big CPU. Not only
that, but since Mafalda schedule algorythm try to minimize the number of used
connections and CPUs, you can run multiple &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MafaldaRouter&lt;/code&gt; instances in the same
server, and Mafalda will balance the resources between them. And when they are
not needed anymore, connections get closed and the CPU cores are freed, so you
can stop the servers to reduce costs, all this without needing to administer
them and almost without configuration.&lt;/p&gt;

&lt;p&gt;The main features of Mafalda are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Fully automated: you only need to create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MafaldaRouter&lt;/code&gt; instance, and it
will take care of everything else&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Optimiced resources consumption on half-used machines compared to libraries
using other architectures, allowing to host multiple rooms on the same machine
without needing to pre-assign resources, that could lead to a suboptimal usage&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Designed to support adding a new server to the cluster, without needing to
restart the whole cluster. It also can make use automatically of new added CPU
cores in a server (if it has support) without needing to restart the process&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;API heavily influenced by &lt;a href=&quot;https://mediasoup.org/&quot;&gt;Mediasoup&lt;/a&gt;, so it’s easy
to understand, use and migrate from your existing code&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;100% acceptance tests code coverage for both lines, branches statements and
functions. This has shown to be very useful to find bugs until the
&lt;a href=&quot;https://twitter.com/el_piranna/status/1400401650993532929&quot;&gt;very last moment&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve been working on Mafalda since March 2021, and so far I’ve fully implemented
&lt;a href=&quot;https://en.wikipedia.org/wiki/Scalability#Vertical_or_scale_up&quot;&gt;vertical&lt;/a&gt;
scaling, and got to a point where both API and code are stable and ready to
start testing in real environments.&lt;/p&gt;

&lt;p&gt;At the same time, I’m currently working on
&lt;a href=&quot;https://en.wikipedia.org/wiki/Scalability#Horizontal_or_scale_out&quot;&gt;horizontal&lt;/a&gt;
scaling too. So far I’ve implemented a client library with
&lt;a href=&quot;https://twitter.com/mafalda_sfu/status/1417030937674665984&quot;&gt;the same API&lt;/a&gt; of
local Mafalda instances, and designed an events-based RPC protocol that
replicate the state of the remote Mediasoup objects and the Mafalda instances,
allowing to work with them as if they were local ones. That would help not only
to easily migrate deployments from local to cluster environments, but also to
simplify automated management of horizontal scaling and inter-server
connections. More on that in a future article ;-)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Previously published at &lt;a href=&quot;https://piranna.github.io/2021/07/22/Presenting-Mafalda-SFU/&quot;&gt;https://piranna.github.io/2021/07/22/Presenting-Mafalda-SFU/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content><author><name>Jesús Leganés-Combarro ‘piranna’</name></author><summary type="html">Mafalda SFU is a massively vertical and horizontal scalable SFU built on top of Mediasoup. This allow to have (theorically) unlimited sized WebRTC-based video room calls.</summary></entry></feed>