사용자 도구

사이트 도구


web:신규서비스

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

양쪽 이전 판 이전 판
다음 판
이전 판
web:신규서비스 [2021/10/27 10:09]
kwon37xi [Excel Download / 통계 제공]
web:신규서비스 [2023/06/01 09:50] (현재)
kwon37xi [Database / 저장소]
줄 19: 줄 19:
 ===== 신기술 선택 ===== ===== 신기술 선택 =====
   * 다음과 같은 용어를 확인해볼것.   * 다음과 같은 용어를 확인해볼것.
-  * ''단점'', ''주의할 점'', ''위험성'', ''cons'', ''pitfall'', ''gotchas'', ''warns'' +  * ''단점'', ''주의할 점'', ''위험성'', ''실수'', ''cons'', ''pitfall'', ''gotchas'', ''warns'', ''when not to use'', ''should not use'', ''avoid''
 ===== 용어 통일 ===== ===== 용어 통일 =====
   * 팀 내/외 소통시 통일된 용어는 매우 중요하다.   * 팀 내/외 소통시 통일된 용어는 매우 중요하다.
줄 67: 줄 66:
     * MSA 로 갈 때는 팀간에 공통 라이브러리를 만들기 보다는 **공통의 규약**을 정립하는 것이 나아 보인다. 프로젝트간 의존성이 묶이는 것은 피해야 각 팀별로 다양한 언어, 기타 라이브러리 의존성 업그레이드 등이 가능해 진다.     * MSA 로 갈 때는 팀간에 공통 라이브러리를 만들기 보다는 **공통의 규약**을 정립하는 것이 나아 보인다. 프로젝트간 의존성이 묶이는 것은 피해야 각 팀별로 다양한 언어, 기타 라이브러리 의존성 업그레이드 등이 가능해 진다.
  
 +===== Team 이 아닌 Project 단위 구성 =====
 +  * APM, Wiki 공간, 각종 로깅 시스템 구분, Resource Tagging 등을 Team 기준으로 하지 말고 Project 기준으로 해야한다.
 +  * Team 은 지속적으로 바뀌지만 Project 는 프로젝트 자체가 폐기 될 때까지 잘 바뀌지 않는다.
 +  * 가급적 애플리케이션 관련 모든 정보 조직과 행위의 기준을 Project 를 단위로 가져가고 Project 에 하위 정보로 Team 을 넣는 방식을 취한다.
 +  * 이렇게 하지 않고 팀을 기준으로 하면 얼마 안가 고아가 된 정보들이 양산되게 된다.
 ===== API Gateway ===== ===== API Gateway =====
-  * **내부 애플리케이션 API를 외부(브라우저, 앱)로 노출시키는 역할에 API Gateway를 사용하지 말라.** +  * **내부 애플리케이션 API를 외부(브라우저, 앱)로 노출시키는 역할에 API Gateway Framework를 사용하지 말라.** 
-    * 이는 치명적인 보안 위협이 된다.+    * 이는 치명적인 보안 위협이 된다. [[https://www.youtube.com/watch?v=P2nM0_YptOA|API Gateway Pattern에는 API Gateway가 없다 - YouTube]]
     * 특히 인증만 하고, 권한 검사를 제대로 안하고 노출시키는 일이 빈번해서, 인증 받은 특정 사용자가 API Gateway 로 열려있는 내부 API에서 본인이 아닌 다른 사용자의 정보를 읽을 수 있게 된다.     * 특히 인증만 하고, 권한 검사를 제대로 안하고 노출시키는 일이 빈번해서, 인증 받은 특정 사용자가 API Gateway 로 열려있는 내부 API에서 본인이 아닌 다른 사용자의 정보를 읽을 수 있게 된다.
-    * Netflix 의 [[java:zuul|Zuul - Edge Service]] 을 보면, 내부 API를 노출시키는 용도가 아니라, 외부 전용 API 서비스인데, 인증 방식이 다른 것들을 API Gateway 로 노출하가게 구성하는 식으로 만든 것이다.+    * Netflix 의 [[java:zuul|Zuul]] 을 보면, 내부 API를 노출시키는 용도가 아니라, 외부 전용 API 서비스인데, 인증 방식이 다른 것들을 API Gateway 로 노출하가게 구성하는 식으로 만든 것이다.
   * 즉, 외부 노출용 API는 그 안에서 사용자를 인증하고, 다른 API를 호출하고, 권한을 검사하는 로직을 다 직접 작성하는 것이 낫다.   * 즉, 외부 노출용 API는 그 안에서 사용자를 인증하고, 다른 API를 호출하고, 권한을 검사하는 로직을 다 직접 작성하는 것이 낫다.
   * 이게 싫다고 **내부용 API 에 권한 로직을 넣는 것도 하지 말라.**   * 이게 싫다고 **내부용 API 에 권한 로직을 넣는 것도 하지 말라.**
줄 78: 줄 82:
     * 게다가 그 와중에 또다른 권한 군이 들어오게 되면 모든 MSA 서비스가 그것들을 모두 신경써가면서 개발해야하게 되어 개발 속도를 떨어뜨리고 보안 위협이 시작되게 된다.     * 게다가 그 와중에 또다른 권한 군이 들어오게 되면 모든 MSA 서비스가 그것들을 모두 신경써가면서 개발해야하게 되어 개발 속도를 떨어뜨리고 보안 위협이 시작되게 된다.
   * **외부 노출 API가 인증과 권한을 전담해야 한다.**   * **외부 노출 API가 인증과 권한을 전담해야 한다.**
 +  * 하나의 API 애플리케이션은 하나의 인증만 처리한다. 인증이 여러 종류가 섞이는 순간 혼동으로 인해 보안 사고가 발생할 확률이 높아진다. 특히, **Front 서버에 내부용 API를 넣는 행동은 절대로 해서는 안된다.**
 ===== 약한 결합도 높은 응집도의 Inteface 기반 개발 ===== ===== 약한 결합도 높은 응집도의 Inteface 기반 개발 =====
   * 각각의 모듈간의 호출은 ''interface'' 기반으로 약한 결합도 높은 응집도를 유지해야 한다.   * 각각의 모듈간의 호출은 ''interface'' 기반으로 약한 결합도 높은 응집도를 유지해야 한다.
줄 127: 줄 132:
   * 비추천(계정관련만 https) : ''https://account.example.com'', ''http://www.example.com'', ''http://articles.example.com''   * 비추천(계정관련만 https) : ''https://account.example.com'', ''http://www.example.com'', ''http://articles.example.com''
   * 개발용 도메인을 ''example.**dev**'', 통합 테스트 도메인 ''example.**test**'' 처럼 전혀 다른 최상위 도메인으로 끝나게 하면 실서비스와 쿠키 정보가 섞이지 않아 편해질 수 있다. 또한 실서비스에서 테스트인 줄 착각하는 일도 줄어든다.   * 개발용 도메인을 ''example.**dev**'', 통합 테스트 도메인 ''example.**test**'' 처럼 전혀 다른 최상위 도메인으로 끝나게 하면 실서비스와 쿠키 정보가 섞이지 않아 편해질 수 있다. 또한 실서비스에서 테스트인 줄 착각하는 일도 줄어든다.
 +  * 내부망용 도메인 네임 규칙과 외부망용 도메인 네임 규칙을 명확히 가져가야 보안 이슈 발생시 대응이 쉽다.
 +  * domain name 을 모두 취합하고, 갱신 일자를 정리하고 관리해야한다. 많은 회사들이 회사 핵심 domain name 갱신일을 놓쳐서 서비스가 중단되는 사태를 겪는다.
 ===== 안정성 ===== ===== 안정성 =====
   * SPoF(Single Point of failure)를 제거하라.   * SPoF(Single Point of failure)를 제거하라.
줄 133: 줄 140:
  
 ===== 확장성 고려 ===== ===== 확장성 고려 =====
-  * 선형 증가 데이터와 지수적 증가 데이터를 잘 구분하여 선형 증가 데이터는 RDBMS(혹은 상황에 따라 다른 적합한 것), 지수적 증가 데이터베이스는 RDBMS sharding 혹은 [[:nosql|NoSQL]]을 고민한다.+  * 선형 증가 데이터와 지수적 증가 데이터를 잘 구분하여 애초에 저장소를 분리한다. 
 +    * 선형 증가 데이터 : 상품, 사용자 등 
 +    * 지수적 증가 데이터 : 주문, 배송 등 상품에 대해 사용자가 행위하는 것 
 +    * 선형 증가 데이터는 RDBMS(혹은 상황에 따라 다른 적합한 것), 지수적 증가 데이터베이스는 RDBMS sharding 혹은 [[:nosql|NoSQL]]을 고민한다.
     * 초반부터 지나친 확장성 대비는 오히려 악영향을 줄 수 있는 것은 사실이지만,     * 초반부터 지나친 확장성 대비는 오히려 악영향을 줄 수 있는 것은 사실이지만,
     * 현재(2013) 상황으로 봤을 때 처음부터 NoSQL을 도입하는 것이 RDBMS를 도입하는 것과 비교해서 특별히 더 어렵지도 않게 되었다.     * 현재(2013) 상황으로 봤을 때 처음부터 NoSQL을 도입하는 것이 RDBMS를 도입하는 것과 비교해서 특별히 더 어렵지도 않게 되었다.
-    * RDBMS 샤딩은 어려운 것은 사실이지만 NoSQL 도입은 초반 부터 고려해도 괜찮아 보인다.+    * RDBMS 샤딩은 어려운 것은 사실이지만 NoSQL 도입은 초반 부터 고려해도 괜찮아 보인다. 혹은 샤딩을 안하더라도 지수적 증가 데이터의 PK 혹은 Unique Key 설계시 샤딩 가능하게 설계하는 것도 좋아보인다.
   * RDBMS와 유사하게 MQ, Cache 등도 도메인별로 분할하라. 처음부터 분할하면 서버 대수가 너무 많이 늘어나게 되는데, 그 경우에는 최소한 Major 도메인에 대해서만이라도 분할하거나 혹은 물리 장비는 하나로 묶더라도 논리적으로는 분할해 두어 추후 Scale Up이 필요할 때 물리적 분할로 인한 코드 변경이 필요없게 해주는게 좋다.   * RDBMS와 유사하게 MQ, Cache 등도 도메인별로 분할하라. 처음부터 분할하면 서버 대수가 너무 많이 늘어나게 되는데, 그 경우에는 최소한 Major 도메인에 대해서만이라도 분할하거나 혹은 물리 장비는 하나로 묶더라도 논리적으로는 분할해 두어 추후 Scale Up이 필요할 때 물리적 분할로 인한 코드 변경이 필요없게 해주는게 좋다.
   * Sharding은 꼭 필요한 경우에만 어쩔 수 없을 때 하고, 선형적 증가 데이터라면 되도록 기능별 DB 분할을 고려하는 것이 낫다.   * Sharding은 꼭 필요한 경우에만 어쩔 수 없을 때 하고, 선형적 증가 데이터라면 되도록 기능별 DB 분할을 고려하는 것이 낫다.
줄 159: 줄 169:
   * SQL에서 데이터를 생성하지 말라. 예를들어 ''now()'', ''password()'' 같은 것 사용금지. **데이터 생성/변형은 애플리케이션에서 일관되게 처리**한다. 그렇지 않으면 추후 확장시 문제요소가 될 수 있다.   * SQL에서 데이터를 생성하지 말라. 예를들어 ''now()'', ''password()'' 같은 것 사용금지. **데이터 생성/변형은 애플리케이션에서 일관되게 처리**한다. 그렇지 않으면 추후 확장시 문제요소가 될 수 있다.
   * Production 서버 애플리케이션에서 사용할 계정은 최소 권한만으로 생성한다. 안그러면 개발자 실수로 drop table, 혹은 hibernate ''hbm2ddl.auto'' 옵션에 의해 DB를 날릴 수 있다.   * Production 서버 애플리케이션에서 사용할 계정은 최소 권한만으로 생성한다. 안그러면 개발자 실수로 drop table, 혹은 hibernate ''hbm2ddl.auto'' 옵션에 의해 DB를 날릴 수 있다.
 +    * DDL 권한은 없어야 한다.
 +    * DML(''update'',''delete'')도 ''where'' 조건이 없으면 실행 불가로 하는 것이 좋다.
   * 특별한 이유가 없다면 무조건 ORM 혹은 이에 준하는 솔루션을 사용하라. DB 쿼리 튜닝보다는 ORM으로 객체지향적이고 유지보수성 높은 코드를 짜고서, Cache 등으로 거시적 튜닝을 하는 것이 좋다.   * 특별한 이유가 없다면 무조건 ORM 혹은 이에 준하는 솔루션을 사용하라. DB 쿼리 튜닝보다는 ORM으로 객체지향적이고 유지보수성 높은 코드를 짜고서, Cache 등으로 거시적 튜닝을 하는 것이 좋다.
   * 저장소에 Script Code 성 데이터(Stored Procedure나 이에 준하는 것들)를 넣지 않는다.   * 저장소에 Script Code 성 데이터(Stored Procedure나 이에 준하는 것들)를 넣지 않는다.
줄 173: 줄 185:
   * 항상 올바른 타입을 사용하려고 노력한다. 날짜는 날짜 타입, boolean, 숫자 등 항상 적합한 타입을 사용해야 쓰레기 데이터를 막을 수 있다.   * 항상 올바른 타입을 사용하려고 노력한다. 날짜는 날짜 타입, boolean, 숫자 등 항상 적합한 타입을 사용해야 쓰레기 데이터를 막을 수 있다.
   * **PK가 아닌 숫자값과 boolean 타입은 모두 NOT NULL**로 설계한다. 그렇지 않으면 숫자 연산에 대해 ''0''인 상태와 ''NULL''인 상태 모두에 대해 항상 조건을 걸거나 ''NULL -> 0'' 변경을 수행해야만 하게 된다. 또한 boolean도 상태가 ''true/false/null'' 세가지 상태가 되어 버린다.   * **PK가 아닌 숫자값과 boolean 타입은 모두 NOT NULL**로 설계한다. 그렇지 않으면 숫자 연산에 대해 ''0''인 상태와 ''NULL''인 상태 모두에 대해 항상 조건을 걸거나 ''NULL -> 0'' 변경을 수행해야만 하게 된다. 또한 boolean도 상태가 ''true/false/null'' 세가지 상태가 되어 버린다.
 +    * 만약에 숫자값의 ''null''이 의미가 있다면 정확하게 주석을 단다.
   * 모든 테이블에는 insert 시간과 modify 시간을 모두 기록한다(''createdAt'', ''modifiedAt'').   * 모든 테이블에는 insert 시간과 modify 시간을 모두 기록한다(''createdAt'', ''modifiedAt'').
   * 중요 테이블의 경우 최종 수정된 내용만 가지고 있고 그에 대해 제약 조건을 모두 지키도록 설계한다. 다만, 변경시마다 **중요 변경사항을 history 테이블을 따로 두어** 남기도록 한다. 그래야 서비스 유지보수시 알 수 없는 오류에 대한 참고 데이터로 삼아 고객의 요구에 대해 올바로 대응 할 수 있다. history는 RDBMS 가 아니라 NoSQL로 남기는 것도 좋다.   * 중요 테이블의 경우 최종 수정된 내용만 가지고 있고 그에 대해 제약 조건을 모두 지키도록 설계한다. 다만, 변경시마다 **중요 변경사항을 history 테이블을 따로 두어** 남기도록 한다. 그래야 서비스 유지보수시 알 수 없는 오류에 대한 참고 데이터로 삼아 고객의 요구에 대해 올바로 대응 할 수 있다. history는 RDBMS 가 아니라 NoSQL로 남기는 것도 좋다.
   * **컬럼의 의미를 바꾸지 말 것.** DB의 역사가 오래될 수록 컬럼의 의미를 중간에 바꾸는 경우가 있는데, **컬럼의 의미를 변경하려면 기존 데이터를 모두 마이그레이션 하던지, 새로운 컬럼을 만들어서 새로운 의미를 부여하든지 한다.** 기존 데이터를 그대로 남겨둔 상태로 컬럼의 의미를 바꾸면 매핑되는 객체 구조 설계에도 문제가 생기고 쿼리 결과를 처리하는 모든 구문에 상황에 따른 조건문이 계속 추가되어 개발 부담을 가중시키게 된다.   * **컬럼의 의미를 바꾸지 말 것.** DB의 역사가 오래될 수록 컬럼의 의미를 중간에 바꾸는 경우가 있는데, **컬럼의 의미를 변경하려면 기존 데이터를 모두 마이그레이션 하던지, 새로운 컬럼을 만들어서 새로운 의미를 부여하든지 한다.** 기존 데이터를 그대로 남겨둔 상태로 컬럼의 의미를 바꾸면 매핑되는 객체 구조 설계에도 문제가 생기고 쿼리 결과를 처리하는 모든 구문에 상황에 따른 조건문이 계속 추가되어 개발 부담을 가중시키게 된다.
-  * DB 자체의 타입 enum 을 사용하지 말라. 또한 프로그래밍 언어 enum 을 사용할 때 절대로 순서 숫자값(ordinal)로 저장하면 안된다. 이 둘은 모두 enum 항목들의 순서 변경이 발생하는 순간 엄청난 마이그레이션을 수행해야한다. 그에 비해 성능 향상은 그리 크지 않아보인다.+  * DB 자체의 타입 enum 을 사용하지 말라. 또한 프로그래밍 언어 enum 을 사용할 때 절대로 순서 숫자값(ordinal)로 저장하면 안된다. 이 둘은 모두 enum 항목들의 순서 변경이 발생하는 순간 엄청난 마이그레이션을 수행해야한다. 그에 비해 성능 향상은 그리 크지 않아보인다. MySQL의 enum은 겉보기와는 달리 ordinal 로 작동하기 때문에 **enum 의 순서가 변경**되면 단순 alter table로 문제가 해결되지 않고 기존 데이터를 모두 마이그레이션 해야 한다.
   * 과도한 동적 쿼리를 생성하지 말라. - 특히 iBatis/MyBatis/문자열 기반으로 쿼리 짤 때 null 을 체크해서 조건문을 넣었다 빼었다 하는 경우   * 과도한 동적 쿼리를 생성하지 말라. - 특히 iBatis/MyBatis/문자열 기반으로 쿼리 짤 때 null 을 체크해서 조건문을 넣었다 빼었다 하는 경우
     * 상태에 따라 WHERE 절이 나타났다 사라졌다하는 식의 동적 쿼리는 자칫 실수하면 모든 조건이 없어져서 엄청난 부하를 일으키는 SELECT 쿼리나 모든 데이터를 삭제하는 DELETE 문이 될 수 있다.     * 상태에 따라 WHERE 절이 나타났다 사라졌다하는 식의 동적 쿼리는 자칫 실수하면 모든 조건이 없어져서 엄청난 부하를 일으키는 SELECT 쿼리나 모든 데이터를 삭제하는 DELETE 문이 될 수 있다.
줄 182: 줄 195:
     * 필요한 쿼리는 항상 상황별로 다 따로 만든다.     * 필요한 쿼리는 항상 상황별로 다 따로 만든다.
   * Backup/Fail Over 전략을 수립해둔다. : DB 생성 초기 부터 백업/복구/FailOver 전략을 각 DB에 맞게 수립해둬야 한다.   * Backup/Fail Over 전략을 수립해둔다. : DB 생성 초기 부터 백업/복구/FailOver 전략을 각 DB에 맞게 수립해둬야 한다.
-  * 페이징으로 데이터를 불한/연속 조회 할 경우에는 항상 **더보기 방식**을 사용한다. API 설계 부분 참조.+  * 페이징으로 데이터를 불한/연속 조회 할 경우에는 항상 **더보기 방식(ID 기반 페이징)**을 사용한다. API 설계 부분 참조.
     * ''offset/limit'' 조회는 ''offset''이 뒤로 갈수록 성능이 계단형으로 느려지게 된다. ''offset''을 계산하려면 그보다 앞에 있는 데이터들의 조건을 모두 검사해야하기 때문이다.     * ''offset/limit'' 조회는 ''offset''이 뒤로 갈수록 성능이 계단형으로 느려지게 된다. ''offset''을 계산하려면 그보다 앞에 있는 데이터들의 조건을 모두 검사해야하기 때문이다.
     * 처리처럼 데이터를 분할해서 가져갈 경우 항상 ''order by PK asc''를 해줘서 페이징의 순서를 명확히 해줘야한다.     * 처리처럼 데이터를 분할해서 가져갈 경우 항상 ''order by PK asc''를 해줘서 페이징의 순서를 명확히 해줘야한다.
     * 대체로 **PK asc**를 하는게 DB에서 가장 성능이 좋다. [[https://www.percona.com/blog/2016/10/20/mysql-8-0-descending-indexes-can-speedup-your-queries/|MySQL 8.0 descending indexes can speedup your queryds]]      * 대체로 **PK asc**를 하는게 DB에서 가장 성능이 좋다. [[https://www.percona.com/blog/2016/10/20/mysql-8-0-descending-indexes-can-speedup-your-queries/|MySQL 8.0 descending indexes can speedup your queryds]] 
 +    * [[https://use-the-index-luke.com/sql/partial-results/fetch-next-page|OFFSET is bad for skipping previous rows]] [[database:seek_method|seek method]] 라고 부름
     * offset/limit 방식(페이징)이 아니라 **''nextPk,limit''**을 조회 조건으로 거는 더보기 방식을 사용해야 성능저하 없이 균일한 응답성을 보장받을 수 있다.     * offset/limit 방식(페이징)이 아니라 **''nextPk,limit''**을 조회 조건으로 거는 더보기 방식을 사용해야 성능저하 없이 균일한 응답성을 보장받을 수 있다.
   * 사용자 ID 같은 경우에는 소문자로통일해서 받는것이 좋다. 대문자로 입력해도 소문자로 변환한다. 사용자는 자신이 대소문자를 어떻게 입력했는지 혼동하는 경우가 많다. 단 email 같은 외부 값을 받는 경우는 있는 그대로 넣어야 한다.   * 사용자 ID 같은 경우에는 소문자로통일해서 받는것이 좋다. 대문자로 입력해도 소문자로 변환한다. 사용자는 자신이 대소문자를 어떻게 입력했는지 혼동하는 경우가 많다. 단 email 같은 외부 값을 받는 경우는 있는 그대로 넣어야 한다.
-  * DB Connection Pool 의 **connectionTimeout** 값(**커넥션풀에서 커넥션을 가져오는데 걸리는 최대시간**)을 2~3초 정도로 짧게 가져간다. 또한 JDBC 사용시, **connectionTimeout**(**실제 DB에 접속하는데 걸리는 시간**)도 1초 이내로 짧게 가져가야한다. 그래야 DB 서버 Fail Over 에 빠르게 반응하게 된다.+  * DB Connection Pool 의 **connectionTimeout** 값(**커넥션풀에서 커넥션을 가져오는데 걸리는 최대시간**)을 2~3초 정도로 짧게 가져간다. 또한 JDBC 사용시, **connectionTimeout**(**실제 DB에 접속하는데 걸리는 시간**)도 1초 이내로 짧게 가져가야한다. 그래야 DB 서버 Fail Over 에 빠르게 반응하게 된다. -> 이 부분은 부하가 증가할 경우 오히려 문제가 될 수도 있다. TODO 좀 더 확인
   * Write 트랜잭션(Transaction)은 최소로 유지해야한다. 만약 write 트랜잭션 내에서 다른 데이터의 과도한 조회 등이 발생한다면 write 를 위한 선행 데이터 조회와 write 그 자체의 트랜잭션을 따로 분할해서  DB Lock 을 줄여야 한다.   * Write 트랜잭션(Transaction)은 최소로 유지해야한다. 만약 write 트랜잭션 내에서 다른 데이터의 과도한 조회 등이 발생한다면 write 를 위한 선행 데이터 조회와 write 그 자체의 트랜잭션을 따로 분할해서  DB Lock 을 줄여야 한다.
   * 위의 상황과 유사하지만 비록 순수 Read라 하더라도 트랜잭션을 짧게 가져가야 한다. Long Transaction을 일으키는 큰 문제중의 하나가 DB 트랜잭션 내부에서 다른 API 호출하는 것이다. 외부 API 호출등  DB와 무관하고 긴 시간이 걸리는 행위는 트랜잭션 안에서 하지 말아야한다. API 호출 지연이 발생하면 실제로 DB 작업을 하지 않아도 Connection Pool 이 고갈돼 버려서 API를 호출하지 않는 다른 DB 호출까지 모두 연쇄 지연되는 현상이 발생한다.   * 위의 상황과 유사하지만 비록 순수 Read라 하더라도 트랜잭션을 짧게 가져가야 한다. Long Transaction을 일으키는 큰 문제중의 하나가 DB 트랜잭션 내부에서 다른 API 호출하는 것이다. 외부 API 호출등  DB와 무관하고 긴 시간이 걸리는 행위는 트랜잭션 안에서 하지 말아야한다. API 호출 지연이 발생하면 실제로 DB 작업을 하지 않아도 Connection Pool 이 고갈돼 버려서 API를 호출하지 않는 다른 DB 호출까지 모두 연쇄 지연되는 현상이 발생한다.
줄 196: 줄 210:
     * PK 기반으로 해야 실수 했을 때 어느 PK의 데이터를 되돌려야할지 확인이 쉽고,     * PK 기반으로 해야 실수 했을 때 어느 PK의 데이터를 되돌려야할지 확인이 쉽고,
     * 처음에 쿼리를 짜던 시점과 실제 실행 시점 사이에 뭔가 변경된 데이터가 있을 때 그걸 알아채기도 쉬움.     * 처음에 쿼리를 짜던 시점과 실제 실행 시점 사이에 뭔가 변경된 데이터가 있을 때 그걸 알아채기도 쉬움.
 +    * 또한 롤백시 롤백이 매우 오래걸릴 수도 있다. 한 방쿼리는 **지양**한다.
   * 여러건 조회시 항상 정렬 조건(order by)를 지정해야한다. 정렬을 넣지 않고 어도 PK로 정령돼 있는 경우들이 있는데 이걸 믿고 있다가 나중에 순서가 예상과 다르게 나와서 장애가 나는 경우가 있다(특히 non-clustered index 조회일경우). 가급적 PK기준으로 지정한다.   * 여러건 조회시 항상 정렬 조건(order by)를 지정해야한다. 정렬을 넣지 않고 어도 PK로 정령돼 있는 경우들이 있는데 이걸 믿고 있다가 나중에 순서가 예상과 다르게 나와서 장애가 나는 경우가 있다(특히 non-clustered index 조회일경우). 가급적 PK기준으로 지정한다.
   * 기본적으로 Slow Query 모니터링을 걸고 알람을 한다. slow query 시간에 따라 알람 레벨을 주는 것도 좋을 것 같다.   * 기본적으로 Slow Query 모니터링을 걸고 알람을 한다. slow query 시간에 따라 알람 레벨을 주는 것도 좋을 것 같다.
 +  * ''in'' 조건 등의 쿼리 생성시 항상 ''in'' 안에 들어오는 값의 갯수가 제한될 수 있도록 한다. 
 +    * 이는 bulk insert/update 에서도 마찬가지이다. 
 +    * **SQL 문자열의 길이에 제한이 있다는 사실**을 잊지말고, **SQL 길이가 무제한 커질 수 있는 쿼리가 절대 만들어질수 없게 한다.** 
 +    * ''in''의 경우 [[java:guava|Guava]] [[https://guava.dev/releases/21.0/api/docs/com/google/common/collect/Lists.html#partition-java.util.List-int-|Lists.partition]] 으로 나눠서 조회해서 합치는 방식등을 사용한다.
 +    * 한방 ''in'' 절은 중복을 제거하고 결과를 반환하지만 ''in''의 키를 분리조회 할 경우에는 중복 조회가 발생하지 않게 조심해야 한다. 미리 중복을 제거하고 조회한다.
 +    * 정렬이 필요한 경우가 있다면 이 경우에는 할 수 없이 애플리케이션 코드에서 정렬한다.
 +  * **즉 모든 데이터 조회는 상방이 무제한 커질 수 없게 제한을 걸어야한다. 그래야 서비스의 증가에 따라 장애가 나는 일이 없어진다.**
 +  * Soft Delete(삭제 여부를 나타내는 컬럼을 두고 마킹만 하기) / Hard Delete (실제로 삭제)
 +    * Hard Delete 선호한다.
 +    * unique index 등을 걸기도 편하다.
 +    * 관건은 삭제된 데이터 히스토리를 남기는 것인데, Hard Delete 를 하고 history table 을 두는게 차라리 나아 보인다. [[java:hibernate:envers|Hibernate Envers]] 참조.
 +    * Soft Delete 는 삭제 여부 컬럼을 ''true/false'' 로 둘 수 없고 PK 와 동일 값으로 지정되면 삭제된걸리 간주하는 식으로 해서 unique index 를 걸수도 있긴하지만 너무 복잡하고 개발자들이 자주 실수한다.
 +
 ===== 사용자에게 전달하는 메시징 솔루션 ===== ===== 사용자에게 전달하는 메시징 솔루션 =====
   * Email, SMS, Mobile Push 등의 Messaging은 초반에는 적을 수 있으나 서비스의 증가에 따라 시스템의 부하 요소가 될 수 있다.   * Email, SMS, Mobile Push 등의 Messaging은 초반에는 적을 수 있으나 서비스의 증가에 따라 시스템의 부하 요소가 될 수 있다.
줄 230: 줄 258:
   * Web Load Balancer 의 경우 최초에 서버를 띄운 직후에 분산 weight를 낮게 줬다가 차츰 올리는 설정이 있는 것이 좋아보인다. 서버 띄운 직후에 초기화를 하면서 서버 부담이 지나치게 크게 올라가서 최초 접속자들에게 응답이 매우 늦을 가능성이 보이고 서버 불안이 야기된다.   * Web Load Balancer 의 경우 최초에 서버를 띄운 직후에 분산 weight를 낮게 줬다가 차츰 올리는 설정이 있는 것이 좋아보인다. 서버 띄운 직후에 초기화를 하면서 서버 부담이 지나치게 크게 올라가서 최초 접속자들에게 응답이 매우 늦을 가능성이 보이고 서버 불안이 야기된다.
   * 전체 갯수를 가지고 계산하는 페이징 UI는 말들지 말아야 한다. 결국엔 전체 갯수 계산으로 인해 서비스가 사용할 수 없을 정도로 느려진다. "이전", "이후", "첫페이지", "마지막페이지" 로 가는 버튼만 만들고 전체 갯수는 "마지막페이지" 버튼을 눌렀을 때만 계산하고 다른때는 전혀 신경쓰지 않게 처리한다.   * 전체 갯수를 가지고 계산하는 페이징 UI는 말들지 말아야 한다. 결국엔 전체 갯수 계산으로 인해 서비스가 사용할 수 없을 정도로 느려진다. "이전", "이후", "첫페이지", "마지막페이지" 로 가는 버튼만 만들고 전체 갯수는 "마지막페이지" 버튼을 눌렀을 때만 계산하고 다른때는 전혀 신경쓰지 않게 처리한다.
 +  * 화면에 보이는 것만 로딩하고 순차 로딩/페이징(더보기 방식)하는 형태로 만들어야 한다. 화면에 나오지도 않는 데이터를 한 번에 다 로딩하는 방식으로 개발을 시작할 경우 추후 성능 저하 대응이 매우 어렵다. 특히 모바일 앱의 경우에 개선이 쉽지 않다.
  
 ===== Excel Download / 통계 제공 / bulk 작업 ===== ===== Excel Download / 통계 제공 / bulk 작업 =====
줄 238: 줄 267:
   * 이런 작업은 항상 batch 를 통해 비동기로 처리하도록 한다.   * 이런 작업은 항상 batch 를 통해 비동기로 처리하도록 한다.
  
-===== 캐시 =====+===== Backend 캐시 =====
   * Local 캐시는 변화가 적고, 일시적인 값 Mismatch는 상관 없고 성능이 더 중요할 때   * Local 캐시는 변화가 적고, 일시적인 값 Mismatch는 상관 없고 성능이 더 중요할 때
   * 분산 캐시를 사용하면 성능은 떨어지만 ''expire''등에 대해 전 서버가 일제히 대응이 가능해져 일관성이 보장 될 수 있다.   * 분산 캐시를 사용하면 성능은 떨어지만 ''expire''등에 대해 전 서버가 일제히 대응이 가능해져 일관성이 보장 될 수 있다.
   * 분산 캐시 사용시에 제일 중요한 것은 내부 네트워크 망의 대역폭(bandwidth)이다. 이를 잘 분산 시킬 수 있어야 한다.   * 분산 캐시 사용시에 제일 중요한 것은 내부 네트워크 망의 대역폭(bandwidth)이다. 이를 잘 분산 시킬 수 있어야 한다.
   * [[:memcached|memcached]]는 데이터 저장 용량 큰 것으로 적은 대수 보다는 **메모리가 작더라도 여러 대로 많이 잘게 배치** 하는 것이 부하 분산 측면과 안정성에서 더 나은 것 같다.   * [[:memcached|memcached]]는 데이터 저장 용량 큰 것으로 적은 대수 보다는 **메모리가 작더라도 여러 대로 많이 잘게 배치** 하는 것이 부하 분산 측면과 안정성에서 더 나은 것 같다.
 +  * **분산 캐시는 value type 이 배포중간에 바뀌는 경우 심각한 오류에 직면할 수 있다.** 이게 중요한 곳에는 분산 캐시를 사용하면 안 된다.
 +  * 분산 캐시의 캐시된 value 에 대한 type 은 DB schema 처럼 매우 중요하게 다뤄야한다. 변경된 캐시 value 타입이 배포되는 동안 에러가 발생하는 현상이 잦기 때문에, 호환성을 항상 염두에 둬야한다.
 +  * [[java:serialization|Java Serialization 직렬화]]으로 분산 캐시 데이터를 저장할 경우 필드 변경, package 변경등의 문제가 지속적으로 발생한다.
 +  * 가급적 JSON 등의 plain 한 방식을 취하고, 필드나 타입이 변경되면 단순히 null 로 처리하고 에러는 안내는 방식을 취해야 한다.
 +    * 하지만 이 경우에도 문제가 있는데, 필드를 null 처리 할 경우 해당 필드를 꼭 사용해야하는 코드가 배포되는 중간에 있을 때 심각한 문제가 발생할 수 있다. 따라서 이 경우에는 cache key 자체를 변경해야 한다.
   * JVM 언어의 경우 memcached 등이 없어도 [[java:ehcache|ehcache]], [[java:infinispan|Infinispan]], [[java:hazelcast|Hazelcast]] 등의 JVM 기반 분산/Replication 캐시를 구축할 수도 있다. 이 경우 Local Cache의 성능 분산 캐시의 일관성이라는 장점을 얻을 수도 있다. 하지만 설정 복잡도가 높아질 것이다.   * JVM 언어의 경우 memcached 등이 없어도 [[java:ehcache|ehcache]], [[java:infinispan|Infinispan]], [[java:hazelcast|Hazelcast]] 등의 JVM 기반 분산/Replication 캐시를 구축할 수도 있다. 이 경우 Local Cache의 성능 분산 캐시의 일관성이라는 장점을 얻을 수도 있다. 하지만 설정 복잡도가 높아질 것이다.
 +    * 애플리케이션 배포가 일어날 때 in memory cache 만 사용하면 복제하는 시간이 들어갈 수도 있다.
 +  * 특정 Cache invalidation 시 해당 cache 에 대한 요청이 여러서버에서 동시에 발생할 경우 원천 데이터(보통 DB)에 대한 트래픽이 폭증할 수 있다. 매우 빈번하게 사용되는 캐시는 invalidation 이 안일어나게 주기적 갱신을 해주는 별도 프로세스를 두는게 좋고, 그렇지 않아면 동시 요청이 오지 않게 할 방안을 마련해야한다.
 +===== Schemaless 저장소 =====
 +  * 분산 캐시(distributed cache), document db, elasticsearch, json 저장소 등 스키마가 없는 저장소에 대해서 코드의 데이터 타입(class 등)을 통해서 스키마를 관리할 때는 데이터 정합성이 코드 배포 흐름에 따라 문제가 안생기는지 항상 확인해야 한다.
  
 ===== 이미지와 정적 리소스 서빙 시스템 image, static resources ===== ===== 이미지와 정적 리소스 서빙 시스템 image, static resources =====
   * CDN 같은 정적 리소스 서빙 시스템을 두자.   * CDN 같은 정적 리소스 서빙 시스템을 두자.
   * CDN이 아니더라도, 별도의 도메인에서 정적 리소스를 통합 서비하는 것이 좋다.   * CDN이 아니더라도, 별도의 도메인에서 정적 리소스를 통합 서비하는 것이 좋다.
 +  * CDN도 이중화 해야한다.
   * JS, CSS 등은 버저닝하여 정적 리소스 서빙 시스템에 미리 배포한다.   * JS, CSS 등은 버저닝하여 정적 리소스 서빙 시스템에 미리 배포한다.
   * 이렇게 해야 리소스 로딩 속도가 조금이라도 빨라지고(깔끔한 HTTP 헤더와 별도 도메인으로 인한 다중 로딩 지원때문), 배포 중간에 발생하는 일시적 CSS, JS mismatch 현상을 조금이나마 줄일 수 있다(Sticky 세션 사용시 정적 리소스 미스매치 현상은 완화 가능할 수도).   * 이렇게 해야 리소스 로딩 속도가 조금이라도 빨라지고(깔끔한 HTTP 헤더와 별도 도메인으로 인한 다중 로딩 지원때문), 배포 중간에 발생하는 일시적 CSS, JS mismatch 현상을 조금이나마 줄일 수 있다(Sticky 세션 사용시 정적 리소스 미스매치 현상은 완화 가능할 수도).
줄 255: 줄 294:
     * 컨텐츠는 시간이 지나면 점점 액세스가 줄어든다.     * 컨텐츠는 시간이 지나면 점점 액세스가 줄어든다.
     * 접근이 적은 썸네을을 삭제하는 방식으로 용량을 줄일 수 있고, 업로드 시간도 확보 가능할 것으로 보임.     * 접근이 적은 썸네을을 삭제하는 방식으로 용량을 줄일 수 있고, 업로드 시간도 확보 가능할 것으로 보임.
 +  * Javascript 등을 캐시하면, 코드를 고쳐 배포해도 사용자가 캐시로 인해 변경된 프로그램을 못 사용한다.
 +    * 따라서 filehashing 혹은 timestamp 등을 파라미터로 붙이는 기법을 각 JS framework 별로 제공해주므로 해당 방법으로 배포시마다 캐시를 무력화해줘야 한다.
 ===== HTML ===== ===== HTML =====
   * HTML과 유사하게 꺽쇠(''<>'') 기반의 커스텀 태그를 사용하는 템플릿 엔진은 사용하지 말라. HTML과 템플릿 코드가 섞여보여서 유지보수성이 현저히 저하된다(JSP, Freemarker 등 쓰지 말라는 얘기).   * HTML과 유사하게 꺽쇠(''<>'') 기반의 커스텀 태그를 사용하는 템플릿 엔진은 사용하지 말라. HTML과 템플릿 코드가 섞여보여서 유지보수성이 현저히 저하된다(JSP, Freemarker 등 쓰지 말라는 얘기).
줄 263: 줄 304:
   * Jade/haml류가 안 된다면 가급적 HTML과 구분되는 템플릿 문법에 HTML의 정합성을 깨지 않는 [[java:template_engine|Java HTML Template Engines]]을 사용한다([[:handlebars|Handlebars.js]], [[java:pebble|Pebble]] 등).   * Jade/haml류가 안 된다면 가급적 HTML과 구분되는 템플릿 문법에 HTML의 정합성을 깨지 않는 [[java:template_engine|Java HTML Template Engines]]을 사용한다([[:handlebars|Handlebars.js]], [[java:pebble|Pebble]] 등).
  
-===== 데이터와 HTML =====+===== 데이터와 View 대한 독립성 - 특히 HTML escape =====
   * 데이터 저장소는 항상 원천 데이터포맷을 명확히 하고 해당 포맷으로만 저장한다.   * 데이터 저장소는 항상 원천 데이터포맷을 명확히 하고 해당 포맷으로만 저장한다.
   * 원천 데이터 포맷은 Text 라면 데이터 저장소에는 그냥 텍스트로 저장해야 한다. 이를 HTML 로 escape 해서 저장하지 말아야 한다.   * 원천 데이터 포맷은 Text 라면 데이터 저장소에는 그냥 텍스트로 저장해야 한다. 이를 HTML 로 escape 해서 저장하지 말아야 한다.
줄 271: 줄 312:
   * 이로 인해 가끔씩 앱이나 일부 웹 애플리케이션에서 ''&lt;, &gt; &amp;'' 같은 문자열들이 노출되게 된다.   * 이로 인해 가끔씩 앱이나 일부 웹 애플리케이션에서 ''&lt;, &gt; &amp;'' 같은 문자열들이 노출되게 된다.
   * HTML escape 의 책임은 데이터 저장소단이 아닌 View 단에서 해야한다.   * HTML escape 의 책임은 데이터 저장소단이 아닌 View 단에서 해야한다.
 +  * **즉, 데이터는 그 데이터의 포맷에 적합하게 저장돼야 하며 view 에 의존적이면 안된다. 데이터 저장은 view 에 대해 독립적이어야 한다.**
 ===== 보안 ===== ===== 보안 =====
   * [[https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project|OWASP Top 10 숙지]] [[http://owasptop10.googlecode.com/files/OWASP%20Top%2010%20-%202010%20Korean.pdf|한국어판]]   * [[https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project|OWASP Top 10 숙지]] [[http://owasptop10.googlecode.com/files/OWASP%20Top%2010%20-%202010%20Korean.pdf|한국어판]]
줄 276: 줄 318:
   * 데이터를 암호화해 저장할 경우, Key를 소스코드에 담지 말고 별도로 담을 수 있도록 한다.   * 데이터를 암호화해 저장할 경우, Key를 소스코드에 담지 말고 별도로 담을 수 있도록 한다.
   * 암호화 Key는 수시로 바뀔 수 있어야 한다. 암호화된 데이터를 저장할 때 Key의 버전도 함께 저장하고, 암호화 코드는 Key 버저닝을 지원해야 한다.   * 암호화 Key는 수시로 바뀔 수 있어야 한다. 암호화된 데이터를 저장할 때 Key의 버전도 함께 저장하고, 암호화 코드는 Key 버저닝을 지원해야 한다.
 +  * 사용자의 비밀번호(password)는 절대로 복호화 불가능한 방식으로 암호화 해야한다.
   * 잦은 이직, 손쉽게 소스를 전달할 수 있는 시스템들(github, ..), 개발자들이 노트북 사용한 원격 근무, 노트북 도난 등 대부분의 회사에서 소스코드를 도난 등 회사의 소스 코드는 회사만의 것이라고 볼 수 없다. 보안을 생각할 때 항상 소스는 노출되어 있다라는 생각으로 해야만한다.   * 잦은 이직, 손쉽게 소스를 전달할 수 있는 시스템들(github, ..), 개발자들이 노트북 사용한 원격 근무, 노트북 도난 등 대부분의 회사에서 소스코드를 도난 등 회사의 소스 코드는 회사만의 것이라고 볼 수 없다. 보안을 생각할 때 항상 소스는 노출되어 있다라는 생각으로 해야만한다.
     * 비밀번호, 암호화 Key, Hash Key 등을 소스에 넣지 말고 원격접속을 통해 어딘가에서 값을 가져오게 한다.     * 비밀번호, 암호화 Key, Hash Key 등을 소스에 넣지 말고 원격접속을 통해 어딘가에서 값을 가져오게 한다.
줄 283: 줄 326:
   * 뭔가를 막아야할 때는 blacklist 보다는 whitelist 방식 권장. blacklist 는 또 다시 막아야할 것이 발견될 가능성이 높다.   * 뭔가를 막아야할 때는 blacklist 보다는 whitelist 방식 권장. blacklist 는 또 다시 막아야할 것이 발견될 가능성이 높다.
     * 문자 필드에 어떤 문자를 사용할 수 없게 해야하는 상황일 경우에도, ''한글,영문 대소문자, 숫자, _, ...'' 식으로 whitelist 를 지정하는게 낫다.     * 문자 필드에 어떤 문자를 사용할 수 없게 해야하는 상황일 경우에도, ''한글,영문 대소문자, 숫자, _, ...'' 식으로 whitelist 를 지정하는게 낫다.
 +  * 인증 토큰(token) 은 변조가 불가하면서 애플리케이션 사용에 필요한 최소한의 데이터를 담도록 한다. [[web:jwt|JWT]] 등을 암호화 하는 방법을 권장함.
 +  * DDoS 공격 방어를 위해 WAF(Web Application Firewall, 웹방화벽) 설정을 해야한다.
 +    * 서비스 런칭 시점부터 하는게 좋다. 나중에 붙이다가 그동안 매우 많이 사용되던 막아서는 안되는 것을 막는 일이 일어나기 쉽다.
 ===== 서버 운영 ===== ===== 서버 운영 =====
   * 절대로 여러 사람이 공유하는 공용 계정으로 서버를 관리하지 말라(AWS 등 포함). 이는 치명적인 보안 사고로 이어진다.   * 절대로 여러 사람이 공유하는 공용 계정으로 서버를 관리하지 말라(AWS 등 포함). 이는 치명적인 보안 사고로 이어진다.
줄 322: 줄 368:
       * 이렇게 해야 ''주문 취소건'' 이라는 검색어로 주문 취소와 관련된 연관 로그를 한 번에 검색 가능해진다.       * 이렇게 해야 ''주문 취소건'' 이라는 검색어로 주문 취소와 관련된 연관 로그를 한 번에 검색 가능해진다.
     * 절대로 예외를 먹지 말것. 정말 특별한 경우가 아니면 항상 예외의 stacktrace까지 모두 남길것. 특별한 경우에는 왜 특별한지 주석 달 것.     * 절대로 예외를 먹지 말것. 정말 특별한 경우가 아니면 항상 예외의 stacktrace까지 모두 남길것. 특별한 경우에는 왜 특별한지 주석 달 것.
 +  * UI 관점에서 사용자 행위 로그를 꼭 남기도록 하고 이를 분석하여 개선 방향을 도출할 수 있도록 한다.
 ===== Production Server ACL ===== ===== Production Server ACL =====
   * 운영 시스템에 대한 ACL은 개발 초기부터 망 분리 등을 통해 운영시스템에서만 접속가능하도록 하고 **절대 개발자 PC, 테스트 시스템 등에서는 접속이 불가능**하도록 구성한다(여기서 말하는 접속은 서버에 대한 SSH 접속이 아니라 DB,Redis,MQ 같은 시스템, API 서버 등에 대한 접속을 뜻한다).   * 운영 시스템에 대한 ACL은 개발 초기부터 망 분리 등을 통해 운영시스템에서만 접속가능하도록 하고 **절대 개발자 PC, 테스트 시스템 등에서는 접속이 불가능**하도록 구성한다(여기서 말하는 접속은 서버에 대한 SSH 접속이 아니라 DB,Redis,MQ 같은 시스템, API 서버 등에 대한 접속을 뜻한다).
줄 373: 줄 419:
   * API 호출 내부에서 다른 API를 호출해서 결과를 합치는 일을 하는 것은 좋지 않다. 내부 호출 API의 장애가 전체적으로 다 전파 돼 버린다.   * API 호출 내부에서 다른 API를 호출해서 결과를 합치는 일을 하는 것은 좋지 않다. 내부 호출 API의 장애가 전체적으로 다 전파 돼 버린다.
     * 최초의 API호출자는 여러 API 호출이 필요하면 다른 API에게 또 다른 API호출을 요청하기 보다는 스스로 모든 요청을 하는게 장애 포인트를 줄여나가는 방법이다.     * 최초의 API호출자는 여러 API 호출이 필요하면 다른 API에게 또 다른 API호출을 요청하기 보다는 스스로 모든 요청을 하는게 장애 포인트를 줄여나가는 방법이다.
-===== MQ 등을 통한 비동기 처리 =====+  * 오류가 나면 오류를 발생시키는게 낫다(''4xx'', ''5xx'' 응답 권장). 
 +    * 오류 발생시 API 응답은 ''200'' 으로 정상으로 내리면서, 응답 데이터에 오류 코드등을 넣게 설계하는 경우가 있는데 
 +    * 이렇게 되면 모든 호출자가 응답에 오류코드가 있는지 일일이 확인해야 한다. 
 +    * 이를 확인하지 않고 데이터를 사용할 경우 에러가 즉각 발생했으면 오히려 문제가 덜 발생했을 것을 더 큰 문제로 커지게 된다. 
 +    * 예를들어 에러코드는 존재하고, 응답 데이터는 empty 로 주는데, 이때 오류를 확인하지 않고 empty 로 온 데이터를 직렬화하면 응답 객체의 필드가 ''null'' 혹은 기본값으로 채워지게 되고, 이를 가지고 정상응답으로 착각하고 나머지 프로세스를 타면 아주 치명적인 문제가 될 수 있다. 
 +  * API Gateway 와 연계하여, 작업중인 API 가 Presentation 계층용도인지 Business 계층 용도인지 명확히 인식하고 이 둘을 하나의 시스템에 섞지 말 것. 
 +  * ''PUT'' 으로 전체 데이터를 수정하는 것은 MSA 에서는 문제가 될 수 있다. 수정 대상 Entity 에 필드 변화가 생겼을 때 이를 호출하는 서비스에서 그 필드 변경을 제대로 인식하지 못하고 ''PUT'' 으로 전체 데이터 수정 요청시 신규 필드의 데이터 유실이 발생할 수 있다. 
 +    * [[https://jsonpatch.com/|JSON Patch]] 혹은 [[https://www.rfc-editor.org/rfc/rfc7386|JSON Merge Patch]] 를 사용하는게 낫다. 
 +    * 필드 변경을 반영한 UI와 Presentation 계층 API가 동시 배포된다면 Presentation 계층에서는 괜찮을 수 있다.  
 +===== MQ 등을 통한 비동기 처리 / event driven =====
   * 비동기 처리는 반응 속도를 높이고 전송 신뢰도를 높여주는 등 좋은 점이 있지만 단점들도 많으므로 확실히 이해해야 한다.   * 비동기 처리는 반응 속도를 높이고 전송 신뢰도를 높여주는 등 좋은 점이 있지만 단점들도 많으므로 확실히 이해해야 한다.
   * 메시지 전송이 실패하는 경우(클라이언트측 버그로 메시지를 먹어버리고 종료되는 경우, 프로듀서는 보냈다고 됐지만 네트워크 단절 등으로 안 가는 경우등이 발생함)에 대비히 철저하게 메시지 재전송이 가능하도록 솔루션을 만들어야 한다.   * 메시지 전송이 실패하는 경우(클라이언트측 버그로 메시지를 먹어버리고 종료되는 경우, 프로듀서는 보냈다고 됐지만 네트워크 단절 등으로 안 가는 경우등이 발생함)에 대비히 철저하게 메시지 재전송이 가능하도록 솔루션을 만들어야 한다.
줄 379: 줄 434:
   * **비동기 타이밍 문제도 심각하다.** 이 경우 비동기를 사용하면 안되는데 비동기를 사용한 경우일 수 있다. 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 커넥션 쓰레드나, 애플리케이션 쓰레드를 점유해버릴 경우 API가 원인 모르게 느려지고 작동을 안하는 현상이 발생한다. 이 둘을 분리해서 장애시 원인이 MQ Consumer 인지, 특정 API 호출인지를 명확히 구분할 수 있다.   * MQ Consumer 서버를 API 서버와 분리한다. MQ Consumer 가 DB 커넥션 쓰레드나, 애플리케이션 쓰레드를 점유해버릴 경우 API가 원인 모르게 느려지고 작동을 안하는 현상이 발생한다. 이 둘을 분리해서 장애시 원인이 MQ Consumer 인지, 특정 API 호출인지를 명확히 구분할 수 있다.
 +  * Event body 에 모든 데이터를 심는 방법은 이벤트 전송 부담을 키운다. 이벤트에는 이벤트를 발생시킨 데이터의 종류(상품,사용자,..)와 해당 Primary Key, 그리고 이벤트 발생 요인(생성,수정,삭제)만 전달하고, 실제 데이터는 이벤트를 받은 시스템에서 API 등으로 호출하여 확인하게 한다. 그리고 상황에 따라 API로 봐야하는 데이터가 너무 크다면 그 중에 어떤 데이터가 변경됐는지(상품중에서 가격이 변경 됐는지 여부?)를 함께 담아 보낸다(zero payload).
 +  * zero payload 상황에서 consumer는 producer 에게 실제 데이터를 API로 요청해야하는데, 이 때 DB replication이 돼 있을 경우 replica 에 데이터 복제 지연이 발생해서 못 읽는 경우가 있을 수 있다. MQ의 전송지연 기능으로 3~5초 정도 지연해서 메시지를 받게 해주면 이 문제가 줄어든다.
 +  * zero payload가 이벤트를 받는측에서 데이터를 조회해야해서 producer 측에 DB부담을 가중시킬 가능성도 있다. 혹은 어떤 상황에서는 이벤트 발행 그 시점의 데이터가 필요할 때도 있다. 따라서 데이터를 넣어서 전송해야 할 일이 있다면 그 정책을 producer가 payload 생성으로 인해 쓰레드가 길어지거나 너무 많은 데이터 조회로  부담되지 않게 잘 정의해줘야 한다.
 ===== Single Page Application? ===== ===== Single Page Application? =====
   * 2020년 현재 대부분의 Front End Framework 이 SPA 기반이다. SPA를 사용하지 말라는 것은 유효하지 않다.   * 2020년 현재 대부분의 Front End Framework 이 SPA 기반이다. SPA를 사용하지 말라는 것은 유효하지 않다.
줄 393: 줄 451:
   * 배치 애플리케이션은 DB 커넥션을 최소로 맺으며 시작하고 필요에 따라 늘려가게 한다. API와는 달리 DB 커넥션 맺는 속도가 문제가 되지 않는다. 오히려 DB 커넥션을 과점유하면 여러 배치 애플리케이션이 동시에 돌 때 문제가 된다.   * 배치 애플리케이션은 DB 커넥션을 최소로 맺으며 시작하고 필요에 따라 늘려가게 한다. API와는 달리 DB 커넥션 맺는 속도가 문제가 되지 않는다. 오히려 DB 커넥션을 과점유하면 여러 배치 애플리케이션이 동시에 돌 때 문제가 된다.
   * DB를 읽을 때 reader/writer 를 올바로 읽는지 테스트를 잘 해야한다. batch 때문에 write node가 불필요한 부담을 동시에 받는일이 생기지 않게 한다.   * DB를 읽을 때 reader/writer 를 올바로 읽는지 테스트를 잘 해야한다. batch 때문에 write node가 불필요한 부담을 동시에 받는일이 생기지 않게 한다.
-  * Batch 가 올바로 시작됐는지, 혹은 올바로 종료됐는지를 모니터링 한다. **시작** 모니터링도 매우 중요할 수 있다. 시작조차 안되면 실패 알람조차도 안오기 때문에 아예 배치가 시작 안됐음을 모르고 며칠이 지나는 경우도 생길 수 있다. [[monitoring:grafana|Grafana]] 혹은 [[logging:kibana|Kibana]] 로 관련 모니터링이 가능하다.+  * Batch 가 올바로 시작됐는지, 혹은 올바로 종료됐는지를 모니터링 한다. **시작** 모니터링도 매우 중요할 수 있다. 시작조차 안되면 실패 알람조차도 안오기 때문에 아예 배치가 시작 안됐음을 모르고 며칠이 지나는 경우도 생길 수 있다. [[:batch|Batch / Scheduled / Cron Jobs]][[monitoring:grafana|Grafana]] 혹은 [[logging:kibana|Kibana]] 로 관련 모니터링이 가능하다. 
 +    * 혹은 시작은 됐으나 기존에 10분이면 끝나던 작업이 갑자기 하루 종일 걸리는 현상 
 +    * 누군가가 실수로 batch job 실행시간을 잘못된 시간으로 옮겨버리는 행위 등을 감지할 수 있어야 한다.
   * 배치 실행 실간을 가정으로 만들지 말 것. 예를들어 앞선 배치가 1시간 걸릴테니 그에 관한 후속 배치는 2시간 이후 실행되게 했는데, 앞선 배치가 2시간이 넘게 걸리는 등의 현상 발생. Job 들간 의존 관계가 있을 경우 명확하게 의존 관계를 코드나 스케줄로 표현할 것.   * 배치 실행 실간을 가정으로 만들지 말 것. 예를들어 앞선 배치가 1시간 걸릴테니 그에 관한 후속 배치는 2시간 이후 실행되게 했는데, 앞선 배치가 2시간이 넘게 걸리는 등의 현상 발생. Job 들간 의존 관계가 있을 경우 명확하게 의존 관계를 코드나 스케줄로 표현할 것.
 ===== 기타 ===== ===== 기타 =====
줄 401: 줄 461:
   * 네트워크 연결 되는 설정의 경우 **Connection Timeout과 Read Timeout** 두 가지가 존재한다. 항상 이 둘을 적절히 설정해야 한다.   * 네트워크 연결 되는 설정의 경우 **Connection Timeout과 Read Timeout** 두 가지가 존재한다. 항상 이 둘을 적절히 설정해야 한다.
   * UI 에 페이징을 사용하지 않는 것이 좋다. 페이징은 전체 결과 갯수를 필요로 하는데 이로 인해 DB 부하가 매우 크다. [[search:elasticsearch|Elastic Search]] 같은 검색엔진 기반이라면 써도 된다. 하지만 DB 기반으로 하는 대부분의 애플리케이션은 페이징 UI 말고 offset/limit 방식을 사용해서 페이징을 구현한다. 안그러면 서비스가 성장했을 때 UI가 사용이 불가능할 정도로 느려진다.   * UI 에 페이징을 사용하지 않는 것이 좋다. 페이징은 전체 결과 갯수를 필요로 하는데 이로 인해 DB 부하가 매우 크다. [[search:elasticsearch|Elastic Search]] 같은 검색엔진 기반이라면 써도 된다. 하지만 DB 기반으로 하는 대부분의 애플리케이션은 페이징 UI 말고 offset/limit 방식을 사용해서 페이징을 구현한다. 안그러면 서비스가 성장했을 때 UI가 사용이 불가능할 정도로 느려진다.
 +  * 현재 시간(Java 에서는 ''LocalDateTime.now()'' 같은 것을 하나의 비즈니스 로직 흐름에서 여러번 호출해서는 안된다. 호출 순서에 따라 시분초 중이 시가 바뀌가나 아예 날짜가 바뀌어버릴 수 있다. 하나의 비즈니스트랜잭션에서는 이 값을 로직의 최초 시작점 혹은 호출자측에서 현재시간을 구해서 인자로 넘겨주고 그 후속 로직은 모두 이 인자를 사용해야 한다. 그렇지 않으면 하나의 트랜잭션안에 현재시간을 나타내는 값이 모두 다르게 들어가서 장애가 날 수 있다.
 +===== 부하 예상상황 =====
 +  * 대규모 포털 광고나 서비스에 영향을 주는 이벤트 일정을 항상 공유하고 그에 대해 대비돼 있는지 체크리스트를 확인하는 절차를 가지고 있어야 한다.
 +  * 부하 상황에서는 서비스에 크리티컬하지 않은 기능을 일시적으로 꺼버리는 옵션을 두는 것이 좋다. 즉, 서비스의 핵심 기능만 작동하고 불필요하게 부하를 일으키는 부가 기능을 꺼버려서 대응할 수 있게 하는게 좋다.
  
 ===== 전사 가이드 ===== ===== 전사 가이드 =====
줄 416: 줄 480:
   * Sprint 를 1주 단위 정도로 잘게 쪼개는 게 좋다. 목표를 명확히 가시화 한다.   * Sprint 를 1주 단위 정도로 잘게 쪼개는 게 좋다. 목표를 명확히 가시화 한다.
   * 스프린트당 통합 테스트를 목표로 정하는게 좋다. 허접해도 통합해서 뭔가를 보는게 좋다.   * 스프린트당 통합 테스트를 목표로 정하는게 좋다. 허접해도 통합해서 뭔가를 보는게 좋다.
 +
 +===== 외부 연결 정보와 인증서 관리 =====
 +  * 외부 API 연동 Key 혹은 HTTPS 인증서 등의 목록을 면밀히 관리해야 한다.
 +  * 특히 인증서 자체의 숫자가 늘어나서 관리가 안 될 수 있기 때문에 소스코드에 API Key 등을 두지 말고, 특정 저장소에서 일관되게 사용할 수 있게 해야 한다.
 +  * HTTPS SSL 인증서의 경우 만료일 관리 스케줄링을 하고, 관리 주체를 명확히 가져가도록 한다.
 +
  
 ===== 신규 개발 조직 구축시 먼저 할 일 ===== ===== 신규 개발 조직 구축시 먼저 할 일 =====
 +  * [[:aws|AWS]], [[ci:jenkins|Jenkins]] 등 써드파티 솔루션의 인증과 사내 인증([[:ldap|ldap]] 등)을 자연스럽게 연동 해 줄 수 있는 [[web:oauth|OAuth]] 서비스나 기타 [[authentication:sso|SSO - Single Sign On]] 솔루션을 확보한다. 
   * [[https://www.sonatype.com/nexus|nexus]] 같은 의존성 저장소를 구축한다.   * [[https://www.sonatype.com/nexus|nexus]] 같은 의존성 저장소를 구축한다.
     * 사내 라이브러리 올리기     * 사내 라이브러리 올리기
줄 425: 줄 496:
     * Github Enterprise     * Github Enterprise
     * Gitlab Enterprise 등.     * Gitlab Enterprise 등.
-  * 이유 : 공개 저장소를 사용하면 항상 보안 문제가 발생한다. 특히 개발자 한명의 실수로 전체 소스코드 유출이 가능해진다. +    * 이유 : 공개 저장소를 사용하면 항상 보안 문제가 발생한다. 특히 개발자 한명의 실수로 전체 소스코드 유출이 가능해진다. 
-  * 무것보다 소스코드에 DB 접속 정보, 개인정보 등이 있을 때 큰문제가 된다. +    * 무것보다 소스코드에 DB 접속 정보, 개인정보 등이 있을 때 큰문제가 된다. 
-  * 근본적으로 어딘가의 접속 정보는 소스코드 저장소에 넣지 않는게 제일 좋지만 이걸 안지키는 경우가 지속적으로 발생한다.+    * 근본적으로 어딘가의 접속 정보는 소스코드 저장소에 넣지 않는게 제일 좋지만 이걸 안지키는 경우가 지속적으로 발생한다
 +  * Branch 전략을 수립한다. gitflow 는 사용하지 말 것. 이건 백포팅이 필요한 package software/library 용 전략임.
   * Local 개발 환경을 구성한다.   * Local 개발 환경을 구성한다.
     * 개발자가 개발시에 여러 팀에 함께 테스트(통합 테스트 환경)하는 DB 등의 리소스를 사용하지 않게 해야, 다른 팀의 개발환경을 깨뜨리지 않고 안정적이고 빠르게 개발할 수 있다.     * 개발자가 개발시에 여러 팀에 함께 테스트(통합 테스트 환경)하는 DB 등의 리소스를 사용하지 않게 해야, 다른 팀의 개발환경을 깨뜨리지 않고 안정적이고 빠르게 개발할 수 있다.
줄 433: 줄 505:
     * [[:docker|Docker]], [[aws:localstack|LocalStack]], [[devops:vagrant|Vagrant]] 등을 활용하여 개발자 전용 개발 환경을 만들어주고     * [[:docker|Docker]], [[aws:localstack|LocalStack]], [[devops:vagrant|Vagrant]] 등을 활용하여 개발자 전용 개발 환경을 만들어주고
     * [[java:database:migration:flyway|Flyway Java Database Migration]], [[java:database:migration:liquibase|Liquibase]], [[devops:terraform|Terraform]], [[devops:ansible|Ansible]]같은 Infrastructure As Code 툴로 운영환경을 Local 에서 복구하는 것이 자동화 돼 있어야 한다.     * [[java:database:migration:flyway|Flyway Java Database Migration]], [[java:database:migration:liquibase|Liquibase]], [[devops:terraform|Terraform]], [[devops:ansible|Ansible]]같은 Infrastructure As Code 툴로 운영환경을 Local 에서 복구하는 것이 자동화 돼 있어야 한다.
-  * 기술 선언서를 만든다. +  * 개발 시간도 아니면서 개발 시간이 부족하게 만드는 요소들(배포 복잡도로 인한 배포 시간이 너무 오래걸린다던가, 매우 반복적인 통계 생성 요청이나 기타 운영 요청을 받아주느라 개발을 못한다던가)이 있는지 확인하고 개선하여 개발 시간을 확보하고 또한 이를 계속 반복 확인한다. 
-    * 장애대응 방법과 태도(비난하지 말고 함께 문제를 해결한다던가)+  * 최대한 IasC 로 작업한다. 
 +  * 기술 선언서 / 개발조직 Ground Rule 을 만든다. 
 +    * 장애대응 방법과 태도(장애를 막기보다는 관리한다, 비난하지 말고 함께 문제를 해결한다던가)
     * 테스트 커버리지     * 테스트 커버리지
     * CI 필요성     * CI 필요성
     * 문서화 수준 요구사항..     * 문서화 수준 요구사항..
     * 지금 이 문서 같은 것을 해당 조직의 기술에 맞게 정리     * 지금 이 문서 같은 것을 해당 조직의 기술에 맞게 정리
 +    * false alarm 에 철저히 대응한다. false alarm 이 많아지면 정작 중요한 alarm 을 놓치게 된다.
     * 최소한의 기술 요구사항등을 정리해 둔다.     * 최소한의 기술 요구사항등을 정리해 둔다.
       * 프로젝트 구성       * 프로젝트 구성
줄 445: 줄 520:
       * Test 방법       * Test 방법
     * 그리고 지속적인 회고로 기술 선언서를 개선한다.     * 그리고 지속적인 회고로 기술 선언서를 개선한다.
 +    * 모든 Rule 에는 그것이 추구하는 가치(이유)를 함께 명시해야한다.
 +      * 변화의 흐름에 따라 규칙이 오히려 생산성을 저해시키는 경우가 생기는데
 +      * 그럴 때 왜 이런 규칙이 생겼는지 그 규칙이 추구하는 가치가 무엇인지를 확인하고 규칙을 변경할 수 있어야 한다.
 +
 +
 +===== git push force 금지 =====
 +  * ''git rebase'' 하는 습관이 있는 개발자들로 인해 지속적인 conflict 가 발생한다.
 +  * conflict 발생은 그 자체로 개발 시간을 잡아먹는 요소이고
 +  * 무엇보다 문제는 conflict 해결중에 잘못 해결해서 소스코드가 사라지거나 꼬여버리는 문제로 버그가 양산된다.
 +  * 아예 rebase 를 금지시키고, rebase 시에 하게 되는 push force도 금지시킨다.
 +  * rebase 대신 리포지토리 fork 후 개발이 끝난 것을 **squash merge request** 방식으로 해소하도록 한다. 작업 완료 후 fork 한 리포지토리는 무조건 삭제하고 그걸 받았던 local 리포지토리도 삭제한다.
  
 ===== 지속적인 업그레이드 ===== ===== 지속적인 업그레이드 =====
web/신규서비스.1635296971.txt.gz · 마지막으로 수정됨: 2021/10/27 10:09 저자 kwon37xi