TIL

24.08.26 TIL - 발생 가능 장애

개발공명 2024. 8. 27. 11:50

오늘 배운 것

  • 발생 가능 장애
  • DB Lock 관련 장애
  • DB 복제 지연
  • 메모리 릭
  • 캐시 압력

 

발생 가능 장애

실제 서비스를 진행하면 많은 장애 상황을 마주할 수 있습니다. 

 

다양한 장애 상황이 발생했을 때 어떻게 해결해야 하는지 아는 것이 중요합니다. 

 

이보다 더 중요한 것은 이런 장애 상황이 발생하지 않도록 개발을 진행할 때 어떤 것에 신경을 써야 하는지 아는 것입니다. 

 

살펴볼 장애 상황들은 아래와 같습니다. 

  • DB Lock 관련 장애
  • DB 복제 지연
  • 메모리 릭
  • 캐시 압력

 

DB Lock 관련 장애

데이터베이스는 여러 사용자나 시스템이 동시에 데이터를 읽고 쓰는 환경에서 운영된다. 

 

따라서 트랜잭션이라는 DB의 상태를 변경시키는 작업의 단위가 있다. 

 

이런 환경에서 문제가 발생할 수 있는 대표적인 사례가 있다. 

 

  • Dirty Read (더티 리드)
    • 한 트랜잭션이 데이터 수정 중일 때 다른 트랜잭션이 데이터를 읽는 상황.
    • 첫번째 트랜잭션이 롤백된다면 두번째 트랜잭션은 잘못된 데이터를 읽은 것이 됨.
  • Non-repeatable Read (반복 불가능한 읽기)
    • 한 트랜잭션이 데이터를 읽은 후, 다른 트잭션이 데이터 수정하고 커밋한 상황.
    • 첫번째 트랜잭션이 동일한 데이터를 다시 읽을 때 값이 달라지게 됨.
  • Lost Update (업데이트 손실)
    • 두개의 트랜잭션이 동시에 같은 데이터를 수정하려는 상황.
    • 한 트랜잭션의 수정 내용이 다른 트랜잭션에 의해 덮어쓰여져 사라지게 됨.

 

이러한 문제점들이 발생할 수 있다.

 

이러한 문제점들을 사전에 막으려면 DB Lock이라는 것이 필요하다. 

 

 

DB Lock이란?

DB 락(Database Lock)은 데이터베이스에서 여러 트랜잭션이 동시에 같은 데이터에 접근할 때, 데이터의 무결성(일관성)을 보장하기 위해 사용되는 메커니즘이다. 

 

한 트랜잭션이 특정 데이터에 대해 작업을 하고 있을 때 다른 트랜잭션이 그 데이터에 접근하지 못하도록 잠그는 것이다

 

이로써 데이터의 일관성을 유지하고, 동시에 발생할 수 있는 충돌을 방지할 수 있다. 

 

DB Lock을 통해 데이터에 대한 접근을 제어하면 위의 문제점들에서 발생할 수 있는 데이터 무결성 문제를 예발할 수 있다. 

 

 

DB Lock의 종류

  • 공유 Lock (Shared Lock)
    • 데이터베이스에서 데이터를 읽을 때 사용한다. 
    • 여러 트랜잭션이 동시에 같은 데이터를 읽을 수 있지만, 공유 락이 걸린 동안에는 데이터를 수정할 수 없다.
  • 배타 Lock (Exclusive Lock)
    • 데이터를 수정할 때 사용한다. 
    • 배타 락이 걸린 데이터는 다른 트랜잭션이 읽거나 수정할 수 없다. 
  • 비관적 Lock (Pessimistic Lock)
    • 데이터를 읽을 때부터 락을 걸어 다른 트랜잭션이 접근하지 못하도록 하는 방식이다. 
    • 데이터 충돌 가능성이 높을 때 유용하다. 
  • 낙관적 Lock (Optimistic Lock)
    • 데이터를 수정하기 전까지 락을 걸지 않고, 수정 시점에만 충돌을 확인하는 방식이다.
    • 주로 데이터의 버전 번호를 사용하여 동시성 문제를 해결한다. 
  • 명명된 Lock (Named Lock)
    • 특정 이름으로 락을 설정하여, 동시에 하나의 프로세스만 특정 리소스에 접근하도록 하는 방식이다.
    • 주로 특정 리소스나 작업에 대한 접근을 직관적으로 제어하기 위해 사용한다.
  • 분산 Lock (Distributed Lock)
    • 여러 시스템이나 인스턴스에서 동시에 동일한 자원에 접근할 때, 자원의 일관성 유지하기 위해 사용되는 락이다.
    • Redis와 같은 분산 시스템을 사용하여 구현된다. 

 

DB 복제 지연

복제 지연은 데이터가 쓰기DB에서 읽기DB로 복제되는 과정에서 발생하는 시간지연 의미한다. 

 

쓰기 작업이 발생한 후 그 변경 사항이 읽기 DB에 반영되기까지의 지연 시간을 말한다. 

 

복제 지연으로 인해 읽기 DB에서 최신 상태의 데이터를 읽지 못하고 이전 상태의 데이터를 읽게 되는 문제 발생할 수 있다.

 

 

이런 문제점을 해결하기 위해서는 아래의 방법들을 사용할 수 있다. 

  • 지역 적용 알고리즘 (Lag Compensating Logic)
    • 쓰기 후 즉시 읽기를 시도하는 경우, 잠시 지연을 두고 읽기를 재시도하는 방법
    • 지연된 시간만큼의 시간이 지나면, 대부분의 경우 읽기 DB가 최신 데이터 반영하는 방법
  • 쓰기 DB로의 직접 읽기 (Read-after-Write)
    • 중요한 데이터에 대해 쓰기 직후 즉시 읽어야 하는 경우, 해당 읽기 작업을 쓰기 DB에서 직접 수행하게 하는 방법
  • 읽기 DB와 쓰기 DB 간의 복제 지연 최소화
    • DB 복제 지연을 최소화하도록 DB 설정 조절하는 방법
    • DB마다 복제 설정을 조절하고 최적화 할 수 있음
    • 그러나 물리적인 네트워크 지연이나 시스템 부하 등으로 인한 지연은 완전히 제거 불가능
  • CQRS 패턴
    • Command Query Responsibility Segregation (CQRS) 패턴은 데이터 저장소로부터 읽기와 쓰기 작업을 분리하는 패턴
    • CQRS 패턴을 적용하여, 쓰기 작업과 읽기 작업을 명확히 분리하는 방법
    • 읽기 작업에 대해 일관성을 보장하는 별도의 메커니즘을 적용 가능
  • 캐시 사용
    • 많이 사용하는 방법이고 쓰기 지연 일어나지 않게 하는 가장 쉬운 방법
    • 캐시 시스템을 도입하여, 쓰기 작업 후 바로 캐시를 갱신하고, 그 이후의 읽기 작업은 캐시에서 제공하는 방식을 사용
    • 캐시는 일관성을 보장하기 위해 일정 시간 동안(예: 1초) 쓰기 DB의 데이터를 캐시하는 방법을 사용 가능

 

메모리 릭

메모리 릭이란 프로세스가 더 이상 필요하지 않은 메모리를 할당한 후 이를 해제하지 않은 것이다. 

 

따라서 해당 메모리가 지속적으로 점유된 상태로 남아있는 현상이다. 

 

이러한 메모리 릭은 메모리 사용량을 지속적으로 증가시킨다. 

 

결국에는 시스템의 메모리를 고갈시켜 성능 저하 또는 애플리케이션의 충돌을 유발할 수 있다.

메모리에서 나가

 

메모리 릭의 일반적인 원인은 아래와 같다. 

  • 이벤트 리스너 및 콜백 등록 후 해제 X
    • 이런 것들을 해제하지 않으면 객체가 계속 메모리에 남아 메모리 릭 발생 가능
  • 전역 변수 사용
    • 전역 변수로 객체 참조하면 해당 객체가 프로그램 종료 시점까지 해제되지 않을 수 있음
  • 캐시 관리 실패
    • 캐시된 객체가 필요 없을 때에도 계속해서 메모리에 유지되면 메모리 릭 발생 가능
  • 잘못된 데이터 구조 관리
    • 객체 저장 후 필요 없을 때 객체 제거하지 않으면 메모리 릭 발생 가능

 

스프링에서의 메모리 릭이 일어날 수 있는 상황은 아래와 같다. 

  • 스프링 빈의 라이프 사이클 관리 문제
    • 빈은 애플리케이션 컨텍스트 살아있는 동안 메모리에 유지됨
    • 빈이 내부적으로 많은 메모리 점유하는 객체 가지고 있으면 메모리 릭 발생 가능
  • @Autowired로 주입된 객체의 잘못된 관리
    • @Autowired로 주입된 객체가 예상치 못한 참조 순환 일으키면 메모리 릭 발생 가능
    • @Autowired로 주입된 객체가 의도적으로 제거되지 않는다면 메모리 릭 발생 가능
  • ThreadLocal의 잘못된 사용
    • ThreadLocal 사용해 스레드별로 데이터 저장하는 경우 스레드 종료 시 해당 스레드와 관련된 모든 데이터 제거해야 함
    • 이런 데이터 명시적으로 제거하지 않으면 메모리 릭 발생 가능
  • 이벤트 리스너의 잘못된 관리
    • 스프링에서 이벤트 리스너 등록 후 리스너 필요하지 않은 경우 제거해야 함
    • 제거하지 않으면 리스너가 계속해 메모리를 점유해 메모리 릭 발생 가능

 

이런 메모리 릭을 해결하려면 아래의 것들에 대해 생각해야 한다.

  • 위의 상황들을 알고 있어야 한다. 
  • 위의 상황들에 해당하는 것들을 의도적으로 메모리에서 제거해줘야 한다. 
  • 캐시에 TTL 시간 조절에 캐시에서 메모리 릭 발생하지 않도록 해야 한다. 
  • 개발자가 객체의 라이프 사이클을 명확하게 이해하고 있어야 한다. 

 

캐시 압력

캐시 압력은 캐시 메모리가 부족해지면서 발생하는 문제다. 

 

캐시가 가득 차서 더 이상 새로운 데이터를 저장할 수 없고, 이로 인해 기존 데이터를 삭제해야 하는 상황 발생할 수 있다. 

 

캐시 압력이 증가하면, 캐시 항목을 삭제하거나 새 데이터를 캐시에 저장하는 동안 대기 상태가 발생할 수 있다. 

 

캐시 압력이 증가하면 새로운 데이터를 저장할 수 없어 요청이 지연되고 대기 상태로 빠지는 문제 발생할 수 있다. 

 

캐시 압력이 증가하면 필요한 데이터가 캐시에 존재하지 않아, 다시 데이터베이스에서 가져오는 상황이 빈번해질 수 있다. 

 

 

캐시 전략을 제대로 수립하지 않으면 캐시 압력이 발생할 수 있다. 

 

필요 없거나 자주 사용하지 않는 데이터를 캐싱해 낮은 캐시 히트율을 가진다면 캐시 압력이 발생할 수 있다.

 

낮은 캐시 히트율은 캐시의 효율성을 떨어뜨리고 메모리 낭비로 이어져 캐시 압력을 증가 시킨다. 

 

 

이런 캐시 압력의 해결 방안으로는 아래와 같은 것들이 있다. 

  • 캐시 제한 설정
    • TTL 
      • 캐시에 TTL 설정해 일정 시간 후 자동으로 만료되도록 설정
    • 최대 캐시 크기 설정
      • 캐시의 최대 크기를 설정해 용량 초과되면 오래된 데이터 자동으로 제거하도록 설정 
  • 페이징 전략 최적화
    • 부분적 캐싱
      • 모든 페이지 캐싱하기보다는 자주 요청되는 특정 페이지만 캐싱
    • 데이터베이스 인덱스 최적화
      • 캐시 사용 대신 데이터베이스 인덱스 최적화해 페이징 성능 향상
  • 분산 캐시 사용
    • 분산 캐시 시스템 사용해 여러 서버에 캐시 데이터 분산
    • 한 캐시의 메모리 용량 한계 극복, 캐시 성능 향상 가능
  • 캐시 우회 전략
    • 특정 조건에서 캐시를 우회하고 직접 데이터베이스에서 데이터 가져오도록 전략 수립

 

'TIL' 카테고리의 다른 글

24.08.28 - JWT in Spring  (6) 2024.08.30
24.08.27 TIL - JWT  (0) 2024.08.28
24.08.23 TIL - 모니터링  (0) 2024.08.26
24.08.22 TIL - 캐싱  (0) 2024.08.23
24.08.21 TIL - session clustering  (0) 2024.08.23