[TIL] 53일차 _ 플러스 Spring #2

Seoyeon Lee·2025년 12월 17일

Today I Learned ...

오늘은 플러스 스프링 강의를 다 들었다!


📚 플러스 Spring #2

오늘은 어제에 이어 플러스 스프링 강의의 3-5주차 강의를 들었다.

먼저, 3주차 강의는 Cache에 대한 내용이었는데, 캐시란 자주 사용하는 데이터를 더 빠른 저장소에 임시로 저장해서 재사용하는 기술을 뜻한다.

캐시는 기존 데이터에 대한 복사본이기 때문에, DB의 값이 바뀜에 따라 캐시도 바뀌어야 한다.
그리고, 캐시 또한 저장 공간을 사용하기 때문에, 너무 많은 캐시를 저장해두게 되면 오히려 성능이 더 안좋아지게 된다.
그래서 캐시는 TTL, 유효기간을 잘 정해두어야 한다.
너무 짧으면 캐시 효율이 떨어지고, 너무 길면 데이터 불일치가 발생하게 된다.
그리고, 변경될 때마다 캐시도 바뀌어야 하기 때문에 변경이 잦은 데이터보다는 조회가 많은 데이터를 중심으로 적용해야 한다.

이 캐시는 애플리케이션 내부 메모리에 저장할 수도 있고, 외부 서버에 저장해서 여러 인스턴스가 공유해서 사용하게 할 수도 있다.

스프링에서 캐시를 저장하기 위해서는 @Cacheable 어노테이션으로 간단하게 생성할 수 있고, @CacheEvict로 캐시를 삭제하거나 @CachePut으로 캐시를 갱신할 수도 있다.
캐시를 사용하길 원하는 메서드에 원하는 어노테이션을 붙여서 사용하면 된다.

캐시를 저장하기 위한 외부 저장소로는 Redis를 사용했는데, Redis도 원래는 MySQL처럼 명령어를 통해 값을 저장하고, 조회해야 했다.
하지만, 스프링에서는 Redis를 쉽게 사용할 수 있도록 RedisTemplate을 제공하고 있기에 아주 간단하게 사용할 수 있었다.

스프링과 Redis를 연동하여 사용하는 실습 내용에 대해서는 노션을 통해 더 자세히 정리해두었다.
Notion 확인하기

4주차의 내용은 인덱스와 쿼리를 튜닝하는 방법에 대한 내용이었다.

먼저, 인덱스는 데이터베이스에서 데이터를 더 빠르게 찾도록 도와주는 검색용 지도인데, B-Tree 알고리즘을 사용해 인덱스와 데이터의 주소를 관리한다.
인덱스를 사용하면 조회에 대한 데이터베이스의 성능이 굉장히 많이 좋아진다.
하지만, 이 또한 메모리 공간을 소모하고, 데이터가 변경되면 트리 구조도 재정렬해야 하기 때문에 자주 갱신되는 컬럼에는 비효율적이다.

인덱스는 한 개의 컬럼은 물론, 여러 개의 컬럼을 묶어 복합 인덱스로 만들어 사용할 수도 있다.
이렇게 인덱스를 만들어두고 where절을 사용해 조회하면, 인덱스를 사용하지 않았을 때보다 훨씬 더 빠르게 결과를 얻을 수 있다.

사실 나도 알지 못하는 새에 이 인덱스를 사용하고 있었는데, 데이터베이스의 PK와 Unique Key가 인덱스로 만들어져 관리된다고 한다.
그래서 ID를 통해 무언가를 조회할 때 굉장히 빠르게 찾을 수 있었던 것이다.

기존에 진행하던 프로젝트들에서 username으로 post를 검색하거나 post ID로 comment를 조회하는 작업이 굉장히 많았다.
여기에 각각 username, post ID를 인덱스로 설정하게 되면, 더 빠르게 조회할 수 있게 된다.
이걸 바로 쿼리 튜닝, 쿼리 성능 개선이라고 한다.

강의의 자세한 설명과 실습 내용은 노션에 정리해두었다.
Notion 확인하기

마지막으로 5주차의 주제는 동시성이다.

동시성이란 하나의 데이터를 여러 사용자가 동시에 수정하거나 읽는 상황을 뜻하는데,
많지는 않지만, 사용자가 많아지면 가끔 여러 사람이 같은 데이터에 접근하는 일이 발생하게 된다.
DB는 모든 요청을 독립된 트랜잭션으로 처리하기 때문에, 지금 다른 요청이 같은 데이터를 수정하고 있는지 알 수 없다.
그래서 이 동시성을 반드시 제어해주어야 한다.

여기에는 3가지 방법이 있는데, 첫 번째는 비관적 락(Pessimistic Lock)이다.
비관적 락은 먼저 데이터를 잠가서 다른 사용자가 접근하지 못하게 하는 방법이다.
그래서 동일한 요청을 처리하더라도 한 트랜잭션이 데이터를 수정하고 있으면 다른 요청은 락이 해제될 때까지 기다려야 한다.

JPA에서 비관적 락을 구현하는 방법은 굉장히 간단한데, 원하는 메서드에 @Lock(LockModeType.PESSIMISTIC_WRITE) 어노테이션을 붙이기만 하면 된다.
이때, 단순 조회에 락을 걸면 다른 트랜잭션이 모두 기다려야 하기 때문에, '쓰기' 작업이 동시에 발생할 가능성이 높은 구간에만 사용해야 한다.

동시성을 해결하는 두 번째 방법은 낙관적 락(Optimistic Lock)인데,
먼저 데이터를 자유롭게 수정한 후, 수정이 완료된 시점에 버전을 확인해 다른 트랜잭션이 동일한 데이터에 작업을 수행했는지를 확인하는 것이다.
내가 버전 1에서 수정을 시작했지만, 그 사이 다른 사용자가 수정해서 버전이 2로 변경되었다면 OptimisticLockException이 발생하게 된다.
그러면 개발자는 이 예외에 대해 재시도를 하도록 도와주는 로직을 추가하기만 하면 된다.

이 또한 JPA에서 구현하기에 굉장히 간단한데, 이번에는 엔티티에 version 필드를 추가한 뒤, @Version 어노테이션을 붙이기만 하면 된다.
그리고 Service 로직에서 OptimisticLockException에 대해 try-catch문으로 처리해두기만 하면,
JPA가 알아서 version을 확인하여 수정하고, 업데이트 이후 version을 증가시켜준다.

동시성을 해결하는 마지막 방법은 분산 락(Distributed Lock)인데,
이건 여러 서버가 동시에 같은 작업을 수행하지 못하도록 하나의 '문'을 만들어 한 명씩만 들어오도록 제한하는 장치이다.
한 대의 서버만 사용할 때는 모든 요청이 순차적으로 처리되기에 낙관적 락 혹은 비관적 락으로 해결이 가능하지만,
서버가 여러 대일 때는 각각의 서버가 서로의 상태를 알지 못하기에 각 서버에서 동일한 로직을 동시에 실행하게 될 수 있다.

앞서 다룬 Redis를 통해 이 명령을 실행 중인 사용자가 있는지 플래그를 세워 확인하게 하면 된다.
Redis에서는 해당 내용을 다룰 수 있도록 SETNX(Set if Not Exist) 명령을 지원하는데, 명령을 실행할 때 여기에 키를 저장함으로써 락을 생성하고, 명령 실행이 끝나면 락을 해제하면 된다.

사실 Redis에 대해 잘 알지 못해 명령어를 이해하는 데에 조금 시간이 필요했지만, 구현 자체는 어렵지 않았다.
많은 메서드에서 반복되는 동작이기 때문에 AOP를 사용해 구현하면 굉장히 간단하게 해결할 수 있었다.

각각의 내용에 대한 더 자세한 설명과 실습 내용은 노션을 통해 기록해두었다.
Notion 확인하기


🙃 오늘의 느낀점

어제와 엊그제 코드카타를 계속 안했더니.. 너무 하기가 싫어졌다..
오늘 강의 다 들었으니까 내일은 정말 정말 정말 코드카타 해야겠다..

드디어 오늘 강의가 끝났는데!! 내일부터 빨리 과제를 시작해야겠다.
그리고 도커와 AWS 강의도 빨리 들어보고 싶다.

profile
백엔드 개발자 지망생

0개의 댓글