-
Notifications
You must be signed in to change notification settings - Fork 5
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
영속화에 red lock 및 트랜잭션 적용 #350
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
성민님 주말에도 열일하시는군용 Nest IoC 컨테이너 적극 활용하시는 부분이 너무 좋네요
컨플릭트 난 부분도 수정 부탁드립니다!!
// private readonly redisClient: Redis; | ||
|
||
constructor( | ||
@Inject(REDIS_CLIENT_TOKEN) private readonly redisClient: Redis, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오옷 이 부분도 수정하셨군요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
넵 근데 redis client를 DI 컨테이너에 넣으니까 RedLockService와 RedisService에서 순환 참조가 발생하더라고요 ㅠㅠ
일단 forwardRef로 해결을 하긴 했는데 이 부분도 수정을 하면 좋을 것 같네요
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P5: 리팩터링때 크론도 분리하면 좋겠군욧
맞다 그리고 데이터 매니저를 획득할 수 있는 상태면 |
저는 콜백 같은 거 사용하면 코드 블록이 하나 더 생기고 가독성이 좀 떨어지는 것 같아서 query runner를 사용했는데 지금 생각해보니 query runner를 사용해도 try, catch 때문에 어차피 코드 블록이 생기네요. |
@ezcolin2 error TS2307: Cannot find module 'redlock' or its corresponding type declarations. |
@Tolerblanc |
오옹 잘 동작하는군요!! 혹시 몰라서 |
🔖 연관된 이슈
📂 작업 내용
redis, page 연산에 RedLock 적용
스케줄러가 redis의 값을 데이터베이스에 영속화하는 과정은 다음과 같습니다.
그런데 만약 위 과정이 시작된 후 1번을 진행하고 2번을 시작하기 전에 해당 페이지 정보를 postgres에 삭제하면 문제가 발생하게 됩니다.
그래서 위 과정이 일어나는 동안 redis와 page 정보를 변경하는 것을 막기 위해 RedLock을 적용했습니다.
redis service와 page service의 조회를 제외한 메소드에 RedLock을 적용하여 lock을 획득했을 때만 연산을 진행하도록 구현했습니다.
redis.service.ts
transaction 적용
위와 같은 영속화 과정에서 redis의 모든 값들은 postgres에 성공적으로 반영되어야 합니다.
이를 위해 트랜잭션을 적용했습니다.
postgres update는 데이터베이스에서 제공해주는 트랜잭션을 사용했고 redis는 실패했을 경우 이전 데이터를 직접 넣어주는 애플리케이션 단에서 처리를 했습니다.
실패했을 때 로직
redis 전체 데이터 조회할 때 keys 연산 사용
redis의 keys 연산은 redis의 모든 key를 조회할 때 사용합니다.
redis는 싱글 스레드로 동작하기 때문에 keys 연산을 사용하면 조회하는 동안 다른 연산을 블록킹하는 문제가 있습니다.
redis 공식 문서에서는 상용 서버에서 keys를 사용하지 말고 scan 명령어를 사용할 것을 추천하지만 저희 프로젝트에서는 keys 연산을 사용하기로 결정했습니다.
이유는 다음과 같습니다.
첫 번째, 저희 프로젝트는 실시간 동시 문서 편집이기 때문에 redis에 굉장히 많은 갱신 연산이 발생하고 스케줄러를 통해 주기적으로 redis의 값을 데이터베이스로 영속화 합니다.
그리고 영속화하는 과정은 redis의 값을 데이터베이스에 넣고 변경 사항이 반영되고 나서 redis의 값을 삭제하는 방식으로 이루어집니다.
그런데 scan을 사용하면 하나의 문서에서 끊임없이 변경 사항이 발생할 때 문제가 생길 수 있습니다.
scan을 통해 일부 데이터를 영속화 한 뒤 삭제하고 그 데이터에 변경 사항이 발생하면 redis에 같은 key 값이 등록이 됩니다.
그렇다면 한 번의 사이클에서 이미 영속화를 한 key 값에 대해서 갱신이 여러 번 발생할 수 있습니다.
어차피 스케줄러가 주기적으로 데이터를 영속화하기 때문에 한 번의 사이클 기준으로 하나의 key에 대해서 한 번의 갱신만 일어나도 된다고 판단했습니다.
두 번째, redis에 저장되는 데이터 양 자체가 많지 않습니다.
redis에서 데이터를 가져와서 영속화를 한 다음 redis의 값을 삭제하기 때문에 스케줄러의 주기 동안 변경 사항이 발생하지 않은 페이지는 redis에 저장되지 않습니다.
그래서 keys 연산을 통해 O(N) 시간 복잡도 동안 redis 접근을 막더라도 큰 성능 저하가 발생하지 않을 것이라고 생각했습니다.
요약
📢 리뷰 요구사항 (선택)
postgres와 redis 연산 사이 트랜잭션 적용하는 게 어렵네요...
postgres는 데이터베이스에서 제공해주는 트랜잭션을 사용했고 redis는 애플리케이션 단에서 실패했을 때 값을 다시 세팅해주는 방식으로 사용했는데 더 좋은 방법이 있을까요?
최대한 redis와 postgres 데이터 정합성을 지키고 싶어서 트랜잭션을 적용했는데 연산이 많지 않아서 그냥 트랜잭션을 사용하지 않는 게 더 좋을 수도 있다는 생각이 드네요
이 부분에 대해서 여러분들 생각이 궁금합니다