Android app cold-start sync takes 15–25s despite idle server — likely same root cause as the "extremely slow performance on Android" thread

Checklist
I have read intro post: https://community.passbolt.com/t/about-the-installation-issues-category/12
I have read the tutorials, help and searched for similar issues
I provide relevant information about my server (component names and versions, etc.)
I provide a copy of my logs and healthcheck
I describe the steps I have taken to trouble shoot the problem
I describe the steps on how to reproduce the issue

Self-hosted Passbolt CE 5.14.3-1-ce (Docker), MariaDB 11.8, behind Traefik v3.1 with HTTP/2 confirmed via ALPN. Client: Android app (fork of mobile-passbolt-android, master branch base), Xiaomi 14 Ultra, on WiFi throughout testing.

Symptom: Every cold start (app killed, then reopened) shows the loading bar for 15–25 seconds before the resource list is usable, even though nothing changed server-side. This matches what’s described in Extremely slow performance on Android almost exactly — staff there confirmed the app makes ~13 sequential API calls on sign-in, and users reported 9–25s delays depending on network.

What I ruled out with direct measurement, not guesswork:

Server load: idle (4 vCPU / 3.8GB RAM box, load average ~0.04, MariaDB Slow_queries = 0)

Client-side decryption: captured via adb logcat — full resource-list decrypt took 0.83 seconds, not the bottleneck

HTTP/1.1 fallback (root cause in a different thread): checked via openssl s_client -alpn — my server correctly negotiates h2

A reported /users.json slow-query pattern (dependent subquery on action_logs, GET /users.json is 100x slower) — reconstructed and timed the same query against my DB: 290ms. Doesn’t apply here (I only have 5 users, vs. 62 in that report)

What I found instead: each API call in the sync sequence pays a large, roughly fixed ~2.4–4 second cost regardless of response size. Confirmed by testing page size 3000 vs. 250: dropping to 250 (6 sequential pages instead of 1) made total resource-fetch time worse — 25.3s vs. 6.7s — because it’s mostly per-request overhead, not per-byte transfer time. This lines up with the architecture-level issue mentioned in the thread above (staff said broader performance work is on the roadmap “post-architecture migration,” ~80% complete at the time).

Fixes I’ve already tried on the client (all real, none solved the core issue):

Parallelized the ~8 independent network fetches in the full-data-refresh pipeline (previously fully sequential, upstream even had a // TODO start multiple async where possible)

Debounced redundant back-to-back full refreshes (15s window)

Raised the in-memory passphrase cache TTL from 5→30 minutes, so a full resync isn’t triggered as often

Stopped the session-key-cache upload (a write-behind step benefiting other devices, not this one) from blocking the “sync finished” signal

All of these reduce how often the slow path runs and shave one round trip off its tail, but none touch the actual multi-second-per-request cost, since that appears to be inherent to the current sequential-call architecture rather than anything fixable client-side.

Question for the team: is there an ETA or tracking issue for the architecture migration mentioned in the other thread? And is there anything server/proxy-side (Traefik config, PHP-FPM pool sizing) worth checking that wouldn’t show up as slow from curl but would from a real mobile client making the full ~13-call sequence?

Might be duplicate of, same issue, slow on Android only.

https://community.passbolt.com/t/extremely-slow-performance-on-android/14067?u=razvanfarte

Definitely not taking an hour as reported here:

https://community.passbolt.com/t/android-app-extreme-slow-after-activating-meta-enryption/13094

To be fair, I believe it was around 8-10 seconds even before the metadata v5 migration. It’s just that upgrading the mobile app loading bar loading time didn’t improve even afterwards.

Hi @RazvanFarte,

thanks for the detailed report. You touched quite a few separate topics, so I will go through them one by one. One thing up front: the “Extremely slow performance on Android” thread you refer to is an older post from before the current release, and a lot has changed since then.

Where we are today with sign-in

The “13 sequential calls” number is from an older version. In the current app (3.2.0) only five calls stand between tapping “sign in” and seeing the home screen: fetching the server PGP key and the RSA key (these two run in parallel, and they are fired early, while you are still on the passphrase screen, so they are usually done before you finish typing or unlock with biometrics), the login itself, feature flags and RBAC rules. Everything else was moved out of the way. Metadata type and key settings are now fetched inside the background refresh. Password policies, password expiry settings and user profile are fetched lazily, only when a screen actually needs them.

Where we are today with the data refresh

3.2.0 was, for the most part, a performance release. The main things that landed:

  • items on a page are decrypted and validated in parallel, and this now applies to every entity type, not only resources
  • resources, folders, users and groups are upserted and only rows that disappeared server-side are deleted, instead of wiping and re-inserting everything
  • resource types are skipped entirely when nothing changed
  • all database writes for a page happen in one transaction
  • the extra toString re-serialization of v4 resources for schema validation is gone
  • page size is now picked automatically based on the device tier, with the expert setting as an override
  • a deterministic progress bar, so you can see where the refresh is

and a number of smaller things. We have internal design documents for both the sign-in work and the refresh work, if you want to dig in let me know.

Also please find the full changelog for 3.2.0 here - https://www.passbolt.com/changelog/android/passbolt-android-app-3-2-0.

The architecture migration

You asked about an ETA of MVI migration - it is fully done. All screens are now on the new architecture (MVI with Compose).

Your points / changes, one by one

Parallelizing the independent fetches. You are right that the TODO is still in our code. We agree it is a win, but a small one, because it only shortens the network part. The expensive part is what happens after the response arrives (decryption, validation, DB insert), and that part has to stay in a specific sequential order because of database constraints.

The 15 second debounce. I would be careful with this one. The extra refresh you are suppressing is mostly the one triggered after creating or editing a resource, and for those we already write the server response into the local database, so skipping the refresh is harmless. But delete and share do not do that yet, they rely on that automatic refresh to update the local data. With a debounce, delete a resource shortly after a refresh finishes and it will stay in your list until the next one. Replacing that post-action refresh with a direct local update is on our list, but note this does not change how long a single refresh takes, it only changes how often it runs.

Passphrase cache 5 to 30 minutes. This one does not do what you wrote it does. The passphrase cache only controls how long the passphrase stays in memory. When it expires the app simply asks for it again the next time it is needed. It never triggers a refresh, and re-entering the passphrase never triggers one either. So the change gained nothing for speed but keeps a secret in memory for a longer time. I would revert it.

Session key cache upload not blocking “sync finished”. You are right that it is the last step and the refresh waits for it. Two things that can change the full picture: 1) the upload is skipped when nothing changed locally 2) errors there do not fail the refresh. Making it fully asynchronous is possible, but only if we make sure a new refresh cannot start while the previous upload is still in progress. It is doable, it just needs care.

About the fixed cost per request

Here is what happens with each page that comes back from the server: it is parsed, each resource is decrypted (or the decryption is skipped if the modified date did not change), validated against the schema, and written into the encrypted local database. With each page there is additional network call, additional initialization cost, and after a page there is also a decryption library garbage collection call, which can be noticeable. That is why it is important to use as big page size as the device can handle and why we introduced the device class detection with predefined defaults with an option to bump that in settings manually.

That said, we do have a separate report open on GitHub (https://github.com/passbolt/mobile-passbolt-android/issues/35) where the encrypted preferences storage is recreated on every request and adds a noticeable delay per call on some devices. The pattern looks similar to what you measured, but we have not verified yet that it is the same thing. We are looking into it right now, and depending on what the measurements show it will land in 3.3.0 or 3.4.0. If the measured data in the issue is correct it can save time, but the decryption and validation still have to happen. If possible could you provide an app log from the device? Here are the instructions if needed https://www.passbolt.com/docs/hosting/maintenance/diagnostics/collect-info/#mobile-applications.

What a cold start looks like now

On the first start after sign-in you get the loading bar, there is no way around processing everything once. On every following start the list is shown immediately from the local database while the refresh runs in the background, and the app is usable in read-only mode during that time. Edits wait for the refresh to finish. In 3.2.0 we unfortunately introduced a bug where opening resource details also waits for the refresh with showing the secret section, which in practice blocks viewing a secret until the sync is done. That is fixed in 3.3.0 (MOB-4888), and we start regression tests and the release process next week. After the fix you can browse and read your secrets while the refresh is running, only editing still waits.

Server side

Nothing in Traefik or PHP-FPM will change this. The time is spent on the device, which matches your own measurements.

Summary

If you would like to investigate more with us, could you please:

  • confirm which version your fork is based on? Is it current 3.2.0 release?
  • share an app log from the device (instructions linked above)
  • consider reverting the passphrase cache change and the debounce, for the reasons above

We will:

  • verify whether the encrypted preferences overhead from the GitHub issue explains the per-request gaps you and the GitHub reporter see
  • ship the resource details fix (MOB-4888) in 3.3.0 so secrets can be viewed during refresh
  • in future versions: replace the automatic refresh after create, edit, delete and share with direct local updates

hi @RazvanFarte,

https://github.com/passbolt/mobile-passbolt-android/issues/35 shipped in 3.3.0-57 (Released on Sep 22, 2026) under MOB-5105: (commit https://github.com/passbolt/mobile-passbolt-android/commit/636b91604c16ac55224a0475098743983543ddfe). it turns out we could save even 20% of time (on average) full refresh. This also impacts:

  • sign in (25-50%)
  • create / edit resource (25-50%)
  • autofill fill (25-45%)
  • reveal or copy password (10-25%)
  • open resource details
  • search
  • open any screen

We also shipped MOB-4888 (commit https://github.com/passbolt/mobile-passbolt-android/commit/20332871cbba1c34d5ed23933d2bb1ea28d04c01) that allows users to browsing cached resource details.

Thanks for the feedback, if you’re still having issues please take a look at @mmichalek comment and let us know how 3.3.0 works for you.