Your users expect a fast and smooth experience. Sometimes what stands in the way is how long it takes to download data. The flyweight design pattern is a possible solution to help reduce bandwidth in your app.
Data is a precious thing. Every organization wants as much as it can get a hold of. Collecting and using the data can be harmful for user experience or even breach a company’s outbound/inbound traffic quota policy.
The Use Case
You wish to collect data from your end users. Your app has this amazing analytics feature. It periodically sends metrics you can later analyze and build your business upon. The more data you collect, the more power you have in your analysis.
This process, when scaled, can become problematic. You add more and more metrics and the messages get bigger and bigger. Eventually, the end user (or his IT team) notice the outbound bandwidth spikes. You get a ticket you need to solve –
please reduce the bandwidth signature of your app, or we cancel the deal.
It can also be problematic to your application. Your server gets a lot of messages in a very short time. If these messages are growing in size, you might have memory issues storing and handling all this data. This might lead to noticeable performance issues or even service availability issues.
The other side of the coin is when you want to send data to the end-user.
Imagine that you are tracking an application’s state in one machine (e.g. a multiplayer game or a document with multiple people editing several parts of it).
The system needs to sync between the states of all of the users in real time. Hence, your end users send real-time updates to the server. The server, in turn, pushes the data back to the other clients for update (via socket or other async communication protocol).
This way we have high speed updates (multiple clients sending periodical states). In addition, we add more features so the messages get bigger. Eventually, if the need for scale arises, you’ll see bandwidth spikes due to massive data collection.
Shall We Play a Game?
The gaming industry has already learned how to handle bandwidth issues. It’s not the bandwidth the usual web developer would think of. Let’s see why game builders use the flyweight design patterns.
Games use the GPU (Graphical Processing Unit or Graphics Card) heavily. A lot of data is being sent to be processed in the GPU.
The CPU is usually very fast while dealing with one problem (we can have multiple cores, but they are also limited to 2/4/8/16 etc.). The GPU, on the other hand, has a “secret power”. It’s “secret power” is parallelism.
When tasked with one task, it performs slower than the CPU. When dealing with thousands of computation tasks at the same time – it can finish tasks that the serial CPU would take much longer to accomplish.
In any event, the problem lies in getting data into the processing unit. The bigger the data, the bigger our problem.
While the processing unit is waiting for the data to come in, it sits idle. Even worse – it can stop in the middle of a computation, while part of the data it needs to complete its tasks is stuck in the heavy traffic.
The Flyweight Design Pattern
So… we have lots of data that’s passing into our clients/server/CPU/GPU. Some part of our system gets clogged and is our bottleneck. It’s bogged down under the data pressure.
What can we do about it?
The flyweight design pattern is a very simple solution that helps you make your data thinner.
In essence – your data has static meta data in it. For instance, in an analytics type of data, you can have the browser, OS, user agent etc. This kind of data repeats itself over a lot of messages.
The flyweight pattern suggests that you extract the static data from every instance. Then, send the “thin” data with only one instance of the static data. The receiver should have some mechanism to know how to piece the data back together.
Sounds abstract? Let’s look at an example.
The use cases mentioned above are a bit complex. In order to show how the pattern is used, I’ve created a simple example.
Code Snippet 1: Simple code that emulates a server. The server exposes two end points (
getFlyweightData) that returns a data structure with
nResponses of entities.
In code snippet 1, the server sends the data to the client in two ways.
The first and naive way is by using the
User class (line 28). In its constructor, we add the
UserMetaData directly on the user (line 34).
Every time we send the data to the client (or to another service) – the recipient gets an array of tools for each element in the data array.
The other end point uses the
FlyWeightUser class (line 38). This class saves only the user type(line 44).
Another part of the pattern is to also send the static meta data itself (also called
intrinsic state by the Gang of Four).
getData end point (line 59) to the
getFlyWieghtData end point (line 63) we can see a difference in the data structure.
getData entry point just sends the array of entities. The
getFlyWeightData entry point sends an object containing the array of
FlyWeightUser instances. In addition, it sends the meta data dictionary (line 64) .
This way, we send the static (
intrinsic) meta data only once and not for every entity in our array.
The recipient of the data has all the clues it needs to match the type with the meta data.
While this example is very simple, it still shows the benefit.
Let’s run a simple client:
Code Snippet 2: A client that uses the two endpoints.
The client in code snippet 2 is very simple as well. It has 2 buttons – one fetches the data using the
getData endpoint and the other using the
getFlyWeightData end point.
The results are shown in Figure 2. The
getFlyWeightData message size is around 14% smaller.
Put this at scale – your analytics messages sent every 2 seconds or your game engine sends an update every frame – and you get a huge reduction in traffic.
In our case, lets look at a user that visits your app for an hour. 3600 seconds / 2 messages/second = 1800 messages every hour. This sums up to 96.3KB * 1800. With the Fly Weight algorithm, we saved 25MB.
With real life data the reduction can be even more significant.
Another point to think about is users with slower internet connection – like G3 or slower.
You can actually test it on your own using chrome dev tools throttling. See Figure 3, in which the time it takes a 3G connection to fetch the data is shown.
The Flyweight design pattern is heavily used in games. It can help save some memory (we have less duplicates in our precious memory). But that’s not its primary use case, since most of our duplicates will just be references to an address in memory.
Its more critical use is saving bandwidth. In gaming – mostly to the processing units. In your application – the data sent back and forth between your services.
We described some use cases. It can help you reduce the load on your services. It can also help you improve user experience – especially for users with a slow internet connection.
It can also save you from an angry client complaining on your SDK breaching their outbound traffic policy (personal experience).
At WalkMe, we’ve used this pattern in various use cases. One of them was for our analytics feature. Our client-side events emitter sent huge amount of data to our events collector service.
Some of our clients monitored the traffic and saw a significant increase in outbound traffic since installing our SDK. Using the flyweight pattern, we managed to reduce the amount of outbound traffic for all of our customers.
In the example shown in this article, we’ve used the Flyweight pattern in order to save on web traffic bandwidth. Note that this example as well as the results are very just to show there is an effect.
The more complex your data and the slower your recipient’s connection – the more critical the difference.