#Question about read-model pattern vs canonical collections (perf issue)

2 messages · Page 1 of 1 (latest)

celest wind
#

Hey, I’m building an app with user-defined saved views over a fairly normalized local data model, and I ran into some performance issues with TanStack DB

Canonical model (simplified)

  • threads
  • thread_members
  • people
  • account_people
  • person_labels
  • labels

Initial approach (fully canonical)

  • Fetch filtered + paginated thread IDs from backend
  • Store only ordered IDs + cursor locally
  • Rebuild visible rows using live queries over canonical collections
  • Apply saved-view filters across multiple related collections (incl. nested subqueries)

Architecturally this felt “correct”, but in practice it became very laggy once the dataset reached low-thousands of rows.

The issue wasn’t a single query, but the combination of:

  • pagination
  • user-defined filters
  • multiple collection layers
  • nested graph reconstruction from normalized entities

Workaround I implemented:

  • Backend returns fully hydrated rows for a saved-view page
  • Store them in dedicated collections acting as a read model
  • Store query metadata separately:
    • ordered row_keys
    • cursor
    • counts
    • UI renders directly from these hydrated collections

This removes the need to rebuild the graph on the fly and gives much better first-render performance

Tradeoff

I now maintain a parallel read model
Canonical optimistic mutations must also patch the hydrated collections
Writes now update both:

  • source-of-truth collections
  • read-model collections

Questions

Is this "read-model collection” pattern acceptable in TanStack DB?
Or is the intended approach to keep everything canonical and optimize the live-query path?
More broadly: are materialized / read-only collections for large inbox/search views considered a normal pattern, or an anti-pattern?

Would love guidance on how this is supposed to be handled at scale.

polar hedge
#

Read models like this are very normal in databases so seems fine