[WIL] 결과가 좋을수록 한 번 더 의심하기
·
루퍼스
이번 주는 상품 목록 기능의 조회 성능을 개선했습니다. 우선, 이 기능을 호출했을 때 SQL이 몇 번 호출되는지를 확인했습니다.목록 한 번 불러오는데 SQL이 22개나 나가고 있었습니다. 이건 그냥 단순하게 쿼리를 개선해서 줄이면 끝나겠다 싶었습니다.그래서 좋아요 개수를 상품마다 따로 세던 걸 한 번에 묶어서 조회했더니 SQL이 3개로 줄었습니다.그럼에도 불구하고.. 제가 목표로 정해두었던 성능(20ms)에는 도달하지 못했습니다.. 조회 성능을 향상시키기 위해 인덱스도 붙였습니다. 이제 빨라지겠지 했지만..? 여전히 평균 응답 시간이 어느 지점(50~60ms)에서 딱 멈추더니 제가 목표로 했던 성능에는 도달하지 못했습니다.. 그 사이에 요상한(?) 일도 있었습니다. 처음에 두 가지 쿼리를 비교해서 느린 ..
SQL 22개를 3개로 줄였는데,왜 평균은 60ms에서 멈췄을까
·
루퍼스
들어가며상품 목록에서 20개를 가져오는 API였습니다. 처음에는 상품마다 좋아요 수를 조회하는 N+1을 없애면 충분히 빨라질 것으로 생각했습니다. 요청당 조회 SQL을 22개에서 3개로 줄였지만, 100 VU 평균 응답 시간은 여전히 수 초였습니다. 인덱스를 추가하자 응답은 60ms 근처까지 내려왔습니다. 그런데 그다음에는 SQL 실행 시간을 줄여도 HTTP 응답이 비슷한 폭으로 줄지 않았습니다. 쿼리 수에 이어 읽는 범위, 커넥션을 기다리는 시간, 캐시가 무효화되는 순간을 나누어 확인해야 했습니다. 이 글은 측정에서 남아 있는 작업을 확인하고, 그 결과에 따라 다음 설계를 선택한 과정을 정리한 기록입니다.캐시 적중 조건에서는 평균 18.5ms를 얻었지만, 좋아요 쓰기를 섞으면 다시 44ms 수준으로 늘..
데이터베이스 인덱스는 왜 빠른가? 색인부터 B+Tree까지
·
루퍼스
TL;DR조회 성능은 반환하는 행 수만으로 알 수 없습니다. 인덱스가 읽은 후보 행, 정렬, 추가 행 조회도 확인해야 합니다.InnoDB에서 세컨더리 인덱스에 없는 컬럼까지 필요하면 PK를 통한 행을 다시 조회할 수 있습니다. 필요한 값이 인덱스에 모두 있으면 이 과정을 줄일 수 있습니다.인덱스는 저장 공간과 쓰기, 갱신 비용도 듭니다. 실행계획과 결과 정확성, 실제 요청 지연까지 함께 확인해야 합니다.들어가며"인덱스를 걸면 빨라진다"는 말은 많이 듣지만, 왜 빨라지는지 설명하려고 하면 막히는 경우가 많았습니다.공부를 시작해도 B+Tree, 팬아웃, 클러스터드 같은 용어부터 해서 원리까지 이해하기가 막막해지기 쉽습니다. 같은 20건을 조회하는데, 왜 인덱스마다 처리 비용이 달라질까요? B+Tree의 기본..
조건부 갱신만으로 충분할까? 브랜드 삭제와 주문 확정에서 락을 고른 기준
·
루퍼스
TL;DR브랜드를 삭제할 때는 Brand를 배타 잠금으로 조회한 뒤 연결 상품과 Brand를 조건부 UPDATE로 논리 삭제했습니다. 상품 등록과 주문 확정은 같은 Brand의 공유락을 얻도록 했습니다.주문 확정에서는 DRAFT 상태, 판매 가능한 상품과 충분한 재고, 결제 가능한 잔액을 각 UPDATE의 조건으로 검사했습니다. 주문 상태 변경과 차감은 하나의 트랜잭션에서 처리했습니다.비관적 락과 조건부 갱신은 함께 사용할 수 있습니다. 어떤 행을 언제 잠그는지, 같은 데이터를 사용하는 다른 기능이 어떤 조건으로 변경하는지를 함께 봤습니다.브랜드를 삭제하는 동안 새 상품이 등록된다면 어디까지 삭제된 것으로 봐야 할까요?주문 두 건이 같은 재고와 잔액을 사용한다면 무엇을 기준으로 성공을 결정해야 할까요?트..
트랜잭션은 어디까지가 DB의 일일까?
·
루퍼스
TL;DRDB 트랜잭션은 하나 이상의 DB 작업을 묶어 함께 커밋하거나 롤백하는 실행 단위다. 원자성은 그 단위가 제공하는 성질이다.ACID는 DB 트랜잭션의 성질이다. 애플리케이션은 원자성으로 함께 반영할 범위를 정하고, 일관성으로 지킬 규칙을 정의하며, 격리성으로 동시 요청을 제어하고, 지속성에 맞춰 커밋 결과에 대해 성공 응답과 재시도를 다룬다.DB 제약은 선언된 데이터 규칙을 강제하고, 애플리케이션은 업무 흐름과 상태 전이 규칙을 구현한다. 동시 요청에서도 규칙이 유지되도록 함께 설계해야 한다.애그리게이트 경계와 DB 트랜잭션 경계를 다르다. 여러 애그리게이트의 변경도 같은 DB에서 즉시 함께 반영되어야 한다면 하나의 트랜잭션으로 묶을 수 있다.DB 트랜잭션은 결제, 이메일, 메시지 발행같은 외부..
재고는 Product 안에, 포인트 잔액은 User와 분리: 애그리게이트 경계를 정한 이유
·
루퍼스
TL;DR 애그리게이트를 객체 관계가 아니라 불변식의 경계로 봤다.`Product` 애그리게이트 내부에서 `StockQuantity`로 재고 규칙을 지키고, `Point` 애그리게이트 내부에서 `PointBalance`로 잔액 규칙을 지키게 했다. 그리고 주문 확정 과정에서 `Order`, `Product`, `Point`의 협력은 `OrderFacade`가 조율하도록 했다.DDD를 적용하면서 가장 크게 달라진 생각은 "어떤 객체가 누구를 참조하는가"보다"어떤 상태를 함께 변경해야 불변식을 지킬 수 있는가?"가 더 중요하다는 점이었다. 재고와 포인트는 모두 숫자로 표현할 수 있다. 하지만 숫자를 Application에서 직접 바꾸면 0 미만 재고, 잔액 부족, 충전 한도 같은 규칙이 호출 지점마다 반복된..
[WIL] 구현하기 전에 무엇을 정해야 할까
·
루퍼스
이번 주에 새롭게 알게 된 것이번 주에는 쿠폰 적용 기능을 구현하지 않았다. 대신 구현하기 전에 무엇이 정해져 있어야 하는지를 계속 정리했다.처음에는 조금 이상했다. 요구사항도 있는데, 쿠폰 도메인을 만들고 할인 계산부터 하면 되는 것 아닌가 싶었다. 그런데 요구사항을 읽을수록 어떻게 구현할지가 아니라, 무엇을 구현해야 하는지가 더 애매했다.구매자는 확정 전에 쿠폰을 적용할 수 있다. 확정된 할인 금액은 유지된다. 문장은 두 개뿐이었는데, 할인 금액이 주문 금액보다 크면 어떻게 되는지, 이미 쿠폰이 있는 주문에 다른 쿠폰을 요청하면 어떻게 되는지, 운영자도 적용할 수 있는지처럼 질문은 계속 나왔다. 이번 주에는 요구사항을 바로 코드로 옮기기 전에, 확인된 사실과 아직 답이 없는 질문을 나누는 일이 먼저라..
모호한 요구사항을 개발자는 어떻게 설계로 바꿀까?
·
루퍼스
모호한 요구사항, 어디까지 개발자가 정해도 될까요?TL;DR모호한 요구사항을 개발자의 추측으로 채우지 않아야 한다.확인된 사실, 실습 장면의 조건, 실제 제품 담당자에게 물을 질문으로 분리한다.불변식은 확정된 사실을 지키는 규칙이지, 아직 답을 받지 못한 제품 정책을 대신 결정하는 수단이 아니다.그 뒤에야 API 계약과 도메인 책임 경계를 선택할 수 있다.들어가며쿠폰 적용 요구사항에 대해 들었을 때, 구현 방법보다 무엇을 구현해야 하는지가 먼저 모호했다."쿠폰을 적용한다"는 문장은 익숙했다. 하지만 막상 설계를 시작하니, 할인 금액은 어디까지 허용해야 하는 거지? 구매자 말고도 쿠폰을 적용할 수 있으려나? 이미 쿠폰이 적용된 주문에 다른 쿠폰을 요청하면 어떻게 해야 하는 거지? 이런 결정되지 않은 부분이..