Traffic reduction and tuned caching strats (#2215)

* Update the feed to only check latest on focus after 30s, but to do a full reset on focus after 1 hour to avoid very stale data

* Remove the isFeedPublic query

* Fix: avoid double next-page fetches

* Reduce some poll intervals to reduce server load

* Guard against double-fires of fetchNextPage

* Reduce polling on blurred screens
This commit is contained in:
Paul Frazee 2023-12-15 15:49:07 -08:00 committed by GitHub
parent dd074371cf
commit 2a712630b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 151 deletions

View file

@ -161,51 +161,6 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) {
})
}
export const isFeedPublicQueryKey = ({uri}: {uri: string}) => [
'isFeedPublic',
uri,
]
export function useIsFeedPublicQuery({uri}: {uri: string}) {
return useQuery({
queryKey: isFeedPublicQueryKey({uri}),
queryFn: async ({queryKey}) => {
const [, uri] = queryKey
try {
const res = await getAgent().app.bsky.feed.getFeed({
feed: uri,
limit: 1,
})
return {
isPublic: Boolean(res.data.feed),
error: undefined,
}
} catch (e: any) {
/**
* This should be an `XRPCError`, but I can't safely import from
* `@atproto/xrpc` due to a depdency on node's `crypto` module.
*
* @see https://github.com/bluesky-social/atproto/blob/c17971a2d8e424cc7f10c071d97c07c08aa319cf/packages/xrpc/src/client.ts#L126
*/
if (e?.status === 401) {
return {
isPublic: false,
error: e,
}
}
/*
* Non-401 response means something else went wrong on the server
*/
return {
isPublic: true,
error: e,
}
}
},
})
}
export const useGetPopularFeedsQueryKey = ['getPopularFeeds']
export function useGetPopularFeedsQuery() {

View file

@ -16,7 +16,7 @@
* 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead.
*/
import {useEffect} from 'react'
import {useEffect, useRef} from 'react'
import {AppBskyFeedDefs} from '@atproto/api'
import {
useInfiniteQuery,
@ -49,6 +49,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
const threadMutes = useMutedThreads()
const unreads = useUnreadNotificationsApi()
const enabled = opts?.enabled !== false
const lastPageCountRef = useRef(0)
const query = useInfiniteQuery<
FeedPage,
@ -104,24 +105,26 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
useEffect(() => {
const {isFetching, hasNextPage, data} = query
let count = 0
let numEmpties = 0
for (const page of data?.pages || []) {
if (!page.items.length) {
numEmpties++
}
count += page.items.length
if (isFetching || !hasNextPage) {
return
}
// avoid double-fires of fetchNextPage()
if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
lastPageCountRef.current !== 0 &&
lastPageCountRef.current === data?.pages?.length
) {
return
}
// fetch next page if we haven't gotten a full page of content
let count = 0
for (const page of data?.pages || []) {
count += page.items.length
}
if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) {
query.fetchNextPage()
lastPageCountRef.current = data?.pages?.length || 0
}
}, [query])

View file

@ -1,4 +1,4 @@
import React, {useCallback, useEffect} from 'react'
import React, {useCallback, useEffect, useRef} from 'react'
import {
AppBskyFeedDefs,
AppBskyFeedPost,
@ -78,6 +78,7 @@ export interface FeedPageUnselected {
api: FeedAPI
cursor: string | undefined
feed: AppBskyFeedDefs.FeedViewPost[]
fetchedAt: number
}
export interface FeedPage {
@ -85,6 +86,7 @@ export interface FeedPage {
tuner: FeedTuner | NoopFeedTuner
cursor: string | undefined
slices: FeedPostSlice[]
fetchedAt: number
}
const PAGE_SIZE = 30
@ -98,11 +100,12 @@ export function usePostFeedQuery(
const feedTuners = useFeedTuners(feedDesc)
const moderationOpts = useModerationOpts()
const enabled = opts?.enabled !== false && Boolean(moderationOpts)
const lastRun = React.useRef<{
const lastRun = useRef<{
data: InfiniteData<FeedPageUnselected>
args: typeof selectArgs
result: InfiniteData<FeedPage>
} | null>(null)
const lastPageCountRef = useRef(0)
// Make sure this doesn't invalidate unless really needed.
const selectArgs = React.useMemo(
@ -152,6 +155,7 @@ export function usePostFeedQuery(
api,
cursor: res.cursor,
feed: res.feed,
fetchedAt: Date.now(),
}
},
initialPageParam: undefined,
@ -214,6 +218,7 @@ export function usePostFeedQuery(
api: page.api,
tuner,
cursor: page.cursor,
fetchedAt: page.fetchedAt,
slices: tuner
.tune(page.feed)
.map(slice => {
@ -279,26 +284,28 @@ export function usePostFeedQuery(
useEffect(() => {
const {isFetching, hasNextPage, data} = query
if (isFetching || !hasNextPage) {
return
}
// avoid double-fires of fetchNextPage()
if (
lastPageCountRef.current !== 0 &&
lastPageCountRef.current === data?.pages?.length
) {
return
}
// fetch next page if we haven't gotten a full page of content
let count = 0
let numEmpties = 0
for (const page of data?.pages || []) {
if (page.slices.length === 0) {
numEmpties++
}
for (const slice of page.slices) {
count += slice.items.length
}
}
if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
) {
if (count < PAGE_SIZE && (data?.pages.length || 0) < 6) {
query.fetchNextPage()
lastPageCountRef.current = data?.pages?.length || 0
}
}, [query])

View file

@ -35,9 +35,7 @@ export function useProfileQuery({did}: {did: string | undefined}) {
// if you remove it, the UI infinite-loops
// -prf
staleTime: isCurrentAccount ? STALE.SECONDS.THIRTY : STALE.MINUTES.FIVE,
refetchInterval: isCurrentAccount
? STALE.SECONDS.THIRTY
: STALE.MINUTES.FIVE,
refetchInterval: STALE.MINUTES.FIVE,
queryKey: RQKEY(did || ''),
queryFn: async () => {
const res = await getAgent().getProfile({actor: did || ''})