Toss Makers Conference 25 Engineering Day 후기
운좋게도 토스 메이커스 2025 컨퍼런스 엔지니어링 데이에 참석했습니다.
들었던 세션 중 인상깊었던 내용, 들으면서 작성했던 내용들을 공유하고자 합니다.
기억에 남는 것
현장결제 서비스의 분산 트랜잭션 관리학 개론
- 비즈니스 로직 구현 시 “표현 모델”을 작성하는 것
수천 개의 API/BATCH 서버를 하나의 설정 체계로 관리하기
- 수천개의 배치 서버를 추가/관리하기 위해 젠킨스를 극한으로 사용하는 것이 인상적
- job 생성/관리 자동화를 위해 오버레이 아키텍쳐에서 템플릿 패턴까지 발전시켜 휴먼에러를 최소화 해낸 과정
가맹점은 변함없이, 결제창 시스템 전면 재작성하기
- 개선을 위한 코드 분석 시 코드를 뜯어보는 방법 : Entry / Prepare / Confirm => 이런식으로.. 패턴을 관찰해서 공통적으로 드러나는 패턴 분석하기
- 관심사 분리가 아주아주 중요하구나~
당신은 이미 팀을 최적화 하고 있다
- 신뢰의 3요소 : 역량 신뢰(이 사람이 이 일을 잘 해낼 수 있을거란 믿음) / 소통 신뢰(이 사람이 어떤 상황에서도 진심으로, 솔직하게, 맥락있게 소통할거란 믿음) / 계약 신뢰(약속을 지킬 사람이란 믿음)
중앙에서 Data Mesh로: Data Ownership 강화를 위한 변화
- 토스의 데이터 엔지니어링 팀 분리 방식이 신기했음 (DEA/Data Mesh)
레거시 정산 개편기: 신규 시스템 투입 여정부터 대규모 배치 운영 노하우까지
- 한방 쿼리를 분해하는 방법 -> JOIN, UNION ALL 등을 분해해서 문제를 작은 단위로 분해 => 새로운 요구사항 들어왔을 때 요구사항 충족 가능
- 쿼리 의존적인 로직이 아니라 코드 의존적인 로직이 될 수 있도록
- 자주 사용하는 조회 데이터는 조건 기반으로 조회 전용 테이블을 분리하여 성능 개선
- 배치에서도 결국 IO를 최소화 하는게 중요하다
- 외부 API 를 호출하는 경우 무조건 지연이 발생(API 자체의 호출 시간이 짧더라도 네트워크 타면 시간 지연이 걸림)
- 이 경우 API 호출을 병렬처리함
- Thread Safe 하지 않아 중복정산이나 누락 발생하는 경우 방지하기 때문에 동기화 적용 && 모듈러 연산으로 id를 나눠 스레드 별로 처리할 데이터를 명확히 나눠줌
- 신규 시스템 투입 방법 : 카나리 투입
- 레거시도 연산 진행하고 신규도 연산 진행
- 레거시 == 신규 -> 신규 적용 / 레거시 != 신규 -> 레거시 적용 && 신규 에러 케이스 로깅
- 안정성 면에서 젠킨스가 뛰어나므로 불편한 점은 젠킨스를 최대한 뜯어고쳐보자
더 빠르고, 더 정교한 이상금융거래탐지 시스템
- 도메인에서 중요하게 여기는 데이터와 FDS에서 중요하게 여기는 데이터 성질의 차이가 발생할 때 => 카프카 이벤트 기반으로 거래 완료 이벤트 발생 시 FDS에서 필요한 데이터 가공하는 방식으로 처리
- 새로운 룰을 추가할 때 일일히 코드로 구현해야 하는게 비효율적 => JSON을 이용한 룰 명세를 해서 Admin에서도 룰을 생성할 수 있도록 함
- 룰 추가 시 Boolean처리 이슈(일부 정보만 가져오는데 성공했을 때, 가져오는데 실패한 데이터가 False로 처리되어 전체 탐지가 안되는 이슈 발생) => Unknown 상태를 추가하여 Unknown 상태값인 룰은 무시하는 방향으로 해결
- 0빼기 이슈 : 휴먼 에러로 임계치 잘못 설정했을 때 => 전체의 xx%를 초과하면 해당 룰을 off
Spring 서버 애플리케이션 구동시간 줄이기
- FlameGraph에서 병목구간 찾아내어 개선해낸것
+ Action Item
- 회사 배치 서버에 오버레이 아키텍쳐 & 템플릿 패턴 적용해서 신규 job 생성 해 보기
- 회사 젠킨스에 확인 후 JOB DSL 어댑터 적용해보기
- 신뢰의 3요소에 맞게 행동하기
- 로그 기반 어뷰징 탐지 배치 만들어보기(어뷰징이면 특정 기능 제한)
- 배치 만들 때 성능 유의해서 만들어보기 : IO 최소화 / 멀티스레딩으로 데이터 병렬처리
- 멀티스레딩 적용 시 모듈러 연산 활용해보기
- FDS 세션 참고해서 Boolean 처리, 과탐방지 로직 적용하기
- 외부 API 호출하는 로직을 병렬적으로 처리할 수 있을지 고민해보기 -> 이걸로 데이터독에서 Flame Graph 보고 Wall-Clock 분석해서 성능 좋아졌는지 확인해보기
- 오래 걸리는 API의 Wall-Clock 분석하기
- 신규 시스템 투입 방법 카나리로 배포할 수 있도록 하기
- 레거시 고친 이후 카나리 투입으로
- 배치 모니터링 도구 적용해보기 (Thread Dump / Async Profiler)
- 이벤트 기반으로 어뷰징 탐지에 필요한 데이터 가공 시도해보기
- 과탐지 자동 모니터링 로직 적용해서 어뷰징 탐지해보기 (전체의 xx% 넘어가면 해당 룰 off)
- 스프링 프로젝트에서 인텔리제이 JFR 확인해서 Timeline wall-clock profiling 확인해보기
- 스프링 프로젝트에 Executable JAR startup 사용중인지 확인해보기
작성한 내용
20년 레거시를 넘어 미래를 준비하는 시스템 만들기
- 레거시 시스템 및 인프라 제약에 영향받지 않는 환경 만들기
- 인프라 -> AWS 사용
- 단계적 시스템 개선
빌드 표준화 -> 신뢰할 수 있는 환경에서 빌드하는게 아니라, 개발자 pc에서 테스트를 하는식으로 함…
- 어느 개발자의 pc던, 어떤 서버던 빌드 가능하게 하고 빌드 결과물 동일하도록 작업합
고가용성 인프라 구성
- MSA로 전환하고 쿠버네티스 도입
- 쿠버네티스로 초 고가용성 인프라 구현
자동화된 코드 업데이트 체계 확보
- 표준화와 자동화가 중요함
- 수백여개에 대한 MSA를 동일하게 처리하기 위해서는..
- 로깅과 모니터링같이 전사에 작용해야 하는 코드는 Common Library에 적용함.
- 문제가 되는 코드 패턴이 보이면 pr을 올리는… 최신버전 라이브러리가 빠르게 적용되는걸 가능하게 함
- JVM 50% 이상의 서비스가 java 17,21 버전 사용하는것에도 영향 -> 다음 업데이트도 빠르게 적용 가능
서비스 안정성 및 성능 고도화
- 서비스 트래픽 가능하도록 함(카나리 방식)
- 트래픽을 새로운 버전에 조금씩 흘려보냄
- 1%단위로 트래픽을 조절해서 보내는게 가능합
- 성능 최적화
대규모 데이터 조회 기술 확보
- 쿼리 결과가 1분에서 1초 이내로, 3개월 데이터만 조회 가능하던게 최근 5년 내 데이터 조회 가능하도록함
- 검색 기간 제어 없이 빠른 데이터 조회가 가능하도록 함
- 긴 범위에 대한 데이터 조회 니즈가 상당히 많음 -> 개선으로 가맹점에서 며칠씩 걸리던게 몇분이면 끝남
보안 강화
- 경계 / 내부망 / 업무망 / 런타임 / 컨테이너 보안
- 서버 호스트들의 보안성을 강화
어플리케이션 개편
- 어플리케이션 레이어 대규모 개편
- 2004 년의 struts 1.2 를 boot3 + kotlin + react
- 쿼리중심 -> 로직중심
- OnPrem, -> 클라우드
현장결제 서비스의 분산 트랜잭션 관리학 개론
가맹점 -> 결제서버 -> (송금서버, 포인트서버, 프로모션 서버)
1
2
3
4
5
6
/**
* Task A = Transaction A + Transaction B
*/
fun taskA {
}
- transaction 하나를 API call 이라고 했을때
- API 호출 결과를 단정할 수 없는 경우 -> Read Timeout, 네트워크 유실, 정의되지 않은 에러
- 성공 / 실패 / 알 수 없음까지 고려해야함. => 사실 4개 (알 수 없음 -> 성공/실패)
- 트랜잭션이 하나씩 더 추가될수록… 트랜잭션간 불일치 상태에 따라 무수한 경우의 수가 발생함
문제 해결을 위해 필요한 것
- 복잡한 상황을 나타낼 수 있는 “표현모델”
- 상황을 트리모델로 표현 -> 여기에 상태 표현
- 결제 정합성을 성공 또는 실패로 맞추는 방법 => 보정 방안(성공, 실패)을 결정하는 주체가 필요함 => 보정 정책도 각 노드가 맞춰야 함. 보정 정책을 가지고 있으면 leaf-> root를 맞출 수 있다.
- 각각의 노드는 사가 오케스트레이터와 다르게 이중 보정 전략을 가지고 있음
언제까지 일관성이 유지되어야 할까 -> 분산 환경에서는 최종적인 일관성을 유지하는게 필연적
- 복잡한 상황을 나타낼 수 있는 표현모델 : 트리구조 / 일관성은 어떻게 보장? : 각 노드가 / 일관성을 언제까지 보장해야할까 : ASAP이지만 최종적 일관성 추구
다양한 정책
- 정책 수립 시 고려해야 할 점 -> 결제 수단별 심리적 가치 차이 / 보상 트랜잭션 난이도(입금이 출금보다 쉬움) / 결제 수단별 처리 난의도(내부 서비스가 외부보다 쉬움 등…)
- 결제수단 승일/환불 순서
- 각 결제수단의 필수 성공 여부
- UNKNOWN 상태의 전파 바운더리 설정
- 금원이동 서버와 결제 서비스의 분리
- api 트랜잭션 내에서의 최소한의 보상 트랜잭션 실행
수천 개의 API/BATCH 서버를 하나의 설정 체계로 관리하기
하나의 설정 체게로 수천개의 실시간 API서버와 배치서버를 다룬다 -> 설정 하나로 문제를 해결한 과정
API 설정
- 개발자의 요구사항
- 모든 서버에 공통 환경변수를 넣고싶다
- 특정 서버에서만 Java Heap 메모리를 올려달라
- 특정 빌드에서만 스크립트를 실행하고 싶다
초기에는 복붙으로 해결했지만 한계가 있음…
실시간 API 설정의 두가지 축
- 오버레이 아키텍쳐 : 설정을 계층으로 나누고, 적용하는 방식.
- 오버레이가 만능은 아님!! -> 값이 문자열인 경우나 긴 경우.. 중복이 발생함
- 템플릿 패턴
- 중괄호를 활용해서 값을 끠워넣을 수 있도록 함!!
- 특정 설정을 조건부로 줄 수도 있도록 함
모든 요구사항을 만족시킬 수 있는 구조
- 핵심 : 계속 진화할 수 있는 구조
- 복붙 -> 오버레이 아키텍쳐 -> 템플릿패턴 -> (그 이후는?)
배치 서버
- 단순할수록 안전하다 -> 여러 배치 솔루션을 검토했지만 젠킨스를 선택
- 완벽하지는 않지만 페이먼츠에서 필요한 배치 프로비저닝에 적절함.
- 스크립트도 잡마다 조금씩 다르고, 자바 경로나 환경구조도 어렵고… -> 이걸 개선함
- 워커에 몇 개의 자바 프로세스를 다뤄야 하냐~ 메모리 이슈로 배치 비정상 종료되는 것도
JOB DSL 플러그인 어댑터
- 반복 설정 줄여줌
- 젠킨스 오픈소스 플러그인 => 플러그인을 한 번 감싸서 개발자가 사용하기 편하게 개선함.
- 어댑터 : 빌더패턴으로 만든 groovy 코드
- 배치에 기대하는 설정을 하나의 파일에 설정 -> 반복, 조건문을 통해 설정 찝어낼 수 있도록도함
- 실행 / 빌드 / 배포 / 젠킨스 파이프라인 실행 / shell 커맨드 실행 등 모든 커멘드를 빌더패턴으로 만들어줌
Dynamic Provisioning 어댑터
- 메모리 부족 현상을 줄여줌
- 어떤 노드에 몇 개의 자바 프로세스를 띄울지 애매함..
- 전용 노드를 붙여주면 해결할 수는 있지만 비용 문제가 있기 때문에, 실행 요청에 따라 자동으로 늘어나고 줄어드는 배치 컴퓨팅 리소스를 만들어줌
- 필요한 경우 배치를 동적으로 늘려주는 방식으로….
핵심 : 계속 진화할 수 있는 구조
- 복붙 -> JOB-DSL -> Dynamic Provisioning -> (그 이후는?)
설정도 일종의 소프트웨어다
가맹점은 변함없이, 결제창 시스템 전면 재작성하기
- 결제창 레거시 -> 절차지향적… 코드 끼워서 분기처리 해주는 방식. 도일한 코드가 재작성됨
- 개발생산성이 너무 낮음. 웹로직과 JSP… 레거시에 대한 학습비용
- 백엔드가 강결합되어 -> 직접 커넥션 받아오고 쿼리 로직.. 화면 로직이 다 같이 개발되어 있었음
- 20년된 레거시 파라미터 연동 방식 & OpenAPI 파라미터 연동방식
- 레거시 파라미터 : flat한 형태… 사실상 전역변수처럼 사용됨
- 외부 인터페이스와 결제창의 Core로직이 강결합된 형태
개편 전략
- 중복으로 존재하는 설정을 Core로직에 묶어줌
- OpenAPI 파라미터 연동 방식 -> 변환 Adapter -> 20년된 레거시 파라미터 연동 방식 -> 토스페이먼츠 pg
- 불필요한 변환 Adapter, 레거시 파라미터 변환 삭제
- feature 단위로…
단일 JSP 에서 FE-BE가 강결합되어 있는 이슈가 있었음… -> FE와 BE가 서로의 관심사에 맞는 일을 하도록 수정해야함.
- 패턴을 관찰해서 공통적으로 드러나는 3가지 스택을 발견
- Entry : FE가 BE에 데이터를 받아옴
- Prepare : 토스페이에 필요한 데이터를 내려줘
- Confirm : 인증 마친 후 결제 성공으로 이동
프론트엔드는 비즈니스 로직을 모르도록 함. 백엔드는 결제인증에 필요한 통신 방식만 지시해주면 됨. 브라우저는 관심이 없음. 카드사 / FE / BE 가 분리됨
당신은 이미 팀을 최적화하고있다
위임, 분배, 책임감
- 할 일을 주변에 위임해라 -> 단순히 할 일을 쪼개서 주는게 아니다…
신뢰의 3요소
- 역량 신뢰(이 사람이 이 일을 잘 해낼 수 있을거란 믿음)
- 소통 신뢰(이 사람이 어떤 상황에서도 진심으로, 솔직하게, 맥락있게 소통할거란 믿음)
- 계약 신뢰(약속을 지킬 사람이란 믿음)
자율성을 존중하면서 퍼미션 얻기 -> 한 번 기다렸다가 얘기하기… 수용성이 올라감. 사전동의를 얻고 하면 방어력이 떨어짐. “잠시 한 마디 해도 될까요?”
매니징업
- 변화, 한 사람, 버드뮤, 알아주기
- 그 사람의 변화를 알아차려줄 수 있는 상위 리더가 필요함. 미세한 변화를 캐치하고 알려줄 사람..
중앙에서 Data Mesh로: Data Ownership 강화를 위한 변화
DAE(Data Analytics Engineer)
- 적재하는걸 넘어 비즈니스 로직에 맞게 표준화하고 정리하는 역할
Data Mesh
- 각 도메인이 데이터 소유권을 분산화해서 가지는 구조
- 각 도메인이 자기 데이터에 대한 책임을 명확히 하고
- 데이터를 제품으로 생각하고 -> 퀄리티있는 데이터를 책임지고 제공할 수 있도록 함
- 도메인에 국한되지 않는 데이터를 제공함.
- 도메인간 협업을 위해 거버넌스팀이 관리해줘야 함
토스가 중앙집중화된 데이터팀에서 데이터매쉬로 변경된 과정 각 제품에 대한 도메인이 명확해지면서, 각 도메인에 맞는 데이터가 필요….
23년까지의 DA는 하나의 조직에서 여러 업무를 함 -> 업무 범위가 넓다보니 고도화의 한계가 생김
- 팀을 둘로 나눔
- 전사 파이프라인 운영/데이터 툴 개발/ DW지원
- 데이터 마트 구축 마트 팀
- 조직의 데이터 리터러시를 올리고 신뢰할 수 있는 데이터를 제공하자
- 더 많은 리소스를 분석 마트에 할애
- 데이터 문서화에 대한 여러 시도 끝에도 원하는만큼의 데이터 자산화는 쉽지 않은 일..
표준화 마트
- 데이터 사일로화 방지 => 데이터 가시성 향상 및 데이터 maturity 개선
- 서비스의 주요 개념 정의(제품팀과의 협업) -> 기본 개념 마트 생성 -> 지표 기본 마트 생성 -> 문서화 -> 정합성 배치 생성
DW 파이프라인
- 입수단계
- 기본 마트
- 전사 집계
- 서브단계 & 백필
테이블센서
- 디펜던시가 직접 이어져있지 않아도 태스크가 수행될 수 있도록 하는 거…
레거시 정산 개편기: 신규 시스템 투입 여정부터 대규모 배치 운영 노하우까지
오랫동안 정상 기능한 정산 기능을 개편한 이유
- 비즈니스 로직이 코드가 아니라 쿼리에 종속되어 있다
- 거래 한 건을 위해 다양한 기능(환전, 수수료계산, 계약조회 등등) => 기존은 레거시 쿼리를 통해 수많은 도메인이 엮임… UNION ALL, 중첩된 DECODE, CASE WHEN 등을 사용함
- 변경에 대한 영향비용도 거치고 개발 유지보수 비용도 커짐
- -> 쿼리를 분석해서 한방 쿼리를 분해함(분할정복에 집중)
- JOIN, UNION ALL 등을 분해함 -> 분리한 카테고리에서 또 분할하여 문제를 작은 단위로 분해함
- 분할 정복 방식으로 -> 코드 분석하고… 새로운 요구사항이 들어왔을 대에도 요구사항 충족할 수 있도록 개편 가능해짐
- 데이터 모델링의 한계
- 한덩어리의 결과이기 때문에 특정 거래에 대한 계산된 결과를 얻기 어려움 -> 데이터의 최소단위를 확보하게 되어 최소단위 데이터를 조합하여 제공할 수 있게 되어 요구사항에 유연하게 대응할 수 있게 됨
- 개선 결과에 계산 당시의 스냅샷을 저장함
- -> 정상 계산에 대한 재처리를 개선 할 수 있게 되는등, 분할하게 되어 세부적인 대응이 가능하게 됨
- 신규에서 거래마다 처리 상태를 분리하여 이슈가 발생한 단계에 대해 대응이 용이해짐
- 데이터 고해상도 문제 -> 파티셔닝과 인덱스 / 조회 전용 테이블을 명확하게 분리
- => 기존 시스템에서는 집계해서 저장… 같은 조회 요청에 대해 신규 시스템은 실시간 데이터 문제발생(조회 느려지는 문제) 자주 사용하는 조회 조건 기반으로 조회 젼용 테이블을 분리하여 효율적인
- 배치 시스템 성능 문제
- 거레 데이터가 실제 수행까지 실행 시간이 무한히 증가하는 이슈.. 신규 시스템에서는 Spring Batch 기반으로 개편했지만 모든걸 해결해주지는 않음 -> 많은 IO 발생, 단일 스레드 기반 처리로 인한 처리량의 한계…
- 배치를 수행하고 거래를 실행할 때 마다 DB에서 조회하는 비효율적인 이슈 -> 설정 정보를 메모리에 캐시해두고, 설정 정보를 앱에서 조회하여 IO를 줄임
- 처리해야하는 대상 객체를 한 건식 전달받음…
- 대상 객체를 하나의 래퍼클래스에 묶어서 전달하는 구조로 바꿈 -> 아이템 프로세서가 객체를 하나만 전달받아도 여러 건을 전달하는 방식과 같아짐 -> 하나의 bulk로 조회하여 IO를 줄여줌
- 정산 결과를 DB에 저장하는 과정 => IO 처리 횟수를 줄임
- 병렬성 개선 -> 여러 외부 API를 호출해야 하는 경우가 생김… 다만 서로 다른 API에 존재하는 데이터를 조회해야 하다보니 여러 건을 순차처리하면 누적 시간이 수시간의 지연을 발생시킴(건건이 적더라도)
- => API 호출을 병렬적으로 호출하여 시간을 단축시켜줌
- 멀티 스레딩을 적용하여 배치를 병렬처리함 =-> 다만 Thread Safe 하지 않아서 중복정산, 누락 발생 할 수 있음…. => 동기화 적용 다만 동기화를 통해 데이터 읽는 시점에 대기가 발생하여 병렬처리 장점이 사라짐 -> 모듈러 연산을 이용해 각 스레드가 처리할 연산을 명확히 나눠줌 (ID%4=0인 데이터만, 스레드2는 ID%4=1인 데이터만… 스레드별로 처리할 데이터를 명확히 나눠줌)
신규시스템에 확신을 갖는 방법
- 경우의 수? => 테스트 자동화 플랫폼을 적극 활용함. 시스템 내에서 원하는 테스트 방식을 표준화하여 제공. 수만개 이상의 테스트케이스 관리에 용이
- 모든 데이터를 취함하여 테스트 자동화를 함. 테스트 계산 API를 별도로 만들어 계산 모듈을 검증함
신규 시스템 투입 방법
- 고입 지점
- 빠른 시간 안에 이슈 복구가 가능한가
- 문제가 발생해도 충분히 대응할 수 있는 감당 가능한 규모인가
- 배치 레이어에서의 카나리 투입진행
- 레거시 시스템으로도 정산건을 수행하고, 신규에서도 정산건을 수행.
- 정산건이 일치하면 신규를 투입 / 불일치하면 기존 건을 투입(추후 이슈 트래킹) => 적용해볼만 할듯
정산 배치를 어떻게 관리할까요 -> 다량의 정산시스템 모니터링을 어떻게 발전시킬까
- 많은 배치가 흩어져 정의됨 => 관리가 불편하고 장애에 취약함. 서버에 이슈 발생 시 즉각 정상화가 어려움. 배치 실행 정보가 crontab으로 정의되어있기 때문에 관리되지 못하는 상황
- 하드웨어 장애에 취약한 환경에 벗어나자 => AWS K8S CronJob
- AWS 데이터센터간 RTT 이슈로 배치 성능 저하
- K8S CronJob 한계로 정확히 한 번 실행을 보정하지 않아 배치가 중복 실행되는 이슈가 발생
- 배치가 안전하게 실행될 수 있도록 젠킨스 선택
- 젠킨스 : 정산은 미션크리티컬한 배치가 많아… “안정성” 부분에서 젠킨스가 뛰어남
- 다른 배치관리 시스템과 비교했을 때 Stateful한 인프라
- 파이프라인 선언을 통해 배치 워크플로우 정의 가능
- Jenkins Plugin 생태계 -> 원하는 기능 충분히 구현 가능
Dynamic Provisioning
- 배치 실행 장비가 고정적으로 할당하면 같은 시점에 실행되는 배치를 유동적으로 감당하기 어렵고 / 그렇다고 서버 자원을 과도하게 할당하면 서버 자원 낭비가 됨
- 다이나믹 프로그래밍 => 실행 노드를 동적으로 관리 / 필요할 경우 서버 노드를 동적으로 할당받고, 배치 없으면 회수함
- Jenkins 파이프라인 노드 잘못 설정해서 배치가 터지거나, 환경 변수를 일괄로 바꿔야 하는데 Job이 수십개일때….. 파라미터를 하나하나 다 추가해야하거나…
- Job 내에 여러 선언을 일괄 변경하기 번거로움 -> 쏟아지는 비즈니스 요구사항 개발에 허들
- 코드화되어있는 프로젝트로 커밋하면 JOB..
배치 모니터링 도구 강화
- Thread Dump / Async Profiler
- Prometheus / Pinpoint
고객은 절대 기다려주지 않는다: 빠른 데이터 서빙으로 고객 만족도를 수직 상승 시키는 법
MSA 전환 이후에도 DB는 분리하지 못해… 검색에 대한 니즈가 커지자 ES에 색인해서 해결 -> CQRS 도입 (RDB에서 집계 힘든 부분을 아파치 드루이드를 도입)
Apache Druid
- 거래 내역을 시계열. OLAP성 쿼리에 유리
- 개발자가 SQL로 쉽게 개발
- 자동 index로 어느정도 보장되는 성능
- 아키텍쳐
읽기/쓰기가 분리되면서 -> 실시간 매출 확인이 가능해짐…
데이터 조회 비즈니스 요구사항
- 최근 n개월 데이터 조회를 하고싶음
- 특정 키가 아닌 범위 조회를 하고싶음
- 범위 조건 없이 특정 컬럼에 대한 like 검색
- 정렬, 페이징, 실시간 집계 연산 들어가면서 응답속도…
- 테이블 n개에 대한 조인을 원함
=> 조회 성능 저하.. -> 빠른 응답속도 충족이 어려워 효율적인 성능 최적화 전략이 필요함
데이터 결합을 위한 데이터 가공 방식
- CDC
- 메세지 발행 -> 메세지만 잘 발행되면 druid에 전잘하여 성능 보장 가능
아키텍쳐 특징 이용해서 비용, 성능 한번에
- 비싼 EBS 안쓰고 S3를 쓰는것만으로 비용과 고가용성 한번에… 데이터 유실도 없음. 일부는 스팟인스턴스같은 휘발성 인스턴스 이용 가능
- 다만
- 20개가 넘는 원장을 기반으로 과거 기간 역정규화 테이블 만들어야 함
- 멱등성 처리 어려움
- 데이터 파편화
- 아키텍쳐 복잡성
검색 기능
- 검색은 elastic으로 해서 키/파티션을 획득하고 -> 해당 건을 druid에서 조인하는 방식으로 해결
druid에서의 집계 최적화 방식
- roll up : 행 압축으로 조회해야 하는 데이터 스캔이 줄어들어 조회 성능 향상됨 -> 시간 훨씬 줄어들었다~ 최적화 되었다~
더 큰 범위의 통합 원장의 필요성 -> 신규 플랫폼(starrocks)
- 멱득성 문제 해결
- 분석 시스템과의 통일성
- 간결한 아키텍쳐
대용량 테이블 조인 성능 향상을 위한 starrocks 에서의 데이터 저장 방식 -> Colocation Group
더 빠르고, 더 정교한 이상금융거래탐지 시스템
- 거래란? : 뱅킹 서비스를 사용하는 유저의 모든 행동(입출금, 카드결제, 대출 등…)
- 이상거래란? : 정상적인 사용 패턴과 다르게 의심스러운 패턴으로 이루어진 거래 -> 소액 송금만 하다가 갑자기 큰 금액을 이체하거나, 사용하지 않던 곳에서 카드를 사용하거나….
- 이상거래 사례 : 대포통장 개설, 보이스피싱을 통한 출금, 기기 도용 로그인, 불법 대출 수수료 편취, 도용/복제 카드 사용 등….
이상 금융 거라 탐지 시스템(FDS, Fraud Detection System)
- 어떻게 탐지? : 과거에 발생했던 사기 패턴을 분석하여 비슷한 패턴이 감지되었을 시 감지 -> ex: 특정 나이대 이상에서 특정 금액 이상 거래했던 것…
- 수십개의 패턴 중 일치하는 패턴(룰)이 있느가를 형상화 하여 -> 차단
- 여러 데이터를 조합하여 패턴을 감지
기존 FDS 룰 엔진 단점 -> 데이터 추가 시 불편함
- 원인 : 도메인과 FDS간 데이터 성질간 차이점 존재 -> 일반 서버(사용자) 입장에서는 “언제” 결제했는지가 중요했는지 반해, FDS에서는 그 결제가 “어떤” 방식으로 결제했는지가 중요(근데 이건 고객은 안궁금해함) => 이런식으로 FDS와 일반 사용자가 중요하게 생각하는 데이터가 다름
- 새로운 요구사항 : 실시간으로 FDS응답을 받아보고 싶음 -> 거래 시도 시 FDS 검증 요청 -> 이렇게 되면 순환참조 발생할 수 있음( 도메인 서버에 또 FDS에 필요한 데이터 요청해야 하니까….)
- 기존 룰 엔진 활용 시 타임아웃 요건 미부합
- 룰 명세 -> 새로 룰을 추가할 때 마다 일일히 코드로 구현하는게 비효율적…. and or과 같은 방식
- 룰이 기존에는 줄글 형태로 …. -> 조건에 눈에 잘 안보임.. ㅠ
- JSON 을 이용한 룰 명세 => Jankson 이용해서 룰을 설계. Admin으로도 룰을 생성할 수 있도록도 함
룰 엔진 개선한 -> 동기화 방식을 통한 데이터 수집
- 카프카에서 거래 완료 이벤트가 발생하면 FDS에서 필요한 데이터 가공하는 방식으로 진행
- 동기화 시 데이터가 많은 인원에 대해 마킹
- 데이터가 적은 경우 개별 쿼리 호출 / 데이터가 많은 경우 쿼리 분할, 병렬 호출(한번에 많이 요청하면 타임아웃 발생하니까)
- 거래가 많은 유저는 병렬쿼리 보내는 방식을 적용함
- 데이터 가용성/성능이 받쳐주면 가능함
- 신규 룰 엔진 장점 : 데이터 추가 시 용이
- DB 용량이 커지는 문제 -> 오래된 데이터는 clean up 하도록 설정
- 정해진 기간의 데이터만 가지고 있음
- 오래된 데이터는 정말 필요 없을까? => 정교해지는 이상금융 거래들을 잡아낼 수 있을까? => 아님.. 오래된 데이터도 필요… 이상거래와 무관하지 않음.
- Feature Store 서버
운영 이슈
- Boolean 처리 -> 송금 정보는 정상적으로 가져왔는데 유저 정보는 제대로 가져오지 않는다면…(일부 정보만 가져오는데 성공했다면)
- 전체 룰을 수행하지 않거나 / 가져온 정보에 대해서만 판별할 수 있는건 다 판별한다
- 여기서 발생한 문제 : 데이터가 없음 false로 처리 했는데 -> 후자로 해결하려면 룰을 작동시킬 때 무조건 False가 되게 함
- 해결 방법 : Unknown 상태를 추가 -> Unknown이 있으면 해당 상태값을 포함한 룰은 무시함.
- 0빼기 이슈 -> 테스트 환경과 라이브에 휴먼에러로 임계치를 잘못 했을때…
- 휴먼 에러 방지를 위해 과탐지 자동 모니터링을 적용 : 탐지량이 전체의 xx%를 초과하면 해당 룰을 off함
FDS가 나아갈 길
- ML연동….
Spring 서버 애플리케이션 구동시간 줄이기
문제
- 긴 시간 실행되는 배치는 이중화된 DC 환경에서 전환시 문제
- 배포시간 단축으로 생산성 증가
Async-profiler
- 자바 어플리케이션에서 성능 분석하는 샘플링 툴. 성능 이슈 없이 샘플링 가능
- wall-clock profiling : 스레드 상태 상관 없이 모든 애플리케이션 샘플링 가능. 스타트업 타임 분석 가능.
- Agent 방식 / 코드 방식 두 방식 중 하나로 프로파일링 가능
- FlameGraph
- 샘플을 수집할 때 어떤 function이 호출되었는지, 몇 번 호출되었는지, 얼마나 수행시간이 걸렸는지
- Filtering options : 너무 많은 스레드를 보지 않기 위해, 내가 보고싶은 스레드만 살펴볼 수 있음
지연 구간을 확인할 수 있음 -> 스레드 옵션을 제거하고 확인하는게 분석에 도움됨
wall-clock 분석
- refresh global cache 구간에서 많은 시간이 사용되는걸 해당 wall-clock에서 확인함 -> batch에서 불필요한 의존성 제거해서 실행시간 단축
Application Startup tracking by SpringApplication
- 스프링 프레임워크에서도 자체적으로 분석 가능
- JFR 확인 -> 인텔리제이에서 Timeline에서 오래걸린 부분에서 확인 가능
- wall-clock profiling을 intellij에서도 확인 가능함
- 로컬 환경에서도 병목 구간 확인 가능함
개선한걸 실제 프로덕션 반영했을때는 개선 효과가 많이 미미했음
- 로컬에서는 개선했으니 K8S에서 다시 분석하기 -> 띄울 때 에이전트 같이 띄워서 Wall-Clock 확인함
- 이슈 보고
- 문제1 : 3.2.0 업그레이드 후 Executable JAR startup 시간이 매우 느려짐
- 문제2 : static path 하위에 정적리소스 추가 후 startup 시간이 느려짐
- 해당 이슈와 현 환경 비고하여 Executable Jar를 다른 Jar로 변경
- 로컬 환경에서 테스트 했을 때 구동 시간이 훨신 줄어들었음
- 배포 환경에 적용했을때에도 개선 폭이 아주 컸던 걸 확인 가능했음
- 수정 후 확인한 월클락 결과에서도 유의미한 결과
- jar 풀어넣었을 때 docker size 커지지 않는지 -> 미미한 차이라서 무리없이 적용 가능한 최적화였다
- Jar 풀어서 구동시간 줄였으니… Layerd Jar 적용?
Layerd Jar
- 도커 이미지 빌드 효율적으로 도와.
- Jar 파일을 여러 레이어로 나눠 구성 -> 코드가 살짝 바뀌었을 때 전체 이미지 빌드하지 않아도 되어서 빌드 성능이 좋아짐
- JAR를 레이어드로 분리하고 나니까 네트워크 bandwidth가 대폭 줄어들음.
- 어플리케이션 구동시간 뿐만 아니라 배포 시간도 대폭줄일 수 있어서 유의미한 결과
CDS
- (Class Data Sharing) : JDK12부터 지원. 구동시간, 메모리 풋프린트 줄이기 위한 기능.
- 클래스 정보 저장한 jsa 파일 설정을 해 둬야 함 -> 이후 클래스 로딩 정보 확인해서 애플리캐이션 로딩 될 때… 캐시에서 이미 저장된 클래스를 끌오는 방식으로 개선이 됨.
- metaspace 비교해봤을 때 클래스 영역 메모리 사용량 줄어든 것 확인함.