#Techniques for reducing request waterfalls for dependent queries in loaders

27 messages · Page 1 of 1 (latest)

frozen plover
#

I'm using TanStack Router, Query, and Start (though I think Start is irrelevant for this Q)

I'm wondering about loaders that are run for client-side transitions that would be run more effectively on the server-side. Consider the following:

// Some route's loader
async function loader(ctx) {
  const { queryClient } = ctx.context;
  const post = await queryClient.ensureQueryData(postQueryOptions(ctx.params.postId));
  // Note this is much slower.
  const authorStats = await queryClient.ensureQueryData(authorStatsOptions(post.authorId));
  // .. maybe other dependent queries
}

I can configure this quite cleanly to run in SSR (server-side rendering) on the initial page load via this pattern in the docs:

1. client --> server
2. server runs all queries
3. client is hydrated en bulk

but if I'm navigating to this page via CSR (client-side rendering) this loader will run on the client, which adds extra latency in between dependent queries:

1. client-side nav kicks off the loader
2. client calls server for first query
3. client gets response, then continues loader, starting second query
4. client gets response, potentially kicking off other query

I recognize that both cases involve dependent queries (eg. query 2 can't run until we finish query 1), but I was wondering if there was a solution here for the added server-roundtrip latency between the queries in the CSR case? In some scenarios this may be the dominant latency driver.

#

Intuitively, what would be neat is if I could say "even on a client-side navigation, this loader should always run on the server", with the same special hydration/streaming hookups under the hood as the SSR-query-integration, so if I do things like ensureQueryData on the server and have it pipe through to the client automagically.

One potential complication here is that the client may have queryClient state that would've instantly resolved on the client but has to be recalculated from scartch in SSR. I'm sure I'm missing some more facets, which is why I wanted to pick the brains of the smarties here and see 😀

hollow fulcrum
#

put all this in a server function to let it run on the server always

frozen plover
#

Hi @hollow fulcrum , thanks for the quick reply 😀

This came to mind, but the issue then is that now I'm returning a large monolithic response, vs having small modular queries which can be reused/deduped.

For instance say I'm fetching many posts and many authors for those posts, ideally I can just rely on queryClient to dedupe the authorStats queries. If I do one monolithic response then I need to consider this and dedupe manually case-by-case, vs some way with queryClient would be super ideal.

little arch
#

Does TanStack DB have a place here?

frozen plover
#

I can imagine a middleground where I return a dehydrated queryClient state and hydrate it client-side in my loader, but that's what led to me wondering if there's a pattern here that can be generalized and exposed in a cleaner way, since dependent queries feels like a common usecase! 😁

little arch
#

Anywhere there is duplicated data between say, REST requests, normalization can play a big role

frozen plover
#

Admittedly in my list of the TanStack Infinity Guantlet™ that I'm utilizing, I haven't touched DB yet. I can investigate to see if there's a cleaner way through there. Will report back if I find anything 🫡

frozen plover
#

Quick loopback here since I promised an update, not expecting a response from the team 😁

Poked around with TanStack DB. I was candidly surprised a bit that some of the flows I'd consider common not being as accessible. For instance, the core queryFn having to return the full state of the collection was really confusing, and having to immediately jump into the advanced flows which are deemed "an escape hatch" makes me wonder if I'm using the wrong tool. It seems these "escape hatches" are what I'd need for nearly all my collections based on how my app works; I expect this to only improve as the library trends towards a stable 1.0, but wanted to mention nonetheless.

More on-topic, I can see how TanstackDB helps to denorm things, but for my top-level-Q we ultimately still have the fundamental tension between

  1. client wants modular queries to reuse in different spots
  2. server wants dependent queries to be run in a single method call to avoid roundtrips

As I mentioned in my OP, this feels great during SSR but for proceeding CSRs it doesn't. I still think a simple option to force-ssr loaders with built-inhydration may be a subtly low-hanging-fruit solution here.

I appreciate the brains batting the idea back and forth with me though. Thanks a lot 😁 🫡

hollow fulcrum
#

cc @umbral rune maybe you can help out here?

umbral rune
# frozen plover Quick loopback here since I promised an update, not expecting a response from th...

advanced flows

You're referring to direct writes? The docs page? Refetching the whole state of the collection is how Query works by default so we're just copying that. That's simple and performant. It's of course normal for many to do advanced things which is why the direct write apis are there — we could tone down the "advanced" and "escape hatch" language if people think these are more normal than advanced APIs. But they are inherently much more error prone than just refetching so my general feel is people should just refetch until they obviously can't. Databases can do an enormous number of reads so most apps are fine until they hit pretty serious scale. And refetching Just Works™ w/o any work which is great early on (granted, you might already at scale — but this is the argument for how the docs are written).

What are some dependent queries your app has? How big of issues is roundtrips?

frozen plover
#

direct writes/the docs pages
Yup! I can give a simple example, I was looking at using TanStack DB for a "Posts feed" on my homepage. For this I'm currently using a "hot posts" infinite query.

My first intuition was to use a "posts"-typed collection and then have a query on top of that which sorts by Hot, but then I realized I can't exactly have all posts loaded to the client, that'd be a lot of posts 😅

I can amend this by doing a "hot posts"-typed collection, which is populated by the first page of hot posts specifically, but now

  1. I have un-denormalized my data, slightly defeating the purpose
  2. I have to manually handle the "infinite" part of the query (DB supports pagination on its tables, but the underlying query collection logic doesn't support fetching via pagination AFAICT

The best solution by far seems to be hand-rigging up useInfiniteQuery with manual writes to the DB/collection, which isn't terrible, but again it feels like I'm fighting the tools a bit for something which could otherwise be smooth. Esp given the tool emphasizes differential data flow, it feels like this type of dynamic should be more first-class rather than in the advanced/escape hatch areas.

Perhaps I'm completely missing the mental model for how this should work or how to use it; happy to be schooled here if so 😁 🧠

#

Ignoring that, I could see DB helping me with the dependent queries like this:

  1. I have my collection of posts, which materialize a view of authors, which are deduped
  2. those deduped authors' data can now be fetched en bulk

Btw again this is a scenario which likely leads to me reaching for the direct-write API, since otherwise I'm having a queryFn which needs a closure/context from the other DB's state, and refetches all authors each time rather than only new ones as-needed

umbral rune
#

it does depend though how many posts & authors you have

#

it might be reasonable to just load all of them

#

and then normalized is easy

#

will make it so you can lazy load authors

frozen plover
umbral rune
#

as you're an obvious infinite query scenario

#

so infinite feed for the main posts & partitioned for all the other related data to keep things tight and normalized sounds pretty good

frozen plover
#

Thankful I had the foresight to say:

I expect this to only improve as the library trends towards a stable 1.0
I figured the TanStack gigabrains would already have plenty cooking to address my usecases, glad to see the specific issues I can track 😁

Appreciate the engagement on my feedback, I'll def keep tracking these issues. And excited more generally for TanStack DB to continue maturing 🫡

umbral rune
#

cc @toxic trench who's the one actually working on these 😆

hollow fulcrum
#

@buoyant coyote seems not related to the original question?

#

please create new questions