-
Notifications
You must be signed in to change notification settings - Fork 891
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
FR: Firestore offline persistence in web workers #983
Comments
Any news on this guys? The lack of offline persistence in service workers & web workers is a bit of a killer for PWA. |
Same here |
Thanks for the feedback. Could folks impacted by this tell us a little bit about their use case / architecture (what kind of worker they're trying to use Firestore from and for what purpose exactly)? |
@mikelehen thanks for responding. In my case, the data I pull from Firestore sometimes needs quite a lot of processing before I present it to users. As a result, I do most of my Firestore interaction inside a Web Worker instead of on the main thread in order to reduce UI jank. I could move this work to the Service Worker instead but I assume that this Firestore SDK limitation applies to Service Workers too since they also do not support the localStorage API. Unfortunately, this approach means that I lose offline persistence in my app, even though workers support IndexedDB. As the Service Worker API in particular becomes more popular I imagine that this will become a problem for more people. |
Thanks for your use case. One option would of course be to pull the data from Firestore on the main event loop and then just do the processing on the webworker, but if it's a lot of data, perhaps there are performance wins for moving the Firestore interaction off the main event loop as well. So it's a reasonable request. Service workers are a bit more complicated. They don't support XHR, which Firestore (and possibly other parts of Firebase) depends on. And service worker lifetime is generally short-lived. Our supposition so far is that it doesn't make that much sense to use Firestore from a service worker. But if folks have use cases, please do share. |
Thanks. I did consider this, but it wasn't practical for a few reasons. For example:
|
Something I forgot to add previously: every now and then I see the main thread blocked for 100ms+ by Firestore XHR-related scripts shortly after cancelling snapshot listeners. (Note that this timing is taken on a powerful MacBook Pro and I'd expect the main thread to be blocked for much longer on mobile.) This is another reason I offload most of my Firestore interaction to a worker thread. |
@joshdifabio That sounds pretty strange. Offhand I can't think of anything CPU-intensive we do when unsubscribing (and XHRs themselves are async). How exactly are you measuring the main thread blocking? Thanks! |
@mikelehen interesting. I observed jank and then used the performance profiler in Chrome. I haven't looked at it in detail yet, I only really saw that Firestore XHR was at the top -- perhaps it was GC or something in my own code being triggered. I'll have another look soonish and get back to you. |
My use case is for offline updates to be saved to Firestore without having to open the relevant web site when online ie. like web push where only the browser is required to be open. I assume due to localstorage API unavailability, this will not be possible |
@dexster I don't think any kind of worker will be able to run when the relevant web site isn't open. For that you'd need a browser extension or something. |
I've done some more performance profiling over the past couple of weeks, and this is what I've observed regarding Firestore performance. Whenever I read data from Firestore, I can see about about 50-150ms spent in Firestore code when the XHR response comes back. Everything happening in that time period appears to be Firebase code. Firestore processes the response synchronously on the main thread, and obviously even the lower bound of this figure (50ms) is way beyond what can realistically be accommodated on the main thread without introducing UI jank. These times were measured on a MacBook Pro; the times are higher on my Pixel 2. I previously said that this work was happening whenever I cancelled Firestore snapshot listeners, but this was a mistake, it's when the data comes back. I see this all the time, it's not an edge case -- 50ms of work in the main thread is the lower bound when listening to a single record. Unless I'm reading the figures wrong in Chrome's profiler and including my own application code in the figures, which I don't think I am, I suspect that these figures will be easily reproducible on any web app which uses the Firestore JS SDK. |
@joshdifabio Thanks for the added details. I was able to reproduce ~50ms with a big document in Chrome on my Mac. The time was basically spent parsing the result from the backend. Note that I believe I made a recent size optimization (4983420) that will also improve performance a little (it removed some logging calls that should have an impact here). That should go out in our next release. All that said, 50ms for a large document doesn't seem unreasonable to me. At 60fps, you might skip a couple frames, but for a typical web app, this should be imperceivable. A live-action game might be a different story. In any case, if you're sensitive to these sorts of hiccups, moving the processing to a separate worker seems like a reasonable approach. Sorry that persistence doesn't work at the moment. We'll give this some thought in the future. |
@mikelehen |
@SebasG22 Oh, interesting... I guess you get that error because we are checking for existence of the
We could check directly for the We may be able to make our dependency on LocalStorage optional, in which case persistence might work to some degree in a web worker... though there may be rough edges. I'll try to take a look at this. |
@mikelehen , It will be really helpful if you can take at look at this. |
Hello All! Just wondering if there is any update on this @mikelehen ? Every other Firestore feature is totally fine in a worker except the persistence - really would like to avoid the cost on the main thread of serializing, copying, and sending snapshots into the worker for further processing. Our current architecture allows for our main thread to be entirely reactive to updates from off thread data changes. Would appreciate any further information on this. Cheers, |
@T-Knott-Mesh Sorry! I have not spent time on this. @thebrianchen mentioned he may be able to take a look in the coming weeks though. So stay tuned. |
No problem ! Thank you for the response :) we will create a work around for now but we are really looking forward to potential capabilities in a worker. Appreciate you all ! @mikelehen @thebrianchen |
I've been looking into enabling persistence for web workers by removing the LocalStorage requirement, and it could work with a few caveats. Firestore implements a leasing mechanism in enabling persistence to ensure that multiple clients do not concurrently write to the underlying IndexedDB that powers it. This leasing mechanism relies on LocalStorage, since it allows Firestore to reliably record lease information even when browser tabs are closing. IndexedDB on its own can't reliably write as a tab is closing. When a client enables persistence, it must first obtain the primary lease, which prevents other clients from becoming the primary. Then, when the client closes, it marks the lease as available in LocalStorage, which allows the next client to become the primary. Consequently, without LocalStorage, once a client holding the lease terminates, no other client can takeover the lease until it expires (after 5 seconds). This creates two limitations:
As a potential workaround, we're considering a
Please try the experimental version by using We are still evaluating if this functionality is useful and exactly how we expect folks to use it. So please provide us feedback based on whether it works for you and how you end up using it (in a web worker, shared worker, etc.) Thanks! SAMPLE APP that implements Firestore with persistence in web workers |
@thebrianchen let's goooooo !! Woo! This is great, already do tab management with Service Worker and notification stuff so building out additional handling is no prob. Will be testing this out tomorrow 🎉 |
Great news! Too bad Safari dropped support for shared workers 😕 Will try with normal workers anyway. |
Also shared workers don't seem to be implemented on Chrome for Android (36% of global users). |
@laurentpayot We understand that shared workers are of quite limited utility. They happen to work quite well with these changes though, so we've called that out. Anyone writing for the general web will have to avoid them and use one of the other options. |
Hey @thebrianchen, |
@thebrianchen great sample app. As you noticed in the comments of
I had the same issue in my app so to be crash-proof I ended up setting the dedicated worker persistence on or off based on tab opening detection via localStorage events. <html><script>
const TAB_DETECTION_TIMEOUT = 50
function isFirstTab() {
return new Promise((resolve) => {
// broadcast that a tab is opening (before any addEventListener if first tab)
localStorage.tabOpening = Date.now() // new value needed to trigger an event
window.addEventListener('storage', e => {
if (e.key == "tabOpening") localStorage.newTabDetected = Date.now()
if (e.key == "newTabDetected") resolve(false)
})
setTimeout(() => resolve(true), TAB_DETECTION_TIMEOUT)
})
}
(async () => {
alert(`Persistence ${await isFirstTab() ? "" : "NOT "}available`)
})()
</script></html> I have no idea what exactly the timeout duration should be, but simply long enough for the events to be triggered from a preexisting tab. What are your thoughts on this approach? |
@laurentpayot That sounds like a viable option for your use case! However, Firestore doesn't do this because (1) it would add 50ms to the initialization time and (2) we aren't sure what timeout duration would be reliable given the potential throttling of background tabs. In your case though, you could continue implementing this in your main application in combination with the current logic in the sample app. The |
Great idea, I will let you know how it works! |
@thebrianchen I ended-up using the following code where tab detection is used only if persistence is taken. Tested after a const TAB_DETECTION_TIMEOUT = 100
let isOtherTab = new Promise(resolve => {
localStorage.tabPing = Date.now()
window.addEventListener('storage', e => {
if (e.key == "tabPing") localStorage.tabPong = Date.now()
if (e.key == "tabPong") resolve(true)
})
setTimeout(() => resolve(false), TAB_DETECTION_TIMEOUT)
})
// tab detection too in case of previous crash with unreleased persistence
let usingPersistence = !(!!localStorage.persistenceTaken && await isOtherTab)
if (usingPersistence) {
localStorage.persistenceTaken = "1"
dedicatedWorker.postMessage('enablePersistence')
// releasing persistence when closing tab
window.addEventListener("beforeunload", () => localStorage.persistenceTaken = "")
} |
@thebrianchen sorry to bother you again but it looks like the firestore experimental build is not compatible with firebase 7.5.1 and higher (see #2420). Would you mind updating the build again? |
@laurentpayot Updated. If you don't want to scroll up here is the link for 1.8.1. |
Thanks @thebrianchen! It did fix #2420 👍 |
We're very interested in this for use in our Chrome Extension as extensions are moving away from a persistent background page model to service workers. The constraints outlined in #983 (comment) are completely acceptable for this use case. There should only ever be one active service worker from the extension's origin so conflicts shouldn't happen. As an aside, have you considered using web locks (https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API) as an option where thy are available? It seems like it might solve this issue on Chrome. |
@scottfr That's exciting news! In your extension, does passing in Thanks for bringing up web locks -- we will definitely consider adding support once we determine the viability of |
Hi everyone. +1 interested in this feature.
In our case, we have an application that uses only Firestore and Auth, just to read and write user data, nothing fancy.
I got those numbers running synthetic tests with Lighthouse server for the branch I'm currently working on. Is there a way to ship the |
I will look into adding this as an experimental feature in the coming weeks. Thanks for sharing the feedback! |
+1 for this as an experimental feature of the official Firestore releases. It's getting harder and harder to make custom builds work along with always changing Firebase products (modules location, types location, testing, etc.) |
Case for this: Worker Driven Design ~ 3:41 ~ Appreciate all the work Firebase is doing and I - as a development platform author of Runtime.dev am making it a first class citizen - Web worker support is paramount to data driven apps :) Cheers, |
So far the |
Hey everyone! This experimental feature has been out for a few months now, and we're hoping to collect some usage feedback to see how this feature has been working for you to see if/how we should support this in the long term. In particular, how are you using |
@thebrianchen It's been working great so far. I use dedicated workers, with this code to detect multi tab usage and disable persistence for new tabs. |
@thebrianchen thank you for the feature. FYI I am using |
Would it not be better and fairer to offer persistence for any web worker on Chrome for Android, independently of whether firebase is in use or not? My use case is recurring PWA notifications, as detailed in this open issue with the Google Chrome for Android PWA team. The lack of web worker persistence on Chrome for Android is currently the only thing standing in the way of an implementation. It really looks like corporate Google engineers of different Google divisions are waging similar battles against a common problem. Moreover, according to the spec, I would be inclined to say: we are dealing here with a Chrome for Android bug. The reason being: A web worker with outstanding timers, database transactions, or network connections should be treated as a |
@marianopaulin02 does this page help? https://stackoverflow.com/questions/63261382/writing-data-in-service-worker-to-cloud-firestore |
Closing this issue as the feature has been launched a while ago. Feel free to open a new issue if there are any other questions or issues! |
Are there any plans to make offline persistence available in web workers? Inspired by @davideast 's article I dropped 95% of my Firebase bundle size using this one weird trick to use firebase in a worker, I've noticed offline persistence isn't available in that context. Browsing the source, I suspect it's the reliance on (and absence in workers of) localStorage that's the issue - so I was wondering if there's any alternative to using synchronous storage in this case? Thanks.
The text was updated successfully, but these errors were encountered: