Java
[My-Storage] 동시성 이슈 해결을 위한 낙관적 락 + 비동기 용량 처리
기존에는 용량 처리를 효율적으로 할 방법이 떠오르지 않아, DB의 집계함수를 활용했다. 상위 부모들을 탐색하며 각 부모들에 대해 전부 MySQL의 SUM을 사용하여 계산했던 것이다.현재는 한 폴더 당 데이터가 100만 개씩 들어있지는 않아서 문제가 되지 않지만, 만약 이러한 경우가 발생한다면 성능에 치명적일 것이라 생각했다. 그래서 어떻게든 이동이 발생한 만큼의 용량만 계산하기 위한 고민을 했다.그림이 조금 어지럽지만, 6번 폴더가 8로 이동한다고 가정하자. 이때 6번 폴더의 용량은 10이다.이 경우 10이라는 용량 차감이 상위로 전파되어야 하고, 4번, 1번 폴더는 각각 10의 용량 차감이 발생해야 한다. 또한 8번, 7번, 2번 폴더의 용량은 10씩 더해줘야 한다.동시에, 4번 폴더의 이동이 발생했다..
[My-Storage] 폴더 이동 로직 락 사용 줄이기
기존 폴더 이동 로직에서는 source folder, target folder 모두에게 락을 걸었다.하지만 잘 생각해 보니, 어떠한 폴더에 락을 걸었다면, 다른 폴더들이 해당 폴더로 이동할 수 없을 것이다. 즉, 이동하는 중에 해당 폴더에 이동을 시도하는 폴더들이 존재하면 안 되기 때문에, 이동하는 폴더에만 락을 걸어서 문제를 해결할 수 있었다.위의 그림처럼 C에 락을 걸고 F로 이동한다고 했을 때, 다른 폴더들은 C로 이동하면 락을 보고 이동을 하지 않을 것이다.만약 탐색 중에 C가 이미 이동한 경우라면, 계속 탐색하여 자신의 자식 폴더로 이동하는지 확인할 수 있을 것이다.그래서 target folder의 락을 제거하고 source folder에만 락을 걸었고, 다시 부하 테스트를 진행했다. 이전과 ..
[My-Storage] 재귀 호출에서 발생할 수 있는 데드락
부하 테스트를 진행하며 위와 같이 서버가 죽어버리는 상황이 발생했다.처음에는 죽었는지 몰라서 DB가 처리를 너무 늦게 하나..?라는 생각을 했었다. 사실 120초라서 말이 안되긴 하지만..DB에는 문제가 없었고,다른 요청을 보내 봤는데, 요청 자체를 처리하지 못하는 것을 볼 수 있었다.힙이 가득 차서 뭔가 처리를 못하고 있나..? 그래서 GC가 계속 돌아가는데 아무것도 지우지 못해서 처리를 못하나..?라는 생각에 확인해 보았다.너무 멀쩡했다!혹시 소켓 수준에서 문제가 있나?라는 생각에 소켓 상태도 확인해 보았다.소켓 상태를 보면 08인 상태들이 대부분이었다. 이건 CLOSE_WAIT을 의미한다.부하테스트가 끝났기에 클라이언트가 연결을 종료했고, 서버는 처리중인 어떠한 작업을 완료하기 전에 CLOSE_WA..
[My-Storage] 폴더 이동 로직에서 사용한 분산락 작업 개선
이전 로직의 문제점기존 폴더 이동은 각각의 root 폴더의 ID 값을 기준으로 락을 획득하고 이동 작업을 진행했다. 그러다 보니 한 번에 하나의 이동 작업만 처리할 수 있었고, 동시에 여러 이동 작업을 처리할 수 없었다.이렇게 했던 이유는 폴더의 순환 구조를 막기 위함이었다. 최근 더 나은 방법이 생각나서 변경하고 테스트를 진행해 보았다. 아이디어이동 작업이 발생한 폴더의 ID를 기준으로 락을 요청한다. 락 획득이 가능하면 획득 후, 해당 폴더를 기준으로 상위로 탐색하며 탐색한 폴더의 ID를 기준으로 락이 걸려 있는지 확인한다. 만약 락이 걸려 있다면 나보다 먼저 이동 작업이 진행 중인 폴더가 존재한다는 것이다. 이 경우 획득한 락을 해제하고 이동 작업에 실패한다. 즉, 현재 이동 작업이 진행 중인 폴더..
[My-Storage 개선하기] Ceph에 파일 저장하기
이전에 간단하게 ceph 클러스터를 구성했으니 spring 서버를 통해 파일을 업로드해 보자.build.gradleimplementation platform('software.amazon.awssdk:bom:2.20.56')implementation 'software.amazon.awssdk:s3' // 필요한 모듈만 추가awssdk를 사용했다. s3 API를 사용하여 RGW에 요청을 보낼 것이기 때문에 s3도 추가했다.RgwConfigurationpackage com.woowacamp.storage.global.config;import java.net.URI;import org.springframework.beans.factory.annotation.Value;import org.springframew..
[My-Storage 개선하기] 로컬 환경에서 Docker로 Ceph 설치하기
현재 파일 처리의 문제점현재 서버는 파일을 직접 처리한다. 즉, InputStream으로 데이터를 읽고, OutputStream으로 쓰고 있다. 그냥 봤을 땐 문제가 없어 보이지만, 사용자의 요청이 증가하면 문제가 발생한다. 트래픽이 문제라면 그냥 서버를 다중화하면 되는 게 아닌가?라고 생각할 수 있다. 하지만, Spring 서버는 파일 처리만 하는 것이 아니라 다른 API에 대한 요청도 처리해야 한다. 이때 모든 스레드가 파일 처리를 하고 있으면 파일 업로드가 완료될 때까지 다른 요청들은 처리할 수 없게 되는 문제가 발생한다. 어떤 API의 요청이 0.1초 만에 끝나도 파일 업로드를 위해 스레드가 10초 동안 사용하지 못한다면 결국 간단한 요청도 오래 기다리거나 timeout이 발생하게 된다. 위 문제..
[My-Storage 개선하기] 데드락 해결, DB lock 사용 줄이기(2) - 테스트
테스트 환경 설정DB, Redis, Spring 모두 별도의 환경에서 테스트를 하고자 했다. 개선 전의 서버와 비교를 하려면 동일한 환경을 보장하는 것이 중요하다고 생각했기 때문이다. 서버를 살 돈은 없었기에 로컬에서 도커를 사용해서 컨테이너 환경에서 cpu와 메모리를 제한했다.Dockerfile# 1. 빌드 단계: Gradle 이미지 사용FROM gradle:7.6-jdk17 AS build# 2. 작업 디렉토리 설정WORKDIR /app# 3. 필요한 파일 복사COPY --chown=gradle:gradle . .# 4. Gradle을 사용해 JAR 파일 빌드RUN gradle clean build --no-daemon --stacktrace || (echo "Build failed. Check bu..

[My-Storage 개선하기] 데드락 해결, DB lock 사용 줄이기(1)
현재 프로젝트의 문제점현재 폴더의 이동이나 삭제 시 하위 폴더와 상위 폴더를 탐색하여 전부 락을 건 후에 작업을 진행한다.삭제의 경우 하위 폴더와 파일까지 모두 탐색하여 일관성 있게 제거하기 위함이고, 위로 거는 락은 이동과 삭제 시 용량 계산을 일관성 있게 하기 위함이다. 위 방법의 가장 큰 문제는 수정을 위해 Read Lock을 사용하면서 읽기 작업에도 영향을 미치게 된다는 점이다. 또한 전부 탐색을 진행한 후에 작업을 처리하기 때문에 커넥션을 길게 소유하게 되고, 동일한 DB를 사용하는 다른 서비스에도 영향을 미칠 수 있다. 또 다른 문제로는 락을 획득하는 과정이 한 방향이 아니라 위, 아래 두 방향으로 데드락이 발생하는 문제도 있었다. 고민그래서 이 모든 문제의 원인인 락을 사용하지 않고 일관성을..