11import { Injectable , Logger } from '@nestjs/common' ;
22import { Cron , CronExpression } from '@nestjs/schedule' ;
3- import { RedisService } from '../redis/redis.service' ;
3+ import {
4+ RedisEdge ,
5+ RedisPage ,
6+ RedisNode ,
7+ RedisService ,
8+ } from '../redis/redis.service' ;
49import { DataSource } from 'typeorm' ;
510import { InjectDataSource } from '@nestjs/typeorm' ;
6- import { Page } from 'src/page/page.entity' ;
11+ import { Page } from '../page/page.entity' ;
12+ import { Node } from '../node/node.entity' ;
13+ import { Edge } from '../edge/edge.entity' ;
714
815@Injectable ( )
916export class TasksService {
@@ -18,28 +25,15 @@ export class TasksService {
1825 this . logger . log ( '스케줄러 시작' ) ;
1926 // 시작 시간
2027 const startTime = performance . now ( ) ;
21- const keys = await this . redisService . getAllKeys ( 'page:*' ) ;
28+ const pageKeys = await this . redisService . getAllKeys ( 'page:*' ) ;
29+ const nodeKeys = await this . redisService . getAllKeys ( 'node:*' ) ;
30+ const edgeKeys = await this . redisService . getAllKeys ( 'edge:*' ) ;
2231
23- Promise . allSettled (
24- keys . map ( async ( key : string ) => {
25- const redisData = await this . redisService . get ( key ) ;
26- // 데이터 없으면 오류
27- if ( ! redisData ) {
28- throw new Error ( `redis에 ${ key } 에 해당하는 데이터가 없습니다.` ) ;
29- }
30-
31- const { title, content } = redisData ;
32-
33- const updateData : Partial < { title : string ; content : any } > = { } ;
34-
35- if ( title ) updateData . title = title ;
36- if ( content ) updateData . content = JSON . parse ( content ) ;
37-
38- // 업데이트 대상이 없다면 리턴
39- if ( Object . keys ( updateData ) . length === 0 ) return ;
40- this . migrate ( key , updateData ) ;
41- } ) ,
42- )
32+ Promise . allSettled ( [
33+ ...pageKeys . map ( this . migratePage . bind ( this ) ) ,
34+ ...nodeKeys . map ( this . migrateNode . bind ( this ) ) ,
35+ ...edgeKeys . map ( this . migrateEdge . bind ( this ) ) ,
36+ ] )
4337 . then ( ( results ) => {
4438 const endTime = performance . now ( ) ;
4539 this . logger . log ( `총 개수 : ${ results . length } 개` ) ;
@@ -56,7 +50,23 @@ export class TasksService {
5650 } ) ;
5751 }
5852
59- async migrate ( key : string , updateData :Partial < { title : string ; content : any } > ) {
53+ async migratePage ( key : string ) {
54+ const redisData = ( await this . redisService . get ( key ) ) as RedisPage ;
55+ // 데이터 없으면 오류
56+ if ( ! redisData ) {
57+ throw new Error ( `redis에 ${ key } 에 해당하는 데이터가 없습니다.` ) ;
58+ }
59+
60+ const { title, content, emoji } = redisData ;
61+
62+ const updateData : Partial < Page > = { } ;
63+
64+ if ( title ) updateData . title = title ;
65+ if ( content ) updateData . content = JSON . parse ( content ) ;
66+ if ( emoji ) updateData . emoji = emoji ;
67+
68+ // 업데이트 대상이 없다면 리턴
69+ if ( Object . keys ( updateData ) . length === 0 ) return ;
6070 const pageId = parseInt ( key . split ( ':' ) [ 1 ] ) ;
6171
6272 // 트랜잭션 시작
@@ -79,8 +89,128 @@ export class TasksService {
7989 // 실패하면 postgres는 roll back하고 redis의 값을 살린다.
8090 this . logger . error ( err . stack ) ;
8191 await queryRunner . rollbackTransaction ( ) ;
82- updateData . title && ( await this . redisService . setField ( key , 'title' , updateData . title ) ) ;
83- updateData . content && ( await this . redisService . setField ( key , 'content' , updateData . content ) ) ;
92+ updateData . title &&
93+ ( await this . redisService . setField ( key , 'title' , updateData . title ) ) ;
94+ updateData . content &&
95+ ( await this . redisService . setField ( key , 'content' , JSON . parse ( content ) ) ) ;
96+ updateData . emoji &&
97+ ( await this . redisService . setField ( key , 'emoji' , updateData . emoji ) ) ;
98+
99+ // Promise.all에서 실패를 인식하기 위해 에러를 던진다.
100+ throw err ;
101+ } finally {
102+ // 리소스 정리
103+ await queryRunner . release ( ) ;
104+ }
105+ }
106+
107+ async migrateNode ( key : string ) {
108+ const redisData = ( await this . redisService . get (
109+ key ,
110+ ) ) as unknown as RedisNode ;
111+ // 데이터 없으면 오류
112+ if ( ! redisData ) {
113+ throw new Error ( `redis에 ${ key } 에 해당하는 데이터가 없습니다.` ) ;
114+ }
115+
116+ const updateData : Partial < Node > = {
117+ x : Number ( redisData . x ) ,
118+ y : Number ( redisData . y ) ,
119+ } ;
120+
121+ // 쿼리 대상이 없다면 리턴
122+ if ( Object . keys ( updateData ) . length === 0 ) return ;
123+ const nodeId = parseInt ( key . split ( ':' ) [ 1 ] ) ;
124+
125+ // 트랜잭션 시작
126+ const queryRunner = this . dataSource . createQueryRunner ( ) ;
127+ try {
128+ await queryRunner . startTransaction ( ) ;
129+
130+ // 갱신 시작
131+ const nodeRepository = queryRunner . manager . getRepository ( Node ) ;
132+
133+ // TODO : 페이지가 없으면 affect : 0을 반환하는데 이 부분 처리도 하는 게 좋을 듯...?
134+ await nodeRepository . update ( nodeId , updateData ) ;
135+
136+ // redis에서 데이터 삭제
137+ await this . redisService . delete ( key ) ;
138+
139+ // 트랜잭션 커밋
140+ await queryRunner . commitTransaction ( ) ;
141+ } catch ( err ) {
142+ // 실패하면 postgres는 roll back하고 redis의 값을 살린다.
143+ this . logger . error ( err . stack ) ;
144+ await queryRunner . rollbackTransaction ( ) ;
145+ await this . redisService . setField ( key , 'x' , updateData . x . toString ( ) ) ;
146+ await this . redisService . setField ( key , 'y' , updateData . y . toString ( ) ) ;
147+
148+ // Promise.all에서 실패를 인식하기 위해 에러를 던진다.
149+ throw err ;
150+ } finally {
151+ // 리소스 정리
152+ await queryRunner . release ( ) ;
153+ }
154+ }
155+
156+ async migrateEdge ( key : string ) {
157+ const redisData = ( await this . redisService . get (
158+ key ,
159+ ) ) as unknown as RedisEdge ;
160+ // 데이터 없으면 오류
161+ if ( ! redisData ) {
162+ throw new Error ( `redis에 ${ key } 에 해당하는 데이터가 없습니다.` ) ;
163+ }
164+
165+ // 트랜잭션 시작
166+ const queryRunner = this . dataSource . createQueryRunner ( ) ;
167+ try {
168+ await queryRunner . startTransaction ( ) ;
169+
170+ // 갱신 시작
171+ const edgeRepository = queryRunner . manager . getRepository ( Edge ) ;
172+ const nodeRepository = queryRunner . manager . getRepository ( Node ) ;
173+
174+ const fromNode = await nodeRepository . findOne ( {
175+ where : { id : redisData . fromNode } ,
176+ } ) ;
177+
178+ const toNode = await nodeRepository . findOne ( {
179+ where : { id : redisData . toNode } ,
180+ } ) ;
181+
182+ if ( redisData . type === 'add' ) {
183+ await edgeRepository . save ( { fromNode, toNode } ) ;
184+ }
185+
186+ if ( redisData . type === 'delete' ) {
187+ const edge = await edgeRepository . findOne ( {
188+ where : { fromNode, toNode } ,
189+ } ) ;
190+
191+ await edgeRepository . delete ( { id : edge . id } ) ;
192+ }
193+
194+ // redis에서 데이터 삭제
195+ await this . redisService . delete ( key ) ;
196+
197+ // 트랜잭션 커밋
198+ await queryRunner . commitTransaction ( ) ;
199+ } catch ( err ) {
200+ // 실패하면 postgres는 roll back하고 redis의 값을 살린다.
201+ this . logger . error ( err . stack ) ;
202+ await queryRunner . rollbackTransaction ( ) ;
203+ await this . redisService . setField (
204+ key ,
205+ 'fromNode' ,
206+ redisData . fromNode . toString ( ) ,
207+ ) ;
208+ await this . redisService . setField (
209+ key ,
210+ 'toNode' ,
211+ redisData . toNode . toString ( ) ,
212+ ) ;
213+ await this . redisService . setField ( key , 'type' , redisData . type ) ;
84214
85215 // Promise.all에서 실패를 인식하기 위해 에러를 던진다.
86216 throw err ;
0 commit comments