무지를 아는 것이 곧 앎의 시작

전체 글 41

Hibernate in_clause_parameter_padding 속성을 사용한 메모리 절약

문제 개발하다 보면 무리한 join을 회피하기 위해 in 절 쿼리를 사용해야할 경우가 많다. public interface ExampleRepository extends JpaRepository{ List findByIdIn(List ids); } 위와 같은 코드를 작성했을 때 다음과 같은 쿼리가 호출될 것이다. select ... from example where id in (? ,? ,? ...) 그런데 여기서 보면 ids는 고정된 크기가 아니며, 요청된 갯수에 따라서 쿼리문이 길어질 수 있다. 애초에 RDBMS 자체에서 갯수 제한을 두기도 하거니와 너무 많은 요소가 포함되었을 때 DB에 한번의 요청이 너무 많은 작업을 요구하기 때문에 in 절에 너무 많은 갯수를 넣는건 바람직하지 못하다. 그래서 적..

JPA 2023.12.14

멀티 모듈 프로젝트 세팅하기

멀티 모듈 프로젝트는 왜 세팅할까? A 엔티티가 있고 이걸 다루는 API 서버 어플리케이션이 있다. 근데 마침 같은 A 엔티티를 다루는 배치 어플리케이션의 개발이 필요하다. 별도의 프로젝트로 배치 어플리케이션을 개발하면, 그 배치 어플리케이션 쪽에도 A 엔티티와 관련된 코드를 옮겨줘야 한다. 그리고 이 A엔티티를 다루는 어드민 전용 API 어플리케이션이 또 필요하다. 이것도 별도의 프로젝트로 개발한다면, A 엔티티는 세 군데에 동시에 코드가 존재하게 될 것이다. 마치 아래 그림처럼. 이러한 구조가 되었을 때, 만약 A 엔티티의 요구사항이 변해서 수정이 생기면 어떻게 될까? 각각의 프로젝트에서 각각 A 엔티티에 수정 사항을 반영시켜야 하고, 미처 반영하지 못하고 배포를 나갔을 경우엔 어떠한 문제가 생길지도..

Spring 2023.11.27

분산락 언제 잡고 어떻게 잡는게 좋을까

프로젝트 진행하다 보면 동시성 이슈를 해결해야할 때가 있다. 최근 사이드 프로젝트에 북마크 기능을 구현할 때 어떻게 동시성 이슈를 잡았었고 어떤 고민을 해서 잡는 방법을 채택했는지 기록해볼까 한다. 도메인 간단 이해 일단 도메인 구조를 보면, Place(장소)라는게 있고, 이 장소에 북마크를 찍는개념으로 해서 Bookmark(북마크)가 있고 1(장소):N(북마크)로 되어있다. 그리고 Place 리스트를 조회할 때 bookmark 갯수가 같이 노출돼야하기 때문에 성능 최적화를 위해 bookmarkCount라는 컬럼을 Place가 반정규화해서 들도록 했다. /// imports... /// annotations... public class Place extends AbstractRootEntity { @Id..

CS 2023.10.09

커버링 인덱스와 ICP(index condition pushdown)에 대한 고민

사이드 프로젝트를 할때 특정 검색 기능의 성능이 느려 개선 백로그로 잡아두었었다. 다만, 회사와 기타 일정에 이리저리 치이고 치이다 보니 결국 그 사이드 프젝이 끝날 때까지 건드리지를 못했었는데, 추석 연휴를 맞이해서 갑자기 생각이 났고.. 쉬는 김에 한번 개선해보기로 했다. 개선 시작 검색 성능 개선이 목적이다 보니 해당 쿼리에 인덱스를 태울 생각을 먼저 했고, 기존엔 그 어떤 인덱스도 타고 있지 않던걸 확인했다. 테스트용 DB를 생성해서 다음과 같이 지저분한(..?) 프로시저를 태워 데이터를 적재했다. 테스트용 더미 데이터 insert create table place( -- 테스트 대상 테이블 id bigint auto_increment primary key, name varchar(150) not..

DB 2023.09.30

RedisTemplate는 @Transactional 안에서 어떻게 동작할까?

최근 사이드 프로젝트를 하며 재밌는 사실을 알게 되어 포스팅을 남겨본다. RedisTemplate를 사용할 때 스프링에서 추상화하여 제공해주는 트랜잭션 기능을 사용하고 싶으면 다음과 같은 세팅을 하면 된다. @Configuration @RequiredArgsConstructor @EnableTransactionManagement public class RedisConfig { //... @Bean public RedisTemplate redisTemplate( final RedisConnectionFactory redisConnectionFactory ) { final RedisTemplate redisTemplate = new RedisTemplate(); redisTemplate.setConnecti..

Spring 2023.06.27

Real MySQL 5장 트랜잭션과 잠금 정리

트랜잭션 개념은 위키백과 참조 MySQL에서의 트랜잭션 논리적인 작업 셋 자체가 100% 반영되거나 반영되지 않게 하는 데에 의미가 있다. InnoDB는 트랜잭션을 지원하지만 MyISAM이나 MEMORY에선 지원하지 않는다. MyISAM이나 MEMORY에서 다음과 같은 sql을 시작할 때 어떻게 될까? CREATE TABLE tab_myisam( fdpk INT NOT NULL, PRIMARY KEY (fdpk) ) ENGINE=MyISAM; INSERT INTO tab_myisam (fdpk) VALUES (3); INSERT INTO tab_myisam (fdpk) VALUES (1),(2),(3); 결과는 1, 2, 3 이 저장된다. 트랜잭션이 지원되지 않기 때문에 1, 2 인서트가 성공하고 3을 ..

DB 2023.05.07

Real MySQL 4장 아키텍처 정리

MySQL 엔진 아키텍처 MySQL 전체 구조 및 간략 설명 MySQL의 구조를 그림으로 표현하면 다음과 같다. 여러 프로그래밍 언어에 드라이버와 API를 제공하며, MySQL서버는 크게 MySQL엔진과 스토리지 엔진으로 구분할 수 있음. MySQL 엔진 MySQL엔진엔 커넥션 핸들러, SQL 파서 및 전처리기, 옵티마이저 등이 있음. 스토리지 엔진 MySQL엔진은 SQL을 분석하거나 최적화하는 등의 DBMS의 두뇌와 같은 역할이라면 스토리지 엔진은 실제 데이터를 디스크에 저장하고 읽어오는 손발과 같은 역할. MySQL 서버에서 MySQL엔진은 하나지만, 스토리지 엔진은 여러 개가 있을 수 있음. create table문에서 끝에 ENGINE=INNODB; 구문을 붙이면 해당 테이블에 대한 읽기 쓰기를 ..

DB 2023.05.07

[Redis] 다건 조회를 캐싱할 때 고민했던 것들

최근 Redis를 사용해서 여러 조건절을 가지는 검색 쿼리의 응답을 캐싱해야하는 기술적인 과제를 겪었다. 꽤나 많은 고민과 주변의 도움으로 나름대로의 해결법을 도출해냈고 그 과정을 기록할까 한다. 문제 원인 일단 상황을 설명하면, 단건 id로 찍어서 조회하는 쿼리는 캐시하기 쉽지만 다건 조회는 신경쓸 포인트가 많아진다. 이유는 원본 데이터에 변경이 일어났을 경우 정합성 때문인데 캐시를 사용하게 되면 발생하는 고질적인 문제다. 단건 조회의 경우 id로 캐시의 key를 특정할 수 있기 때문에 조회시 캐시하고, 이후로 캐시된 값을 반환하고 변경이 발생하면 변경시 해당 캐시를 무효화하게 하면 정합성이 맞춰진다. 반면, 검색 조건이 여러 개가 들어가는 다건 조회의 경우 검색 조건까지 key에 넣고 결과를 캐싱하기..

Redis 2023.02.21

캐시와 관련된 HTTP 헤더들

HTTP 요청을 통해 데이터를 주고 받을 때 네트워크 통신 비용이 발생한다. 만약 데이터가 변경되지 않은 상태라면, 이전에 요청을 통해 받은 데이터를 클라이언트나 캐시 서버에 캐시해둬서 오리진 서버측에 직접 요청하지 않고 통신 비용을 아낄 수 있지 않을까? HTTP에선 응답 데이터에 대해서 캐시를 어떻게 다룰 지에 대한 명세를 나타내는 헤더들이 존재한다. 이번 포스팅에선 이 헤더들에 대해 알아보겠다. Cache-Control Cache-Control 헤더는 캐시에 관련된 HTTP/1.1 부터 도입된 표준 헤더이다. 브라우저의 캐시 뿐만 아니라 프록시나 CDN에 대해서도 다룬다. 디렉티브라고 부르는 옵션들이 있고 콤마로 구분한다. 일반적으로 다음과 같은 옵션을 다룬다. no-cache Cache-Contr..

Web 2022.12.10