문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판 이전 판 다음 판 | 이전 판 다음 판 양쪽 다음 판 | ||
web:신규서비스 [2023/08/08 11:13] kwon37xi [MQ 등을 통한 비동기 처리 / event driven] |
web:신규서비스 [2024/02/08 09:41] kwon37xi [MQ 등을 통한 비동기 처리 / event driven] |
||
---|---|---|---|
줄 190: | 줄 190: | ||
* 중요 테이블의 경우 최종 수정된 내용만 가지고 있고 그에 대해 제약 조건을 모두 지키도록 설계한다. 다만, 변경시마다 **중요 변경사항을 history 테이블을 따로 두어** 남기도록 한다. 그래야 서비스 유지보수시 알 수 없는 오류에 대한 참고 데이터로 삼아 고객의 요구에 대해 올바로 대응 할 수 있다. history는 RDBMS 가 아니라 NoSQL로 남기는 것도 좋다. | * 중요 테이블의 경우 최종 수정된 내용만 가지고 있고 그에 대해 제약 조건을 모두 지키도록 설계한다. 다만, 변경시마다 **중요 변경사항을 history 테이블을 따로 두어** 남기도록 한다. 그래야 서비스 유지보수시 알 수 없는 오류에 대한 참고 데이터로 삼아 고객의 요구에 대해 올바로 대응 할 수 있다. history는 RDBMS 가 아니라 NoSQL로 남기는 것도 좋다. | ||
* **컬럼의 의미를 바꾸지 말 것.** DB의 역사가 오래될 수록 컬럼의 의미를 중간에 바꾸는 경우가 있는데, **컬럼의 의미를 변경하려면 기존 데이터를 모두 마이그레이션 하던지, 새로운 컬럼을 만들어서 새로운 의미를 부여하든지 한다.** 기존 데이터를 그대로 남겨둔 상태로 컬럼의 의미를 바꾸면 매핑되는 객체 구조 설계에도 문제가 생기고 쿼리 결과를 처리하는 모든 구문에 상황에 따른 조건문이 계속 추가되어 개발 부담을 가중시키게 된다. | * **컬럼의 의미를 바꾸지 말 것.** DB의 역사가 오래될 수록 컬럼의 의미를 중간에 바꾸는 경우가 있는데, **컬럼의 의미를 변경하려면 기존 데이터를 모두 마이그레이션 하던지, 새로운 컬럼을 만들어서 새로운 의미를 부여하든지 한다.** 기존 데이터를 그대로 남겨둔 상태로 컬럼의 의미를 바꾸면 매핑되는 객체 구조 설계에도 문제가 생기고 쿼리 결과를 처리하는 모든 구문에 상황에 따른 조건문이 계속 추가되어 개발 부담을 가중시키게 된다. | ||
- | * DB 자체의 타입 enum 을 사용하지 말라. 또한 프로그래밍 언어 enum 을 사용할 때 절대로 순서 숫자값(ordinal)로 저장하면 안된다. 이 둘은 모두 enum 항목들의 순서 변경이 발생하는 순간 엄청난 마이그레이션을 수행해야한다. 그에 비해 성능 향상은 그리 크지 않아보인다. MySQL의 enum은 겉보기와는 달리 ordinal 로 작동하기 때문에 **enum 의 순서가 변경**되면 단순 alter table로 문제가 해결되지 않고 기존 데이터를 모두 마이그레이션 해야 한다. | + | * DB 자체의 타입 enum 을 사용하지 말라. 또한 프로그래밍 언어 enum 을 사용할 때 절대로 순서 숫자값(ordinal)로 저장하면 안된다. 이 둘은 모두 enum 항목들의 순서 변경이 발생하는 순간 엄청난 마이그레이션을 수행해야한다. 그에 비해 성능 향상은 그리 크지 않아보인다. |
* 과도한 동적 쿼리를 생성하지 말라. - 특히 iBatis/ | * 과도한 동적 쿼리를 생성하지 말라. - 특히 iBatis/ | ||
* 상태에 따라 WHERE 절이 나타났다 사라졌다하는 식의 동적 쿼리는 자칫 실수하면 모든 조건이 없어져서 엄청난 부하를 일으키는 SELECT 쿼리나 모든 데이터를 삭제하는 DELETE 문이 될 수 있다. | * 상태에 따라 WHERE 절이 나타났다 사라졌다하는 식의 동적 쿼리는 자칫 실수하면 모든 조건이 없어져서 엄청난 부하를 일으키는 SELECT 쿼리나 모든 데이터를 삭제하는 DELETE 문이 될 수 있다. | ||
줄 277: | 줄 277: | ||
* **분산 캐시는 value type 이 배포중간에 바뀌는 경우 심각한 오류에 직면할 수 있다.** 이게 중요한 곳에는 분산 캐시를 사용하면 안 된다. | * **분산 캐시는 value type 이 배포중간에 바뀌는 경우 심각한 오류에 직면할 수 있다.** 이게 중요한 곳에는 분산 캐시를 사용하면 안 된다. | ||
* 분산 캐시의 캐시된 value 에 대한 type 은 DB schema 처럼 매우 중요하게 다뤄야한다. 변경된 캐시 value 타입이 배포되는 동안 에러가 발생하는 현상이 잦기 때문에, 호환성을 항상 염두에 둬야한다. | * 분산 캐시의 캐시된 value 에 대한 type 은 DB schema 처럼 매우 중요하게 다뤄야한다. 변경된 캐시 value 타입이 배포되는 동안 에러가 발생하는 현상이 잦기 때문에, 호환성을 항상 염두에 둬야한다. | ||
- | * [[java: | + | * [[java: |
* 가급적 JSON 등의 plain 한 방식을 취하고, 필드나 타입이 변경되면 단순히 null 로 처리하고 에러는 안내는 방식을 취해야 한다. | * 가급적 JSON 등의 plain 한 방식을 취하고, 필드나 타입이 변경되면 단순히 null 로 처리하고 에러는 안내는 방식을 취해야 한다. | ||
* 하지만 이 경우에도 문제가 있는데, 필드를 null 처리 할 경우 해당 필드를 꼭 사용해야하는 코드가 배포되는 중간에 있을 때 심각한 문제가 발생할 수 있다. 따라서 이 경우에는 cache key 자체를 변경해야 한다. | * 하지만 이 경우에도 문제가 있는데, 필드를 null 처리 할 경우 해당 필드를 꼭 사용해야하는 코드가 배포되는 중간에 있을 때 심각한 문제가 발생할 수 있다. 따라서 이 경우에는 cache key 자체를 변경해야 한다. | ||
줄 333: | 줄 333: | ||
* 서비스 런칭 시점부터 하는게 좋다. 나중에 붙이다가 그동안 매우 많이 사용되던 막아서는 안되는 것을 막는 일이 일어나기 쉽다. | * 서비스 런칭 시점부터 하는게 좋다. 나중에 붙이다가 그동안 매우 많이 사용되던 막아서는 안되는 것을 막는 일이 일어나기 쉽다. | ||
* 이 때 IP 차단시 동일 NAT 를 사용하는 사용자가 많이 존재할 수 있음을 염두에 두어야 한다. 명백하게 해당 NAT IP 를 사용하는 자가 공격일 경우에만 차단한다. | * 이 때 IP 차단시 동일 NAT 를 사용하는 사용자가 많이 존재할 수 있음을 염두에 두어야 한다. 명백하게 해당 NAT IP 를 사용하는 자가 공격일 경우에만 차단한다. | ||
+ | * 규모가 큰 시스템이고 보편적이지 않은 흐름에서 데이터 소유주 본인이 아닌 사용자에 의해 admin 등을 통한 데이터 조작 행위가 일어날 경우 이를 트래킹하는 저장소 시스템을 만들고, 각 admin 혹은 데이터 변경을 일으키는 시스템에서 해당 시스템으로 행위자와 행위 관련 데이터(뭐를 어떻게 언제 변경시켰는지)를 전송하고 트래킹할 수 있는 시스템을 구축하는 것이 좋다. | ||
===== 서버 운영 ===== | ===== 서버 운영 ===== | ||
* 절대로 여러 사람이 공유하는 공용 계정으로 서버를 관리하지 말라(AWS 등 포함). 이는 치명적인 보안 사고로 이어진다. | * 절대로 여러 사람이 공유하는 공용 계정으로 서버를 관리하지 말라(AWS 등 포함). 이는 치명적인 보안 사고로 이어진다. | ||
줄 435: | 줄 436: | ||
* [[https:// | * [[https:// | ||
* 필드 변경을 반영한 UI와 Presentation 계층 API가 동시 배포된다면 Presentation 계층에서는 괜찮을 수 있다. | * 필드 변경을 반영한 UI와 Presentation 계층 API가 동시 배포된다면 Presentation 계층에서는 괜찮을 수 있다. | ||
+ | * enum 에 대해 주의가 필요하다. | ||
+ | * Java Enum 으로 API 요청을 받는데, 해당 Enum 이 외부에서 관리되는 것이라면 배포 타이밍에 따라 Enum 값 갱신이 안 된 상태일 수 있다. | ||
+ | * Enum 공급자(변경자) 측은, enum 추가시 사용처를 모두 확인해서 공지해주고 배포 타이밍을 다른 팀에 모두 Enum 을 추가한 뒤로 맞춰줘야 한다. | ||
+ | * Enum 소비자측은 | ||
+ | * Enum 으로 뭔가 작업이 필요할 때는 해당 타입도 Enum 으로 하여 Enum 이 올바로 추가 안됐을 때 차라리 에러를 내는게 나을 수 있다. | ||
+ | * 혹은 String 으로 받되 해당 시스템이 파악하고 있는 Enum 인지 여부를 검사하고 정확한 에러를 내게 처리하는게 나을 수도 있다. | ||
+ | * enum 값을 단지 DB에 저장하거나 다른 API 호출에 사용만 하고 실제 이를 가지고 비즈니스 로직을 처리하지 않는 경우는 '' | ||
===== MQ 등을 통한 비동기 처리 / event driven ===== | ===== MQ 등을 통한 비동기 처리 / event driven ===== | ||
* 비동기 처리는 반응 속도를 높이고 전송 신뢰도를 높여주는 등 좋은 점이 있지만 단점들도 많으므로 확실히 이해해야 한다. | * 비동기 처리는 반응 속도를 높이고 전송 신뢰도를 높여주는 등 좋은 점이 있지만 단점들도 많으므로 확실히 이해해야 한다. | ||
줄 441: | 줄 449: | ||
* **비동기 타이밍 문제도 심각하다.** 이 경우 비동기를 사용하면 안되는데 비동기를 사용한 경우일 수 있다. A -> B로 메시지를 보냈는데 B 가 메시지를 Consuming 하기 전에 A 혹은 C 등의 다른 서비스가 B가 메시지를 받았다는 가정하에 어떤 행위를 할 경우 오류가 발생한다. 매우 빈번히 발생하는 문제이다. 비동기를 사용하면 안 되거나 B가 메시지를 받았는지 확인할 수 있는 방안을 강구해야 한다.(JMX의 경우에는 Consume 확인 기능이 존재함) | * **비동기 타이밍 문제도 심각하다.** 이 경우 비동기를 사용하면 안되는데 비동기를 사용한 경우일 수 있다. A -> B로 메시지를 보냈는데 B 가 메시지를 Consuming 하기 전에 A 혹은 C 등의 다른 서비스가 B가 메시지를 받았다는 가정하에 어떤 행위를 할 경우 오류가 발생한다. 매우 빈번히 발생하는 문제이다. 비동기를 사용하면 안 되거나 B가 메시지를 받았는지 확인할 수 있는 방안을 강구해야 한다.(JMX의 경우에는 Consume 확인 기능이 존재함) | ||
* MQ Consumer 서버를 API 서버와 분리한다. MQ Consumer 가 DB 커넥션 쓰레드나, | * MQ Consumer 서버를 API 서버와 분리한다. MQ Consumer 가 DB 커넥션 쓰레드나, | ||
- | * Event body 에 모든 데이터를 심는 방법은 이벤트 전송 부담을 키운다. 이벤트에는 이벤트를 발생시킨 데이터의 종류(상품, | ||
- | * zero payload 상황에서 consumer는 producer 에게 실제 데이터를 API로 요청해야하는데, | ||
- | * zero payload가 이벤트를 받는측에서 데이터를 조회해야해서 producer 측에 DB부담을 가중시킬 가능성도 있다. 혹은 어떤 상황에서는 이벤트 발행 그 시점의 데이터가 필요할 때도 있다. 따라서 데이터를 넣어서 전송해야 할 일이 있다면 그 정책을 producer가 payload 생성으로 인해 쓰레드가 길어지거나 너무 많은 데이터 조회로 | ||
* 모니터링 | * 모니터링 | ||
* 메시지 발생 실패를 모니터링 / 알람 해야 한다. | * 메시지 발생 실패를 모니터링 / 알람 해야 한다. | ||
* 발행은 성공했으나 consumer 처리 적체가 발생할 수 있으므로 Queue 에 대기중인 메시지가 너무 많을 경우에 대해서도 모니터링 알람해야한다. | * 발행은 성공했으나 consumer 처리 적체가 발생할 수 있으므로 Queue 에 대기중인 메시지가 너무 많을 경우에 대해서도 모니터링 알람해야한다. | ||
+ | |||
+ | |||
+ | ===== Event PayLoad 정책 ===== | ||
+ | ==== ᅟZero Payload ==== | ||
+ | * Consumer 측에서 이벤트에 대해서 Event Source 데이터를 최신화하기만 하면 되는 경우에는 zero payload 가 유리해 보임. | ||
+ | * 즉, 데이터의 최종 상태 동기화만 하면 되는 경우(Eventually Consistency 보장)다. | ||
+ | * 예시) 업체 정보, 고객 정보 등의 변경을 consumer 측에서 최신 데이터로 계속 갱신만 하면 되는 상황 | ||
+ | * Event body 에 모든 데이터를 심는 방법은 이벤트 전송 부담을 키운다. 이벤트에는 이벤트를 발생시킨 데이터의 종류(상품, | ||
+ | * zero payload 상황에서 consumer는 producer 에게 실제 데이터를 API로 요청해야하는데, | ||
+ | * zero payload가 이벤트를 받는측에서 데이터를 조회해야해서 producer 측에 DB부담을 가중시킬 가능성도 있다. 혹은 어떤 상황에서는 이벤트 발행 그 시점의 데이터가 필요할 때도 있다. 따라서 데이터를 넣어서 전송해야 할 일이 있다면 그 정책을 producer가 payload 생성으로 인해 쓰레드가 길어지거나 너무 많은 데이터 조회로 | ||
+ | |||
+ | ==== Full Payload ==== | ||
+ | * Consumer 측에서 event source의 상태 변경에 대해서 서로 다른 행위를 해야하는 경우에는 full payload 가 아니면 장애가 날 수 있다. | ||
+ | * 이 경우 **순서 보장도 매우 중요하다.** | ||
+ | * 보통 이 경우는 event source 데이터의 최신화는 목적이 아니다. | ||
+ | * 예시) 주문 정보의 변화 이벤트는 이 이벤트를 받는 측에서 주문 정보를 최신으로 저장하는 것에는 관심이 없고 주문이 생성되면 " | ||
+ | |||
===== Single Page Application? | ===== Single Page Application? | ||
* 2020년 현재 대부분의 Front End Framework 이 SPA 기반이다. SPA를 사용하지 말라는 것은 유효하지 않다. | * 2020년 현재 대부분의 Front End Framework 이 SPA 기반이다. SPA를 사용하지 말라는 것은 유효하지 않다. |