Throttling Yjs updates with garbage collection

I implemented throttling when sending updates by merging all updates within a certain interval with Y.mergeUpdatesV2, but deletions aren’t garbage collected and this takes away some of the incentives of throttling.

Is it possible to perform garbage collection on only the merged updates to keep the final updates small?

That’s correct. When merging updates using Y.mergeUpdates([update1, update2, ..]) you don’t perform garbage collection. Yjs needs the complete document state to perform garbage collection. Hence, it is only performed when doing Y.applyUpdate.

You could apply the throttled document updates to a fresh Yjs document (based on the previous state) before sending them to the other peers.


// This is the live document
const ydoc = new Y.Doc()

// # Throttling starts here
// Listen to updates on ydoc
const updates = []
ydoc.on('update', update => updates.push(update))

// Create a clone of the previous ydoc that you will use to apply garbage-collection.
const ydocBeforeThrottlingStarts = new Y.Doc()
Y.applyUpdate(ydocBeforeThrottlingStarts, Y.encodeStateAsUpdate(ydoc))

// Perform some changes on ydoc that will be sent later..
ydoc.getMap().set('key', 'val')
...

// Throttling ends here. Now we want to send the generated updates (merged and gc'd) to the server

// Apply updates to the previous document state to perform garbage-collection.
// We send the generated update

ydocBeforeThrottlingStarts.once('update', update => {
  sendToOtherClients(update)
})

// Apply updates in a single transaction to ensure that they only generate a single update event
ydocBeforeThrottlingStarts.transact(() => {
  updates.forEach(update => {
    Y.applyUpdate(ydocBeforeThrottlingStarts, update)
  })
})

I’m not sure if this is worth it to clone the previous document state. But yeah, this is what it takes :wink: