Explain cache miss #371

Closed
opened 2026-06-03 18:19:19 +00:00 by guettli · 1 comment
guettli commented 2026-06-03 18:19:19 +00:00 (Migrated from codeberg.org)

This issue says .... Cache miss.

https://codeberg.org/guettli/sharedinbox/issues/367

How can a cache miss happen?

Please explain that.

I thought the ui reads only from local DB.

This issue says .... Cache miss. https://codeberg.org/guettli/sharedinbox/issues/367 How can a cache miss happen? Please explain that. I thought the ui reads only from local DB.
guettlibot commented 2026-06-03 18:25:02 +00:00 (Migrated from codeberg.org)

Let me read the project files and explore the codebase to understand the cache architecture.Now let me explore the codebase to understand the cache mechanism and find relevant source files.Let me check a few key files directly before writing the plan.Now I have a clear picture. Here is the implementation plan / explanation to be posted as the issue comment:


Explanation: How a Cache Miss Can Happen

The "UI reads only from local DB" statement is mostly true, but not for email bodies.

There are two distinct data paths:

1. Email list / thread list — truly local DB only

observeThreads() returns a Drift reactive stream from the local Threads and Emails tables. No network call is ever made for list rendering. Pull-to-refresh enqueues a sync job; it does not block the UI on a network round-trip.

2. Email body (detail view) — local DB with network fallback

When you open an email, the UI calls getEmailBody(emailId) (email_repository_impl.dart:209). This method:

  1. Looks up the EmailBodies table in the local SQLite DB.
  2. If a row exists and cachedAt is within the last 7 days → returns from local DB (cache hit).
  3. Otherwise → cache miss → opens a live IMAP/JMAP connection and fetches the full message body from the server, then writes it to the local DB for next time.

A cache miss for an email body happens in three cases:

Case Condition
Never fetched No row in EmailBodies yet (e.g. new email, background sync didn't pre-fetch the body)
TTL expired cachedAt is older than 7 days (_bodyCacheTtl, email_repository_impl.dart:206)
Legacy row cachedAt is null (rows written before schema v9, treated as expired)

Where the confusion comes from

The email headers, thread metadata, flags, and mailbox counts all live purely in the local DB and are never fetched on demand. Only the full message body (text + HTML + attachments) has a TTL-based network fallback, because bodies are large and there is no guarantee the background sync pre-fetched them before the user opens the email.

No code change needed for this issue

This is a documentation/understanding issue. The behaviour is intentional: the UI is local-first for listing, but falls back to the network for bodies that haven't been cached yet or have expired. If the goal is to eliminate on-demand body fetches entirely, that would require ensuring the background sync always pre-fetches bodies — but that is a separate design decision to make explicitly.

Let me read the project files and explore the codebase to understand the cache architecture.Now let me explore the codebase to understand the cache mechanism and find relevant source files.Let me check a few key files directly before writing the plan.Now I have a clear picture. Here is the implementation plan / explanation to be posted as the issue comment: --- ## Explanation: How a Cache Miss Can Happen The "UI reads only from local DB" statement is **mostly true, but not for email bodies**. There are two distinct data paths: ### 1. Email list / thread list — truly local DB only `observeThreads()` returns a Drift reactive stream from the local `Threads` and `Emails` tables. No network call is ever made for list rendering. Pull-to-refresh enqueues a sync job; it does not block the UI on a network round-trip. ### 2. Email body (detail view) — local DB with network fallback When you open an email, the UI calls `getEmailBody(emailId)` (`email_repository_impl.dart:209`). This method: 1. Looks up the `EmailBodies` table in the local SQLite DB. 2. If a row exists **and** `cachedAt` is within the last 7 days → returns from local DB (cache hit). 3. Otherwise → **cache miss** → opens a live IMAP/JMAP connection and fetches the full message body from the server, then writes it to the local DB for next time. A cache miss for an email body happens in three cases: | Case | Condition | |---|---| | Never fetched | No row in `EmailBodies` yet (e.g. new email, background sync didn't pre-fetch the body) | | TTL expired | `cachedAt` is older than 7 days (`_bodyCacheTtl`, `email_repository_impl.dart:206`) | | Legacy row | `cachedAt` is `null` (rows written before schema v9, treated as expired) | ### Where the confusion comes from The **email headers, thread metadata, flags, and mailbox counts** all live purely in the local DB and are never fetched on demand. Only the **full message body** (text + HTML + attachments) has a TTL-based network fallback, because bodies are large and there is no guarantee the background sync pre-fetched them before the user opens the email. ### No code change needed for this issue This is a documentation/understanding issue. The behaviour is intentional: the UI is local-first for listing, but falls back to the network for bodies that haven't been cached yet or have expired. If the goal is to eliminate on-demand body fetches entirely, that would require ensuring the background sync always pre-fetches bodies — but that is a separate design decision to make explicitly.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: guettli/sharedinbox#371