10개월이 흘렀다. 많은 것을 배우고 너무 감사한 시간이었다.우테코 전의 삶우테코에 합류하기 전 내 모습을 돌아보면, 개발 경험은 있지만 경쟁력은 없었다. 여러 시행착오를 통해 드디어(?) 개발에 대해서 재미를 느꼈던 시기였다.하지만 단순히 기능 구현에만 초점을 맞추고 깊은 고민을 하지 않았다. 또 기술 논의에 대해 자신감이 없었고 많이 회피했던 것 같기도 하다.우테코동안의 삶우테코에 합격하고 나서 다양한 사람들과 함께 의견을 나누고 고민했다. 우테코 크루들은 페어 프로그래밍 등 기술에 대해 많은 토론을 한다. (초반에는 너무 부담스러웠다ㅋㅋ)여러 번의 미션과 코드 리뷰를 통해 정말 필요한가? 이 기술은 무엇을 위한 기술일까? 내가 작성한 코드는 잘 읽히는가? 변경에 유연한 코드는 무엇인가? 등을 고민할..
마지막 미션 요구사항 중 @DynamicUpdate라는 어노테이션을 설정하는 요구사항이 주어졌다.@DynamicUpdate변경이 일어난 컬럼에 대해서만 update 쿼리를 발생하는 어노테이션이다.우리 도메인 중 하나인 템플릿에는 반정규화를 통해 좋아요 컬럼을 가지고 있다. 좋아요 요청이 오게 될 경우 더티 체킹을 통해 좋아요 컬럼을 업데이트 하는데, 만약 @DynamicUpdate가 없다면 전체 필드에 대해 업데이트 컬럼을 날리게 된다. 하지만 @DynamicUpdate를 추가하게 된다면 변경이 발생한 컬럼에 대해서만 업데이트 쿼리가 발생하여 다음과 같이 쿼리가 나간다. 그렇다면 불필요한 컬럼에 대해 업데이트를 하지 않으면 좋을 것 같다고 생각이 드는데, 과연 어떨까?성능 측면에서 고민할 것스프링의 아버..
쿠폰 미션에서 성능을 위해 캐시를 사용하라는 요구사항을 받았다.캐시를 사용할 때 적용할 수 있는 전략에 대해 알아보고자 작성한다. 읽기 전략Look Aside (Lazy Loading) 전략조회 시 캐시를 먼저 확인하고 없다면 DB에서 조회한다.그 후 조회된 데이터를 캐시에 저장하는 전략이다.public Product getProduct(Long id) { // 1. 캐시 먼저 확인 Coupon coupon = cacheRepository.get(id); if (coupon != null) { return coupon; } // 2. 캐시에 없으면 DB에서 조회 coupon = dbRepository.findById(id); // 3. DB..
Coupon 미션 중 Read, Write DB가 분리된 분산 데이터베이스 환경에서 DataSource를 연결을 했어야 했다.그 과정에서 DataSource 빈 주입을 할 때, LazyConnectionDataSourceProxy라는 객체에 대해 알게 되었는데 해당 객체가 무엇이고, 왜 프록시 객체를 빈 등록해주어야 하는지 알아보고자 글을 작성하게 되었다. @Primary@DependsOn({"routingDataSource"})@Beanpublic DataSource dataSource(DataSource routingDataSource) { return new LazyConnectionDataSourceProxy(routingDataSource);}LazyConnectionDataSourcePr..
인스턴스 한 대 기준으로 핵심기능에 목표 TPS를 정한다. 그리고 안정적으로 서비스될 수있게 아래 값을 설정하고 이유를 공유해야한다. 위 프로젝트 요구사항을 받고 현재 어플리케이션의 TPS가 어떻게 되는지 측정해야했다. 스프링 부트를 통해 프로젝트를 진행하고 기존에 그라파나와 프로메테우스로 이미 메트릭 모니터링 시스템을 구축해놨었기 때문에이를 재활용해서 부하 테스트를 진행하기 전에 TPS를 확인하고자 했다. 관련 메트릭 설정 그라파나에서 TPS를 측정하려면 spring boot actuator 기준 http.server.requests 수집이 활성되어 있어야 한다./actuator/metrics 에 접속하여 활성화되어 있는지 확인하자. 그 중에서도 사용할 매트릭은 http_server_requests_s..
Connection Pool이 없을 때의 문제웹 개발을 하다보면 보통 애플리케이션에서 DB를 사용한다. 하지만 매번 DB가 필요한 요청마다 매번 위의 과정을 거쳐 DB에 연결을 하게 되면 비용이 상당히 많이 든다. DB 연결을 생성하는 과정에서 네트워크 연결, 인증 등의 과정이 필요하다.각 연결은 메모리와 네트워크 리소스를 사용하기 때문에 많은 연결을 만들면 서버 리소스가 빠르게 소모된다.데이터베이스 서버도 각 연결을 관리해야 해서 부하가 증가할 수 있다.연결 생성에 시간이 소요되어 전체 응답 시간이 늘어나고 애플리케이션 성능이 저하된다.DB와의 연결을 위해 생성한 java.sql.Connection 등 연결 객체의 GC 처리가 필요한데, GC 작업이 빈번할 수록 성능 저하가 발생할 가능성이 있다.Con..
가용성이란?먼저 가용성은 무엇일까? 위키피디아에 따르면 서버와 네트워크, 프로그램 등의 정보 시스템이 정상적으로 사용 가능한 정도를 말한다.즉, 사용자가 정상적으로 서비스를 사용할 수 있는 시간이라고 생각해도 된다.공식에서 알 수 있듯이 다운타임이 적을 수록 고가용성에 가까워진다. 왜 가용성은 중요할까?한번 가정을 해보자.만약 배가 고파 음식 주문을 하려고 할 때 사용할 수 있는 2가지 앱이 있다. 하나의 앱은 사용 중에 자주 튕기고 하나의 앱은 그렇지 않는 경우 어떤 서비스를 이용하고 싶은가? 즉 가용성은 사용자가 느끼는 서비스의 신뢰로 이어지고 이는 곧 사용자 손실과 직결될 수 있다.그러면 이렇게 중요한 가용성에 영향을 미치는 것은 무엇일까? 언제 다운타임이 발생하여 가용성이 저하될까 생각해보자. 내..
검색 기능의 성능 이슈로 Full Text Index를 사용하기로 하면서 적용 방법에 대해 알아보았다.Full Text Index는 MySQL 등 DBMS에서 제공하는 전문 검색용 인덱스이다. 하지만 이 기능을 도입하면서 몇몇 문제 상황을 마주쳤는데, 어떻게 해결하였는지 작성하고자 한다. 참고기존 구현은 QueryDSL이 아닌 Specification과 Criteria API를 Like 쿼리로 사용하여 구현이 되어있었다.if (keyword != null && !keyword.trim().isEmpty()) { String likeKeyword = "%" + keyword.trim() + "%"; Predicate titlePredicate = criteriaBuilder.like(root.get("ti..
이전 포스팅에서 쿼리 개선을 통해 검색 API를 개선했다.하지만 여전히 속도가 느렸고 해당 API는 사용자가 회원가입을 하지 않아도 볼 수 있는 유일한 페이지에 사용되기 때문에 이 부분에서 속도가 너무 오래 걸릴 경우 서비스 이탈율이 심각해질 것이라고 생각했다. 기존 구현기존 검색 쿼리는 동적으로 검색 조건 쿼리를 생성하는데, 그중에서도 키워드 조건이 들어올 경우 Like 절을 통해 4가지의 컬럼에 대해 스캔을 한다. 다른 조건은 모두 id 값을 기준으로 조회하기 때문에, 인덱스를 탈 수 있지만 Like 절은 전방일치만 인덱스를 타기 탈 수 있다.하지만 우리 요구사항에는 %moly% 처럼 전체 포함된 것을 검색해야 했기에 적절하지 않았다. 실제로 10만건의 템플릿, 30만건의 소스코드의 더미데이터를 넣고..
5차 요구사항은 쿼리 성능 개선이었다.주요 요구사항은 다음과 같았다.핵심 테이블에(핵심 기능에서 read/write 되는 테이블) 대량의 데이터를 쌓고 성능을 개선한다.데이터를 10만/100만건 생성한 뒤, 쿼리 성능을 테스트한다.대량의 데이터에서도 기능이 잘 동작할 수 있도록, 쿼리 성능 개선을 위한 인덱스를 설정한다. 우리 서비스 중 요구사항을 받고 가장 고민이었던 API는 바로 "검색"이었다.데이터의 갯수가 크지 않은 현재 운영 서버에서 평균 API 처리 속도가 10초를 넘어갔었다. 또 100만건의 템플릿 더미데이터를 넣었을 경우에는 평균 26초에서 많게는 35초가 넘어가는 성능을 보였기 때문에 사용성에 있어서 치명적이라고 생각했다. 나 같은 경우에도 사이트 렌더링 속도가 5초 이상 또는 그 이하..