return empty array if no candidates for home feed selection

This commit is contained in:
Hongbo Wu
2024-06-26 17:19:08 +08:00
parent 0276c4683b
commit 329dd13ed2
3 changed files with 66 additions and 23 deletions

View File

@ -258,12 +258,13 @@ const rankCandidates = async (
}
const redisKey = (userId: string) => `home:${userId}`
const emptyHomeKey = (key: string) => `${key}:empty`
export const getHomeSections = async (
userId: string,
limit = 100,
maxScore?: number
): Promise<Array<{ member: Section; score: number }>> => {
): Promise<Array<{ member: Section; score: number }> | null> => {
const redisClient = redisDataSource.redisClient
if (!redisClient) {
throw new Error('Redis client not available')
@ -285,6 +286,19 @@ export const getHomeSections = async (
limit
)
if (!results.length) {
logger.info('No sections found in redis')
// check if the feed is empty
const isEmpty = await redisClient.exists(emptyHomeKey(key))
if (isEmpty) {
logger.info('Empty feed')
return []
}
logger.info('Feed not found')
return null
}
const sections = []
for (let i = 0; i < results.length; i += 2) {
const member = JSON.parse(results[i]) as Section
@ -317,6 +331,14 @@ const appendSectionsToHome = async (
}
const key = redisKey(userId)
const emptyKey = emptyHomeKey(key)
if (!sections.length) {
logger.info('No available sections to add')
// set expiration to 1 hour
await redisClient.set(emptyKey, 'true', 'EX', 60 * 60)
return
}
// store candidates in redis sorted set
const pipeline = redisClient.pipeline()
@ -337,6 +359,8 @@ const appendSectionsToHome = async (
// add section to the sorted set
pipeline.zadd(key, ...scoreMembers)
pipeline.del(emptyKey)
// keep only the new sections and remove the oldest ones
pipeline.zremrangebyrank(key, 0, -(sections.length + 1))
@ -375,6 +399,10 @@ const mixHomeItems = (
items: Array<Candidate>,
batches: Array<Array<Candidate>>
) => {
if (batches.length === 0) {
return
}
const batchSize = Math.ceil(items.length / batches.length)
for (const item of items) {
@ -424,32 +452,36 @@ const mixHomeItems = (
>,
}
distributeItems(shortItems, batches.short)
distributeItems(longItems, batches.long)
batches.short.length && distributeItems(shortItems, batches.short)
batches.long.length && distributeItems(longItems, batches.long)
// convert batches to sections
const sections = []
const hiddenCandidates = rankedHomeItems.slice(50)
sections.push({
items: hiddenCandidates.map(candidateToItem),
layout: 'hidden',
})
hiddenCandidates.length &&
sections.push({
items: hiddenCandidates.map(candidateToItem),
layout: 'hidden',
})
sections.push({
items: batches.short.flat().map(candidateToItem),
layout: 'quick_links',
})
batches.short.length &&
sections.push({
items: batches.short.flat().map(candidateToItem),
layout: 'quick_links',
})
sections.push({
items: batches.long.flat().map(candidateToItem),
layout: 'top_picks',
})
batches.long.length &&
sections.push({
items: batches.long.flat().map(candidateToItem),
layout: 'top_picks',
})
sections.push({
items: justAddedCandidates.map(candidateToItem),
layout: 'just_added',
})
justAddedCandidates &&
sections.push({
items: justAddedCandidates.map(candidateToItem),
layout: 'just_added',
})
return sections
}
@ -495,7 +527,6 @@ export const updateHome = async (data: UpdateHomeJobData) => {
if (!justAddedCandidates.length && !candidates.length) {
logger.info('No candidates found')
return
}
// TODO: integrity check on candidates

View File

@ -41,7 +41,8 @@ export const homeResolver = authorized<
const sections = await getHomeSections(uid, limit, cursor)
log.info('Home sections fetched')
if (sections.length === 0) {
if (!sections) {
// home feed creation pending
const existingJob = await getJob(updateHomeJobId(uid))
if (existingJob) {
log.info('Update job job already enqueued')
@ -63,6 +64,17 @@ export const homeResolver = authorized<
}
}
if (sections.length === 0) {
// no available candidates
return {
edges: [],
pageInfo: {
hasPreviousPage: false,
hasNextPage: false,
},
}
}
const endCursor = sections[sections.length - 1].score.toString()
const edges = sections.map((section) => {
@ -127,7 +139,7 @@ export const hiddenHomeSectionResolver = authorized<
const sections = await getHomeSections(uid)
log.info('Home sections fetched')
if (sections.length === 0) {
if (!sections) {
return {
errorCodes: [HomeErrorCode.Pending],
}

View File

@ -79,4 +79,4 @@ class ScoreClientImpl implements ScoreClient {
}
}
export const scoreClient = new ScoreClientImpl()
export const scoreClient = new StubScoreClientImpl()