Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

New option to optimize yjs clientID #2125

Open
4 tasks
dfahlander opened this issue Feb 6, 2025 · 0 comments
Open
4 tasks

New option to optimize yjs clientID #2125

dfahlander opened this issue Feb 6, 2025 · 0 comments

Comments

@dfahlander
Copy link
Collaborator

dfahlander commented Feb 6, 2025

Yjs documents will keep the history of all involved clientIDs over time. clientID are generated randomly in the Y.Doc constructor so every time a document is opened, a new clientID will be used that is unique to that editing session.

@dmonad recommends against altering the default behavior unless you would be 100% certain that no two clients can produce conflicting operations using the same clientID (see this discussion)

According to @domad, the existence of all historical clientIDs is not such a big concern if using the V2 versions of the update functions in yjs as it would not take very much space anyway. However, I could assume that it would depend on how the documents are being used over time and could possibly be of some concern for documents over time if they are frequently edited with new clientIDs.

This is hypotetic proposal to achieve a waterproof way to keep each persisted client to use a single clientID most of the times and spare numerous of historical clientIds, with indexedDB persistance and navigator.locks.

Assumptions:

  • Dexie's yjs-integration creates Y.Doc instances for the user using the property getter for declared Y.Doc properties and keeps it in a global cache so that there is never two doc instances in the same browsing context representing the same document. This getter would also be the one setting clientID to a persisted clientID.

Implementation:

  1. Store a collection of clientID in dexie (initially empty)
  2. In the DB open procedure of dexie (can be extended using db.on('ready'), it opens the table containing local clientIds.
  3. For each clientId in this table, it uses navigator.locks to try to lock a unique string with a prefix and with the clientId in it. If the lock is aquired, the clientID can be kept for all created Y.Doc instances in this browsing context and the lock will be kept in a locked state forever. It will automatically be released by the system when the browsing context exists or crashes. If the lock could not be aquired, it continues trying to lock the next entry until all persisted clientIDs have been tried.
  4. If no clientIDs could be aquired, or if the list of clientIDs was empty (as it would be first time), a new clientID is persisted along with an indexed timestamp for ordering (so that next browsing context will always try with the last used clientID first).

Outstanding questions:

  • Iframed applications: Would an iframe on the same origin as its parent be considered another browsing context and thus not able to aquire the same lock as its parent. If not, there might be a problem since the iframe will have its own javascript realm.
  • Multiple package instances: If the package holding the locked ID globally does it on a package-local global variable, there would be an issue. A workaround for this is storing a reference on globalThis, such as globalThis[Symbol.for("something")] = objectContaingAquireClientId. Dexie already use this way of ensuring that multiple instances of "dexie" library cannot be imported in the same javascript realm.
  • Are navigator.locks waterproof and bug free in all implementations? Does it release automatically on app crashes and page reloads?

Things to verify:

  • Verify that the lock is released immediately when reloading a page so that the same page can re-aquire the same lock on a reload.
  • Verify that there is no bugs of navigator.locks related to bfcache or visibilitystate on desktop or mobile
  • Verify that the scenarios works in Electron, capacitor etc and that an app restart will reaquire the same clientID
  • Verify whether iframe windows acts as their own browsing context when aquiring a lock with navigator.locks. Both when the iframe is on same origin as its parent and when on a different origin.

Notes:

Holding a lock will prohibit bfcache the same way as BroadcastChannels and IndexedDB connections can prohibit it. There would be totally unsafe to work around that in this case (like we do with IndexedDB connections in dexie by listening to pagehide and pageshow event) because a resurrected tab would have to aquire the lock again and may not get the same clientID while it would still have instances of Y.Doc with the aquired clientID in them.

So the invalidation of bfcache would be nescessary here. The bfcache feature is only an optimization that makes browser back and forward faster. Apps that use Y.js and Dexie Cloud might not gain so much from bfcache anyway assuming they are typically single page applications.

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

No branches or pull requests

1 participant