Skip to content

Commit 8abb255

Browse files
authored
Merge pull request #54 from velog-io/feat/post-score-queue
redis queue setup for post score
2 parents f6f415f + 5220e78 commit 8abb255

File tree

11 files changed

+103
-41
lines changed

11 files changed

+103
-41
lines changed

apps/cron/src/common/plugins/global/cronPlugin.mts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { GenerateFeedJob } from '@jobs/GenerateFeedJob.mjs'
44
import { CalculatePostScoreJob } from '@jobs/CalculatePostScoreJob.mjs'
55
import { GenerateTrendingWritersJob } from '@jobs/GenerateTrendingWritersJob.mjs'
66
import { DeleteFeedJob } from '@jobs/DeleteFeedJob.mjs'
7-
import { FastifyPluginAsync } from 'fastify'
7+
import type { FastifyPluginAsync } from 'fastify'
88
import { container } from 'tsyringe'
99
import { ENV } from '@env'
10-
import { CheckSpamPostJob } from '@jobs/CheckSpamPostJob.mjs'
10+
import { CheckPostSpamJob } from '@jobs/CheckPostSpamJob.mjs'
1111
import { DeletePostReadJob } from '@jobs/DeletePostReadJob.mjs'
12+
import { ScorePostJob } from '@jobs/ScorePostJob.mjs'
1213

1314
const cronPlugin: FastifyPluginAsync = async (fastfiy) => {
1415
const calculatePostScoreJob = container.resolve(CalculatePostScoreJob)
@@ -18,8 +19,9 @@ const cronPlugin: FastifyPluginAsync = async (fastfiy) => {
1819
const statsDailyJob = container.resolve(StatsDaily)
1920
const statsWeeklyJob = container.resolve(StatsWeekly)
2021
const statsMonthlyJob = container.resolve(StatsMonthly)
21-
const checkSpamPostJob = container.resolve(CheckSpamPostJob)
22+
const checkPostSpamJob = container.resolve(CheckPostSpamJob)
2223
const deleteReadPostJob = container.resolve(DeletePostReadJob)
24+
const scorePostJob = container.resolve(ScorePostJob)
2325

2426
// 덜 실행하면서, 실행되는 순서로 정렬
2527
// crontime은 UTC 기준으로 작성되기 때문에 KST에서 9시간을 빼줘야함
@@ -51,10 +53,15 @@ const cronPlugin: FastifyPluginAsync = async (fastfiy) => {
5153
jobService: calculatePostScoreJob,
5254
param: 0.5,
5355
},
56+
{
57+
name: 'score post in every 1 minutes',
58+
cronTime: '*/1 * * * *', // every 1 minutes
59+
jobService: scorePostJob,
60+
},
5461
{
5562
name: 'check post spam in every 2 minutes',
5663
cronTime: '*/2 * * * *', // every 2 minutes
57-
jobService: checkSpamPostJob,
64+
jobService: checkPostSpamJob,
5865
},
5966
{
6067
name: 'delete post read in every 2 minutes',
@@ -151,8 +158,9 @@ type JobService =
151158
| StatsDaily
152159
| StatsWeekly
153160
| StatsMonthly
154-
| CheckSpamPostJob
161+
| CheckPostSpamJob
155162
| DeletePostReadJob
163+
| ScorePostJob
156164

157165
type BaseJobService = {
158166
name: string

apps/cron/src/jobs/CheckSpamPostJob.mts renamed to apps/cron/src/jobs/CheckPostSpamJob.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { injectable, singleton } from 'tsyringe'
22
import { Job, JobProgress } from './JobProgress.mjs'
33
import { PostService } from '@services/PostService/index.mjs'
4-
import { CheckPostSpamArgs, RedisService } from '@lib/redis/RedisService.mjs'
4+
import { type CheckPostSpamQueueData, RedisService } from '@lib/redis/RedisService.mjs'
55
import { DiscordService } from '@lib/discord/DiscordService.mjs'
66

77
@injectable()
88
@singleton()
9-
export class CheckSpamPostJob extends JobProgress implements Job {
9+
export class CheckPostSpamJob extends JobProgress implements Job {
1010
constructor(
1111
private readonly redis: RedisService,
1212
private readonly discord: DiscordService,
@@ -23,7 +23,7 @@ export class CheckSpamPostJob extends JobProgress implements Job {
2323
while (true) {
2424
const item = await this.redis.lindex(spamQueueName, 0)
2525
if (!item) break
26-
const data: CheckPostSpamArgs = JSON.parse(item)
26+
const data: CheckPostSpamQueueData = JSON.parse(item)
2727
try {
2828
await this.postService.checkSpam(data)
2929
} catch (error) {

apps/cron/src/jobs/ScorePostJob.mts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { injectable, singleton } from 'tsyringe'
2+
import { Job, JobProgress } from './JobProgress.mjs'
3+
import { RedisService } from '@lib/redis/RedisService.mjs'
4+
import { PostService } from '@services/PostService/index.mjs'
5+
import { ScorePostQueueData } from '@packages/database/velog-redis'
6+
import { DiscordService } from '@lib/discord/DiscordService.mjs'
7+
8+
@singleton()
9+
@injectable()
10+
export class ScorePostJob extends JobProgress implements Job {
11+
constructor(
12+
private readonly postService: PostService,
13+
private readonly redis: RedisService,
14+
private readonly discord: DiscordService,
15+
) {
16+
super()
17+
}
18+
public async runner(): Promise<void> {
19+
console.log('ScorePostJob start...')
20+
console.time('ScorePostJob')
21+
22+
const scorePostQueueName = this.redis.queueName.scorePost
23+
let handledQueueCount = 0
24+
25+
while (true) {
26+
const item = await this.redis.lindex(scorePostQueueName, 0)
27+
if (!item) break
28+
const data: ScorePostQueueData = JSON.parse(item)
29+
try {
30+
await this.postService.scoreCalculator(data.post_id)
31+
} catch (error) {
32+
console.log('ScorePostJob error', error)
33+
const message = { message: 'ScorePostJob error', payload: item, error: error }
34+
this.discord.sendMessage('error', JSON.stringify(message))
35+
} finally {
36+
await this.redis.lpop(scorePostQueueName)
37+
handledQueueCount++
38+
}
39+
}
40+
41+
console.log(`handled Queue count: ${handledQueueCount}`)
42+
console.timeEnd('ScorePostJob')
43+
}
44+
}

apps/cron/src/lib/redis/RedisService.mts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { injectable, singleton } from 'tsyringe'
22
import { ENV } from '@env'
33
import { RedisService as Redis } from '@packages/database/velog-redis'
4-
export type { ChangeEmailArgs } from '@packages/database/velog-redis'
5-
export type { CheckPostSpamArgs } from '@packages/database/velog-redis'
6-
export type { CreateFeedArgs } from '@packages/database/velog-redis'
4+
export type {
5+
ChangeEmailArgs,
6+
CheckPostSpamQueueData,
7+
CreateFeedQueueData,
8+
} from '@packages/database/velog-redis'
79

810
@injectable()
911
@singleton()

apps/cron/src/routes/posts/v1/index.mts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ const v1: FastifyPluginCallback = (fastify, opts, done) => {
1919
},
2020
)
2121

22+
// dev 환경에서만 사용 가능
2223
fastify.patch('/score', async (_, reply) => {
2324
const processedPostsCount = await postController.calculateRecentPostScore()
2425
reply.status(HttpStatus.OK).send({ processedPostsCount })
2526
})
2627

28+
// dev 환경에서만 사용 가능
2729
fastify.post('/test/spam-filter', async (_, reply) => {
2830
await postController.spamFilterTestRunner()
2931
reply.status(HttpStatus.OK).send(HttpStatusMessage.Ok)

apps/cron/src/services/FeedService/index.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface Service {
1212
@injectable()
1313
@singleton()
1414
export class FeedService implements Service {
15-
constructor(
15+
constructor(
1616
private readonly db: DbService,
1717
private readonly utils: UtilsService,
1818
private readonly followUserService: FollowUserService,

apps/cron/src/services/PostService/index.mts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { injectable, singleton } from 'tsyringe'
44
import geoip from 'geoip-country'
55
import { subMonths } from 'date-fns'
66
import { DiscordService } from '@lib/discord/DiscordService.mjs'
7-
import { CheckPostSpamArgs } from '@lib/redis/RedisService.mjs'
7+
import type { CheckPostSpamQueueData } from '@lib/redis/RedisService.mjs'
88

99
interface Service {
1010
findById(postId: string): Promise<Post | null>
1111
scoreCalculator(postId: string): Promise<void>
12-
checkSpam(args: CheckPostSpamArgs): Promise<void>
12+
checkSpam(data: CheckPostSpamQueueData): Promise<void>
1313
}
1414

1515
@singleton()
@@ -68,7 +68,7 @@ export class PostService implements Service {
6868
})
6969
}
7070

71-
public async checkSpam({ post_id, user_id, ip }: CheckPostSpamArgs): Promise<void> {
71+
public async checkSpam({ post_id, user_id, ip }: CheckPostSpamQueueData): Promise<void> {
7272
const post = await this.db.post.findUnique({
7373
where: {
7474
id: post_id,

apps/server/src/lib/redis/RedisService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { injectable, singleton } from 'tsyringe'
33
import { RedisService as Redis } from '@packages/database/velog-redis'
44
export type {
55
ChangeEmailArgs,
6-
CheckPostSpamArgs,
7-
CreateFeedArgs,
6+
CheckPostSpamQueueData,
7+
CreateFeedQueueData,
88
} from '@packages/database/velog-redis'
99

1010
@injectable()

apps/server/src/services/PostApiService/index.mts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ import { SeriesService } from '@services/SeriesService/index.mjs'
1717
import { SearchService } from '@services/SearchService/index.js'
1818
import { ExternalIntegrationService } from '@services/ExternalIntegrationService/index.js'
1919
import { PostService } from '@services/PostService/index.js'
20-
import { CreateFeedArgs, RedisService, CheckPostSpamArgs } from '@lib/redis/RedisService.js'
20+
import {
21+
RedisService,
22+
type CreateFeedQueueData,
23+
type CheckPostSpamQueueData,
24+
} from '@lib/redis/RedisService.js'
2125
import { GraphcdnService } from '@lib/graphcdn/GraphcdnService.js'
2226
import { ImageService } from '@services/ImageService/index.js'
2327
import { UserService } from '@services/UserService/index.js'
@@ -315,24 +319,24 @@ export class PostApiService implements Service {
315319
// create feed
316320
setTimeout(() => {
317321
if (!post) return
318-
const queueData: CreateFeedArgs = {
322+
const queueData: CreateFeedQueueData = {
319323
fk_following_id: signedUserId,
320324
fk_post_id: post.id,
321325
}
322-
this.redis.createFeedQueue(queueData)
326+
this.redis.addToCreateFeedQueue(queueData)
323327
}, 0)
324328

325329
// check spam
326330
setTimeout(() => {
327331
if (!post) return
328332
if (isSpam) return
329333
if (isTusted) return
330-
const queueData: CheckPostSpamArgs = {
334+
const queueData: CheckPostSpamQueueData = {
331335
post_id: post.id,
332336
user_id: signedUserId,
333337
ip,
334338
}
335-
this.redis.addToSpamCheckQueue(queueData)
339+
this.redis.addToCheckPostSpamQueue(queueData)
336340
}, 0)
337341
}
338342

apps/server/src/services/PostService/index.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -405,17 +405,7 @@ export class PostService implements Service {
405405
}
406406
}
407407
public async updatePostScore(postId: string) {
408-
try {
409-
await axios.patch(
410-
`${ENV.cronHost}/api/posts/v1/score/${postId}`,
411-
{},
412-
{
413-
headers: {
414-
'Cron-Api-Key': ENV.cronApiKey,
415-
},
416-
},
417-
)
418-
} catch (_) {}
408+
await this.redis.addToScorePostQueue({ post_id: postId })
419409
}
420410
public shortDescription(post: Post): string {
421411
if (post.short_description) return post.short_description

packages/database/src/velog-redis/index.mts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ interface Service {
44
connection(): Promise<void>
55
get generateKey(): GenerateRedisKey
66
get queueName(): Record<QueueName, string>
7-
createFeedQueue(data: CreateFeedArgs): Promise<number>
7+
addToCreateFeedQueue(data: CreateFeedQueueData): Promise<number>
8+
addToCheckPostSpamQueue(data: CheckPostSpamQueueData): Promise<number>
9+
addToScorePostQueue(data: ScorePostQueueData): Promise<number>
810
}
911

1012
type RedisOptions = {
@@ -53,17 +55,23 @@ export class RedisService extends Redis.default implements Service {
5355
return {
5456
createFeed: 'queue:feed',
5557
checkPostSpam: 'queue:checkPostSpam',
58+
scorePost: 'queue:scorePost',
5659
}
5760
}
5861

59-
public async createFeedQueue(data: CreateFeedArgs): Promise<number> {
62+
public addToCreateFeedQueue(data: CreateFeedQueueData) {
6063
const queueName = this.queueName.createFeed
61-
return await this.lpush(queueName, JSON.stringify(data))
64+
return this.lpush(queueName, JSON.stringify(data))
6265
}
6366

64-
public async addToSpamCheckQueue(data: CheckPostSpamArgs): Promise<number> {
67+
public addToCheckPostSpamQueue(data: CheckPostSpamQueueData): Promise<number> {
6568
const queueName = this.queueName.checkPostSpam
66-
return await this.lpush(queueName, JSON.stringify(data))
69+
return this.lpush(queueName, JSON.stringify(data))
70+
}
71+
72+
public addToScorePostQueue(data: ScorePostQueueData): Promise<number> {
73+
const queueName = this.queueName.scorePost
74+
return this.lpush(queueName, JSON.stringify(data))
6775
}
6876
}
6977

@@ -81,20 +89,24 @@ type GenerateRedisKey = {
8189
deployBook: (bookId: string) => string
8290
}
8391

84-
type QueueName = 'createFeed' | 'checkPostSpam'
92+
type QueueName = 'createFeed' | 'checkPostSpam' | 'scorePost'
8593

8694
export type ChangeEmailArgs = {
8795
email: string
8896
userId: string
8997
}
9098

91-
export type CreateFeedArgs = {
99+
export type CreateFeedQueueData = {
92100
fk_following_id: string
93101
fk_post_id: string
94102
}
95103

96-
export type CheckPostSpamArgs = {
104+
export type CheckPostSpamQueueData = {
97105
post_id: string
98106
user_id: string
99107
ip: string
100108
}
109+
110+
export type ScorePostQueueData = {
111+
post_id: string
112+
}

0 commit comments

Comments
 (0)