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

장소 저장할 때 ES에도 저장 #164

Merged
merged 17 commits into from
Nov 24, 2024
Merged

장소 저장할 때 ES에도 저장 #164

merged 17 commits into from
Nov 24, 2024

Conversation

koomchang
Copy link
Collaborator

@koomchang koomchang commented Nov 21, 2024

📄 Summary

장소 저장할 때 ES에도 저장

🙋🏻 More

  • typeorm-transactional 모듈 설치
  • @nestjs/schedule 모듈 설치

처음에는 장소저장할 때 ES에도 저장을 같이하고 Transactional로 묶었다.

class PlaceService {
    
    @Transactional
    async addPlace() {
        const savedPlace = await this.placeRepository.save(place)
        await this.searchService.save(place)
        return {id: savedPlace.id}

그런데 MySQL에 저장이 되었는데 ES에 저장이 되지 않았다고 롤백하는 건 문제가 있다고 생각했다.
사용자 입장에서 생각해보면

  1. 새로운 장소를 등록했다.
  2. ES에 저장이 되지 않아 롤백되었다. (500에러인데 서버에서 처리해야 하는게 아닌가? + ES 다운되면 장소 추가가 아예 불가해짐)
  3. 사용자는 처리되지 않았다는 메세지를 받는다.

그래서 다시 생각을 해본 것이
MySQL에 저장하는 것만 트랜잭션을 걸고 (사실 저장로직만 있어서 지금은 트랜잭션 어노테이션 없어도 될 것 같긴함)
ES에 저장하는 이벤트만 발생을 시키고 id를 반환하는 식으로 구현했다.

class PlaceService {

    @Transactional
    async addPlace() {
        const savedPlace = await this.placeRepository.save(place);
        this.eventEmitter.emit('place.created', savedPlace);
        return { id: savedPlace.id };

이런식으로 하면 사용자는 ES의 상태에 상관없이 장소추가를 잘할 수 있고 서비스 운영에도 문제가 되지 않을 것이다.


추가적으로 이렇게 하면서 생각이 든 것이 장소 수정, 삭제 하는 경우에도 이런 식으로 이벤트 호출을 하면 굳이 배치를 쓰지 않아도 될 것이라 생각했다. 그래서 구현은 안 했는데 팀원분들의 생각이 궁금하니 코멘트 남겨주세요


241123 추가

생성, 수정, 삭제 2시간 내의 장소들을 검색하여 1시간 마다 ES와 동기화 (bulk, upsert) 할 수 있도록 구성했습니다. 실제 데이터가 잘 동기화되는 것을 확인했습니다.

스크린샷 2024-11-23 16 49 26 스크린샷 2024-11-23 16 48 16

🕰️ Actual Time of Completion

6

close #163

@koomchang koomchang added BE 백엔드 관련 이슈입니다 🌱 기능추가 새로운 기능 요청입니다 ⚙️ 리팩터링 코드 퀄리티를 높입니다 labels Nov 21, 2024
@koomchang koomchang added this to the week4 milestone Nov 21, 2024
@koomchang koomchang self-assigned this Nov 21, 2024
@koomchang koomchang linked an issue Nov 21, 2024 that may be closed by this pull request
2 tasks
Copy link
Collaborator

@Miensoap Miensoap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가적으로 이렇게 하면서 생각이 든 것이 장소 수정, 삭제 하는 경우에도 이런 식으로 이벤트 호출을 하면 굳이 배치를 쓰지 않아도 될 것이라 생각했다.

-> 얼마나 자주 수정/삭제 되는지에 따라 다를 것 같아요.

ES나 네트워크 상황에 따른 실패는 배치로 처리해야 하지 않을까요?

},
}),
EventEmitterModule.forRoot({
maxListeners: 15,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 15는 기본값인가요?
따로 결정했다면 이유가 있나요?

테스트 실패 로그보니 이 모듈 의존성이던데, 테스트 모듈에도 추가해야할 것 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기본값이 10인데, 큰 이유는 없습니다. 값을 어느정도로 조절해야할지는 잘 모르겠습니다.

async dataSourceFactory(options) {
const dataSource = new DataSource(options);
await dataSource.initialize();
return addTransactionalDataSource(dataSource);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 테스트에서 datasource 를 오버라이드해서 사용하는데,
이 설정도 따로 적용해야 할까요?

Copy link
Collaborator Author

@koomchang koomchang Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수정했습니다!

document: data,
});
} catch (e) {
this.logger.error(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1. ES 나 네트워크 상황에 따른 실패상황은 이후 재시도 해야 하지 않을까요?

그러러면, 이벤트를 처리하고 끝나는 것이 아니라,
배치 작업에서 구분할 수 있게 비동기적으로 성공/실패 여부를 저장해야 할 것 같아요.

검색에 안나와서 같은 장소에 대해 등록 요청이 다시 들어온다면?

이런 상황도 고려해야겠네요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞습니다.

이벤트 성공/실패 여부에 따른 작업이 배치로 이루어지거나 슬랙 알림등 오면 좋을 것 같아요. 혹시 에러로그로 해두면 이 부분을 슬랙 알림을 보낼 수 있게 설정 가능한가요?

Copy link
Collaborator

@Miensoap Miensoap Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://elastalert.readthedocs.io/en/latest/

특정 패턴에 알림을 보내게 설정할 수 있는 것 같아요.

실패 원인이 데이터 문제인 경우도 있을까요?
ES 통신 문제라면 그쪽을 모니터링해 알림을 받는게 낫지 않을까요?
ex) health check

@Miensoap Miensoap changed the title 코스 저장할 때 ES에도 저장 장소 저장할 때 ES에도 저장 Nov 21, 2024
@koomchang koomchang requested a review from Miensoap November 23, 2024 08:23
Copy link
Collaborator

@Miensoap Miensoap left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 안되는 거 잘 해결하셨네요!
수고하셨습니다~

q. 트랜잭션에서 Elasticsearch 에 저장하는 로직은 제외했다고 하셨는데,
제외하지 않으면 저 @Transactioanl 데코레이터가 ES 에 저장하는 것도 롤백시킬 수 있나요?

저 라이브러리 설정 dataSource 에 적용하는 것 같아서요.

@@ -11,8 +11,8 @@ export class CoursePermissionGuard implements CanActivate {
const courseId = Number(request.params.id);
const userId = Number(request.user.userId);

const course = await this.courseService.getCourseOwnerId(courseId);
if (course !== userId) {
const courseOwnerId = await this.courseService.getCourseOwnerId(courseId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2. 항상 함께 사용될 것 같은데 인증 가드를 상속해 사용하면 어떨까요?
불필요한 컨트롤러 인자와 가드 사용 코드를 줄일 수 있을 것 같아요.

참고 : AdminGuard

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 밑에서 다시 사용하나요?
변수로 분리하신 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 밑에서 다시 사용하나요? 변수로 분리하신 이유가 있나요?

가독성 고려했습니다!

this.logger.debug(`syncPlacesToElastic() 배치 시작`);
try {
this.logger.debug(`동기화 작업을 시작합니다.`);
const places = await this.placeRepository.findUpdatedPlacesForTwoHours();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 수동으로 동기화 한 것 포함해서 모두 동기화하나요?

많지 않아서 괜찮을 것 같습니다~

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 수동으로 잘 저장되는 데이터 수가 아주 많아서,
중복으로 동기화하는 비용이 유의미하게 커지는 지점이 어디쯤일지 계산해보면 좋을 것 같아요.
-> 그렇다면 실패했다는 표시를 어떻게 해서 주기적 동기화에 포함할 것인지?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 수동으로 동기화 한 것 포함해서 모두 동기화하나요?

많지 않아서 괜찮을 것 같습니다~

넵! 현재는 그런 상황이고 만약 syncedAt 같은 칼럼을 추가하면 최근에 싱크된 데이터는 빼고 동기화할 수 있을 것 같네요!

만약 수동으로 잘 저장되는 데이터 수가 아주 많아서, 중복으로 동기화하는 비용이 유의미하게 커지는 지점이 어디쯤일지 계산해보면 좋을 것 같아요. -> 그렇다면 실패했다는 표시를 어떻게 해서 주기적 동기화에 포함할 것인지?

맞습니다! 같이 고민해봐야 될 부분인 것 같네요

controllers: [PlaceController],
providers: [PlaceService, PlaceRepository],
providers: [PlaceService, PlaceRepository, SearchService, PinoLogger],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 이거 테스트 때문에 로거 추가하신건가요?

저도 비슷하게 글로벌 모듈로 이미 등록된 ConfigModule 을 다시 import 했는데요,
혹시 다른 방법 없을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 이거 테스트 때문에 로거 추가하신건가요?

저도 비슷하게 글로벌 모듈로 이미 등록된 ConfigModule 을 다시 import 했는데요, 혹시 다른 방법 없을까요?

테스트 때문에 추가했습니다.
저도 비슷한 고민을 했는데 테스트뿐만 아니라, 일반 로직이 들어있는 service들에서도 DIlogger를 받는게 좀 어색한 것 같더라구요. 로거를 싱글톤으로 이용할 수 있으면 좋지 않을까 고민했습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존의 nest 에서 제공하는 LoggerModule 구조를 따라간건데,
그것도 고민해봐야겠네용

readonly rating: number,
readonly location: {
lat: number;
lon: number;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3. lng 은 어떨까요?
보통 이렇게 줄이는 것 같아서요.

저는 아예 안줄이는 것도 좋다고 생각합니다

}
}

async searchPlace(
query: string,
lat?: number,
long?: number,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lon, long, lng , longitude 중 하나로 모든 코드를 통일하는 게 좋을 것 같아요.

});

this.logger.debug(
`Elasticsearch에 동기화된 장소의 갯수: ${response.items.length}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

디버그 로깅이 꼼꼼해서 좋네요 ㅎㅎ

container = await new MySqlContainer().withReuse().start();
dataSource = await initDataSource(container);

const module: TestingModule = await Test.createTestingModule({
imports: [
LoggerModule.forRoot({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 로거에 대한 의존성이 생기니까 테스트에도 영향이 가네요.
분리할 수 있는 방법이 없을까요?

logger.info() 등을 빈 함수로 모킹하는 테스트 유틸 모듈을 사용할 수 있을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q. 로거에 대한 의존성이 생기니까 테스트에도 영향이 가네요. 분리할 수 있는 방법이 없을까요?

logger.info() 등을 빈 함수로 모킹하는 테스트 유틸 모듈을 사용할 수 있을까요?

같이 고민해봐요..! 글로벌에서 테스트 모듈을 사용하는 것도 하나의 방법일 것 같고..
테스팅용 모듈을 만드는 비용이 크지 않다면 전체적인 의존성을 다 주입해도 괜찮지 않을까하는 생각도 해봤습니다!

@koomchang
Copy link
Collaborator Author

테스트 안되는 거 잘 해결하셨네요! 수고하셨습니다~

q. 트랜잭션에서 Elasticsearch 에 저장하는 로직은 제외했다고 하셨는데, 제외하지 않으면 저 @Transactioanl 데코레이터가 ES 에 저장하는 것도 롤백시킬 수 있나요?

저 라이브러리 설정 dataSource 에 적용하는 것 같아서요.

저번에 테스트 했을 때 mysql에 데이터 저장 되고, ES에서 터지면 mysql이 롤백되는 건 확인했습니다. 다만, ESNoSQL이다보니 트랜잭션 개념이 없어서, 롤백 개념이 없습니다. 쿼리가 실행되면 되돌릴 수 없습니다.

@koomchang koomchang merged commit 2a36c81 into develop Nov 24, 2024
2 checks passed
@koomchang koomchang deleted the feature/#163 branch November 24, 2024 14:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BE 백엔드 관련 이슈입니다 ⚙️ 리팩터링 코드 퀄리티를 높입니다 🌱 기능추가 새로운 기능 요청입니다
Projects
None yet
Development

Successfully merging this pull request may close these issues.

장소 데이터 추가, 수정, 삭제에 따른 배치 처리를 한다
3 participants