Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: AI 요약에 SQS 적용 #100

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
},
"dependencies": {
"@aws-sdk/client-lambda": "^3.606.0",
"@aws-sdk/client-sqs": "^3.624.0",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
"@nestjs/core": "^10.0.0",
Expand Down Expand Up @@ -67,6 +68,7 @@
"@sentry/webpack-plugin": "^2.20.1",
"@swc/cli": "^0.3.14",
"@swc/core": "^1.6.5",
"@types/aws-lambda": "^8.10.143",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/lodash": "^4.17.6",
Expand Down
903 changes: 872 additions & 31 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

36 changes: 35 additions & 1 deletion serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ provider:
runtime: nodejs20.x
region: ap-northeast-2
architecture: arm64
iamRoleStatements:
- Effect: 'Allow'
Action:
- 'sqs:ReceiveMessage'
- 'sqs:SendMessage'
- 'sqs:DeleteMessage'
Resource: '*'

custom:
dotenv:
Expand All @@ -32,11 +39,38 @@ functions:
origin: '*'
aiWorker:
handler: worker-dist/index.handler
timeout: 30
timeout: 600
description: AI Classification Async Worker
events:
- sqs:
arn:
Fn::GetAtt: [AIEventQueue, Arn]
maximumConcurrency: 5

package:
exclude:
- '**/*'
include:
- 'dist/**'
- 'worker-dist/**'

resources:
Resources:
AIEventQueue:
Type: 'AWS::SQS::Queue'
Properties:
QueueName: 'ai-event-queue.fifo'
FifoQueue: true
ContentBasedDeduplication: true
VisibilityTimeout: 600
RedrivePolicy:
deadLetterTargetArn:
Fn::GetAtt:
- 'AIEventDeadQueue'
- 'Arn'
maxReceiveCount: 1
AIEventDeadQueue:
Type: 'AWS::SQS::Queue'
Properties:
FifoQueue: true
QueueName: 'ai-event-dead-queue.fifo'
21 changes: 12 additions & 9 deletions src/ai_handler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { Handler } from 'aws-lambda';
import { SQSHandler } from 'aws-lambda';
import { DatabaseModule } from './infrastructure';
import { AiModule } from './infrastructure/ai/ai.module';
import { AiClassificationPayload } from './infrastructure/aws-lambda/type';
Expand All @@ -24,16 +24,19 @@ import { AiClassificationService } from './modules/ai-classification/ai-classifi
})
export class WorkerModule {}

export const handler: Handler = async (event: AiClassificationPayload) => {
export const handler: SQSHandler = async (event) => {
const app = await NestFactory.create(WorkerModule);
const aiClassificationService = app.get(AiClassificationService);

const result = await aiClassificationService.execute(event);
for (const record of event.Records) {
const body: AiClassificationPayload = JSON.parse(record.body);
const result = await aiClassificationService.execute(body);

// NOTE: cloud-watch 로그 확인용
console.log({
result: result.success ? 'success' : 'fail',
event: JSON.stringify(event, null, 2),
summarizeUrlContent: result,
});
// NOTE: cloud-watch 로그 확인용
console.log({
result: result.success ? 'success' : 'fail',
event: JSON.stringify(event, null, 2),
summarizeUrlContent: result,
});
}
};
14 changes: 14 additions & 0 deletions src/infrastructure/queue/queue.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ClassProvider, Module } from '@nestjs/common';
import { SQSService } from './sqs.service';

const providers: ClassProvider[] = [
{
provide: 'IQeueuService',
useClass: SQSService,
},
];
@Module({
providers,
exports: providers,
})
export class QueueModule {}
3 changes: 3 additions & 0 deletions src/infrastructure/queue/queue.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IQeueuService {
send<T = unknown>(data: T, queueName?: string): Promise<void>;
}
27 changes: 27 additions & 0 deletions src/infrastructure/queue/sqs.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { SQS, SendMessageRequest } from '@aws-sdk/client-sqs';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IQeueuService } from './queue.service';

@Injectable()
export class SQSService implements IQeueuService {
private queue: SQS;

constructor(private readonly config: ConfigService) {
this.queue = new SQS({
region: 'ap-northeast-2',
});
}

async send<T>(data: T) {
const queueUrl = this.config.get('AIEventQueue');
const payload: SendMessageRequest = {
QueueUrl: queueUrl,
DelaySeconds: 1,
MessageBody: JSON.stringify(data),
MessageGroupId: 'AI_CLASSIFICATION_EVENT',
};

await this.queue.sendMessage(payload);
}
}
7 changes: 7 additions & 0 deletions src/infrastructure/queue/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type AiClassificationPayload = {
postContent: string;
folderList: { id: string; name: string }[];
userId: string;
postId: string;
url: string;
};
6 changes: 2 additions & 4 deletions src/modules/posts/posts.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import {
Post,
PostSchema,
} from '@src/infrastructure';
import { AwsLambdaModule } from '@src/infrastructure/aws-lambda/aws-lambda.module';
import { AwsLambdaService } from '@src/infrastructure/aws-lambda/aws-lambda.service';
import {
PostKeyword,
PostKeywordSchema,
} from '@src/infrastructure/database/schema/postKeyword.schema';
import { QueueModule } from '@src/infrastructure/queue/queue.module';
import { PostsRepository } from '@src/modules/posts/posts.repository';
import { UsersModule } from '@src/modules/users/users.module';
import { AiClassificationModule } from '../ai-classification/ai-classification.module';
Expand All @@ -31,15 +30,14 @@ import { PostsService } from './posts.service';
{ name: PostKeyword.name, schema: PostKeywordSchema },
]),
UsersModule,
AwsLambdaModule,
AiClassificationModule,
QueueModule,
],
controllers: [PostsController],
providers: [
PostsService,
PostsRepository,
FolderRepository,
AwsLambdaService,
PostKeywordsRepository,
],
exports: [PostsService, PostsRepository],
Expand Down
15 changes: 5 additions & 10 deletions src/modules/posts/posts.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Inject, Injectable } from '@nestjs/common';
import { parseLinkTitleAndContent } from '@src/common';
import { IS_LOCAL } from '@src/common/constant';
import { Keyword, Post } from '@src/infrastructure';
import { AwsLambdaService } from '@src/infrastructure/aws-lambda/aws-lambda.service';
import { AiClassificationPayload } from '@src/infrastructure/aws-lambda/type';
import { FolderType } from '@src/infrastructure/database/types/folder-type.enum';
import { IQeueuService } from '@src/infrastructure/queue/queue.service';
import { CreatePostDto } from '@src/modules/posts/dto/create-post.dto';
import { PostAiStatus } from '@src/modules/posts/posts.constant';
import { PostsRepository } from '@src/modules/posts/posts.repository';
Expand All @@ -29,9 +28,8 @@ export class PostsService {
private readonly folderRepository: FolderRepository,
private readonly postKeywordsRepository: PostKeywordsRepository,

private readonly awsLambdaService: AwsLambdaService,
private readonly aiClassificationService: AiClassificationService,
private readonly config: ConfigService,
@Inject('IQeueuService') private readonly queueService: IQeueuService,
) {}

async listPost(userId: string, query: ListPostQueryDto) {
Expand Down Expand Up @@ -98,6 +96,7 @@ export class PostsService {
thumbnail,
PostAiStatus.IN_PROGRES,
);

const payload = {
url: createPostDto.url,
postContent: content,
Expand Down Expand Up @@ -270,10 +269,6 @@ export class PostsService {
return await this.aiClassificationService.execute(payload);
}

const aiLambdaFunctionName = this.config.get<string>(
'LAMBDA_FUNCTION_NAME',
);

await this.awsLambdaService.invokeLambda(aiLambdaFunctionName, payload);
await this.queueService.send(payload);
}
}