회고록, 일기, 후기

최근 사내 파일럿 프로젝트를 통해 경험한 기술적 문제들....

joohyukkim 2022. 8. 6. 23:37

 

입사 후 3주간 진행한 프로젝트 회고

 

주요 사항

 

> 데이터는 1000만건이 들어있어 야한다.

> 페이징

> 조회수 기능 추가

> OAuth2.0, 인증, etc…

 

나는 무엇을 고민했는가…?

 

조회 방법을 고민

내가 작성한 코드가 실제로 어떤 일들을 수행하는지를 알아야만 했다. 

  1. Covering Index 와 QueryDSL 기반의 명시적인 sql statement 수행
  2. general_log 테이블 활용 방법 알아내서 내가 어떤 쿼리를 발생시키는지 DB 레벨에서 직접 확인하기
  3. 스프링의 Transactional을 적용했을떄 어떻게 트랜잭션을 적용하는지 (SET AUTOCOMMIT =…..)

 

#1 대댓글 게시판 : 글과 답글에 대한 CRUD (depth 1)

 

성능이 나와야하기 때문에 조회방법을 고민하게된다.

어떤 데이터를 만들어내고 저장하고, 연관관계를 설정하고 저장을 할지

연관관계,삭제여부, 댓글과 대댓글의 관계를 표현하기 위해 어떻게 응답을 줄지, 또는 저장을 할지고민하고 스레드 <- 글의 관계를 선택했다.

 

나는 인덱스를 사용했다. 저장되는 자료구조 (nested. set vs RDB row) 를 생각하고 관계형 데이터베이스에 부합하는 구조로 데이터를 적재시켰다.

연관관계도 느슨하게, depth 1로 잡고 진행함 

 

#2 OAuth 2.0 기반 사용자 인증 방식

 

사용자테이블을 OAuth2.0에서 관리하게 구성해놓은 상태

api서버는 요청에 담겨있는 토큰만가지고 사용자를 판단하게 구성하게한다.

인증서버에, 특히 인증서버가 연결하는 데이터베이스에 추가적인 부하가 더 발생하는 상황

  • 이런 경우 캐싱을 고려해보게된다. 캐싱을 고려하면 Validation 정책도 생각해야되기떄문에 우선은 단순하게 api서버에서 HTTP 전송하고 나머지 문제들읋 해결하게  구성해놓은상황

 

#3 기본 1000만 건의 데이터를 핸들링

 

1000만건의 데이터는 한번에 저장하는것도 호락호락하지가 않았다. 복잡하지는 않은 엔티티 구조였지만 이마저도 무지성으로 실행하는 INSERT 문에서는 문제가 있더라.

그래서 트랜잭션 꺼놓고 한번에 실행함.

천만건도 연관관계가 완전히 없는 것들이 아니기 때문에 말이지?

 

FK제약조건도 최소화하고 테이블들도 단순화시키고, TEXT 데이터타입 사용하던 것들도 평범한 VARCHAR로 축소시키고 하다보니 밤새도록 인서트가 안되던 1000만건의 데이터가 8분으로 단축됨

 

특히나 트랜잭션과 FK에서 큰 영향을 미친것으로 보임 (InnoDB 기준)

 

#4 페이징 기능 

 

 페이징 기능은 팀에서 기본으로 제공해준 UI를 간절하게 참고했다 ㅋㅋ

 프로젝트의 documents 폴더의 발표자료 열어보면 있음

 내가 구현한게 버튼 기준으로 보면 첫페이지, 다음페이지, 이전페이지, 마지막페이지가 있었음.

 페이지1에서 페이지30만으로 순간이동하는건 뭘해도 시간이 오래걸렸기때문에 현재 페이지의 가장 첫 개시글과 가장 마지막 게시글을 where 절에 넣는걸로 QueryDSL을 작성했더니 옵티마이저가 확실하게

쿼리를 최적화해줌. 

 

내 페이징쿼리는 type이 using index 로 나올 정도이니까 나쁘지 않았다고 볼 수 있음.

첫페이지 마지막페이지모두 같은쿼리를사용하되 정렬기준을 반대로 잡았다.

그리고 게시글 리스트를 리턴할 때 어플리케이션 레벨에서 한번 정렬하고 리턴해줌

 

#5 게시글 조회수 기능

 

게시글 조회수기능은 consistency를 고민할 수 있는 좋은 기회라고 생각한다.

두 가지가 있다 strongly consistent vs eventually consistent

strongly consistent 하려면 db레벨에서 같은 트랜잭션에서 묶는게 좋을것이라는 생각이 들었다.

 

Eventually consistent 하다면 조회수 증가시키는 작업을 별도 메시지 큐나 스레드로 분리시키고 비동기로 실행하는것이 적합할 것이고 말이다.

 

나는 비동기로, 별도의 스레드로 실행하도록 구현했다. 대신 같은 (메인) 쓰레드 풀에서 처리함으로써 성능 모니터링의 편의성을 고려했다.

 

#6 총게시글수확인기능 

 

UI 보다는 페이징 관점에서 총 게시글 수는 STRONGLY CONSISTENT 하게 구현하려고 했다.

이유는

마지막 페이지로 이동하면 버튼이 일정한 갯수로 나오는게 아니라 총 게시글 수에 맞게 나오는 것을 고려했다는 것을 표현하고 싶었음.

그래서 Strongly consistent 이여야만했음.

어플리케이션 레벨에서 Transaction을 잡는것은 요구사항에 만족하지못한다고 생각이 들어서

MariaDB의 TRIGGER 로 delete & Insert 이벤트에 개시글 수를 강제화함. 개발자가 어플리케이션에서 뭔짓을 해도 안되게

추가로 권한을 부여할 수 있다고도 생각함. 다른데서 아무것도 못하게.

 

#7 Soft Delete > 어플리케이션에서 트랜잭션을 활용해서 처리함

 

마스킹이 필수라고 생각을 했다.

트랜잭션에서 게시글.마스킹처리() 실행해서 실제 테이블 로우를 마스킹 처리해버리고

별도 테이블에 게시글 저장함

 

#8 성능테스트 진행 및 시각화

 

F-Lab 프로젝트에서도 사용한 prometheus  + Grafana w조합에 333333333333333333333333

 

#9 보안 (XSS 방어)

 

느낀점

 

책으로 배운 내용들을 실제로 적용하는 경험,인덱스,Real MySQL