tldraw / tldraw-yjs-example

An example of using tldraw together with yjs.
141 stars 43 forks source link

perf: extremely high network usage (Yjs updates are redundant) #11

Open WofWca opened 7 months ago

WofWca commented 7 months ago

Hi! Great work on making the lib CRDT-compatible.

The problem

So, when I draw a line with the "D" tool, just scribbling around without stopping the mouse or releasing left mouse button, bandwidth usage goes up to about 1 Mbit / s. That is one megabyte every 8 seconds, which is comparable to streaming video, whereas I'm just drawing a line. Based on my testing, it appears that the number can vary depending on the device's performance (the better the performance, the higher the bandwidth).

How do I know that it's not supposed to be like this?

Well, if you go to the "network" tab in dev tools, select the WebSocket connection and filter with "ke", you can see that we send an update every few milliseconds with a constantly growing size (it then resets at about 10kB, because I believe the line gets split). If you look inside each update and compare them, you can see that they're nearly identical bit-wise.

image

Comparably, after 30 seconds of continuously drawing a line, the resulting document is just 30 kB, which is about 100 times smaller than the amount of data I upload while drawing it.

How I discovered it

I was working on porting this example to webxdc, using the webxdc-yjs-provider library. It records all the document updates for a couple of seconds, then Y.mergeUpdates() them and sends to other peers. See https://codeberg.org/WofWca/webxdc-yjs-provider/issues/7 For that use case such high network usage is not acceptable, because all the messages are non-ephemeral, they're stored on the email server and are handled each time a webxdc app gets opened. Megabytes of data for a small drawing is not good.

Potentially useful clues

This to me looks extremely like what would happen if we were to continuously delete and add the line being drawn instead of updating the existing one.

I have tried to Y.mergeUpdates() several of such updates together, but doesn't reduce the size of collected updates almost at all (it's around 1%). However, if we send the data to other peers using diff like described here with a frequency of ~1 second, instead of sending each update immediately, such as the y-websocket library does, the updates are much smaller.

Also I noticed that simply moving such a line also creates pretty big updates.

WofWca commented 7 months ago

Hey, good news. There is a fork of this example app adapted to Loro CRDT, and recently they apparently made performance improvements to it:

Maybe we could adapt them.

steveruizok commented 5 months ago

Oh, that's great! I'll check the example to see if they've come up with a better solution.

Our updates are very far from optimized, though luckily most people don't draw for eight seconds at a time. Still, there's a lot of room for improvement.

...and arguably yjs/CRDTs are not the right multiplayer solution for tldraw, given the contextual information that needs to be sent across. Now that the tldraw dot com is source available, you can check out our own sync solution which is much closer to our sockets example. https://github.com/tldraw/tldraw-sockets-example