본문 바로가기
카테고리 없음

230307 TIL

by hbIncoding 2023. 3. 8.

1.  오늘 해결한 에러

 1)S3 Credential 추가 

 		AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
        final AmazonS3 s3 = AmazonS3ClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region).build();
        LogoAndImage logoAndImage = logoAndImageRepository.findByBoardId(id)
                .orElseThrow(()->new CustomException(ErrorCode.NULL_IMAGE_DATA));

        System.out.println("엔티티다 찾음");
        String deletelogokey = logoAndImage.getLogoKey();
        if (!deletelogokey.equals("")){
            try{
                s3.deleteObject(bucket,deletelogokey);
                System.out.println("로고삭제성공");
            }catch (Exception e){
                throw new CustomException(ErrorCode.NULL_IMAGE_DATA);
            }
        }
  • 위와 같이 .withCredential(..)을 추가해주지 않으면 S3 권한 에러가 발생한다.
  • S3 설정과 연관이 있겠지만 파일 업로드시에는 발생하지 않았었는데 삭제시에는 저렇게 바꿔줘야 했다.

 2)Postman에서 working directory로 파일이 전송되지 않을 때

  • 해당 에러가 발생 시 On으로 설정이 되었는지 확인해 주자.

 

2.  Spring 지식

 1)JsonIgnore

  • 연관관계 매핑에서 아래와 같이 서로를 계속 참조해서 무한 참조가 걸릴 때 사용한다.

  • 아래와 같이 설정해주면 user는 무시하고 호출받아 해결할 수 있다.

 2)동시성 문제

  • 동인한 자원에 대해 여러 스레드가 동시에 접근하면서 발생하는 문제
  • 동시성 문제는 지역 변수에서 발생하지 않는다
    • 지역변수는 쓰레드마다 각각 다른 메모리 영역이 할당되기 때문
  • 같은 인스턴스 필드(주로 싱글톤) 또는 static 같은 공용 필드에 접근할 때 발생한다.
  • 무조건 동시에 접근한다고 발생하는 건 아니고 값을 어디선가 변경할 때 발생한다.
    • 즉 읽기만 해서는 발생하지 않는다.
  • 예시
@Slf4j
public class FieldService {

    private String nameStore;

    public void logic(String name){
        nameStore.set(name);
        log.info("저장 name={} -> nameStore={}",name,nameStore.get());
        sleep(1000);
        log.info("조회 nameStore ={}",nameStore.get());        
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


@Slf4j
public class FieldServiceTest {

    private FieldService fieldService = new FieldService();

    @Test
    void field(){
        log.info("main start");

        Thread threadA = new Thread(() -> fieldService.logic("userA"));
        Thread threadB = new Thread(() -> fieldService.logic("userB"));

        threadA.start();
        sleep(100);
        threadB.start();

        sleep(3000);
        log.info("main exit");

    }
    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 위 코드를 통해 name값을 nameStore필드에 저장하고 1초 쉬고 저장된 nameStore값을 찍어보는 메서드를 작성한다.
  • 그리고 이 메서드를 0.1초단위로 실행해본다.
//기대 값
main start
저장 name=userA -> nameStore=userA
조회 nameStore =userA
저장 name=userB -> nameStore=userB
조회 nameStore =userB
main exit

//실제 값
main start
저장 name=userA -> nameStore=userA
저장 name=userB -> nameStore=userB
조회 nameStore =userB
조회 nameStore =userB
main exit
  • 기대와 실제가 다르다는 것을 알 수 있다.

 

 

 3)ThreadLocal을 통한 동시성 문제 해결

  • ThreadLocal을 해당 쓰레드만 접근할 수 있는 개인 저장소를 의미한다.
  • 하나의 자원을 보관하는 창구 직원, 두 개의 쓰레드를 문건 보관소 고객이라고 이하하면 쉽다.
@Slf4j
public class FieldService {

    private ThreadLocal<String> nameStore = new ThreadLocal<>();

    public void logic(String name){
        nameStore.set(name);
        log.info("저장 name={} -> nameStore={}",name,nameStore.get());
        sleep(1000);
        log.info("조회 nameStore ={}",nameStore.get());
        nameStore.remove();      
    }

    private void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • ThreadLocal 적용
    • 동시에 접근하는 자원의 타입을 ThreadLocal로 변경하고 기존의 타입을 제네릭 안에 넣는다.
    • 반드시 new ThreadLocal로 생성
    • ThreadLocal.set() : 값을 저장
    • ThreadLocal.get() : 값 조회
    • ThreadLocal.remove() : 값 제거
      • 쓰레드가 사용이 끝난 후에는 반드시 remove로 값을 제거해야 한다.

 

 4)ThreadLocal사용 시 주의 사항

  ㄱ. 쓰레드로컬 사용후 반드시 remove로 제거해야 하는 이유

  1. 사용자 A가 저장 HTTP 요청을 보낸다.
  2. WAS는 쓰레드 풀에서 쓰레드를 꺼내서 Thread-A를 할당한다.
  3. Thread-A는 사용자 A의 데이터를 Thread-A의 쓰레드 로컬에 저장한다.

  1. 사용자 A의 HTTP 응답이 종료된다.
  2. WAS는 사용이 끝난 Thread-A를 쓰레드 풀에 반환한다.
  3. Thread-A는 쓰레드 풀에 아직 살아있기 때문에 Thread-A의 쓰레드 로컬도 여전히 살아있다.

  1. 사용자 B가 조회 HTTP 요청을 보낸다.
  2. WAS는 쓰레드 풀에서 쓰레드를 꺼내서 할당했는데 마침 Thread-A이다.
  3. Thread-A가 조회요청을 수행하면서 이전에 사용자 A가 저장했던 쓰레드 로컬에 있는 데이터를 조회한다.
  • 결과적으로 사용자 B는 사용자 A의 데이터를 확인하게 되는 심각한 문제가 발생합니다.
    따라서 해당 쓰레드의 요청이 끝날 때 반드시 쓰레드 로컬 값을 비워줘야 합니다.

 5)Lock

  • db에 동시 접근 시 충돌이 일어나는 것을 방지해야한다.
    • 첫번째 : 테이블 row에 접근시 lock을 걸고 다른 lock이 걸려있지 않을 경우에만 수정가능하게 할 수 있다.
    • 두번째 : 수정할 때 내가 먼저 이값을 수정했다고 명시하여 다른 사람이 동일한 조건으로 값을 수정하지 못하게 막는다.

  ㄱ. 비관적 락(pessimistic lock)

  • 트랜잭션이 시작될 때 Shared Lock 또는 Exclusive Lock을 걸고 시작하는 방법
  • Shared Lock을 걸게 되면 write를 하기위해서는 Exclucive Lock을 얻어야하는데 Shared Lock이 다른 트랜잭션에 의해서 걸려 있으면 해당 Lock을 얻지 못해서 업데이트를 할 수 없습니다.
  • 수정을 하기 위해서는 해당 트랜잭션을 제외한 모든 트랜잭션이 종료(commit) 되어야합니다.
  • transaction을 이용하여 충돌을 예방하는 방법

  1. Transaction_1 에서 table의 Id 2번을 읽음 ( name = Karol )
  2. Transaction_2 에서 table의 Id 2번을 읽음 ( name = Karol )
  3. Transaction_2 에서 table의 Id 2번의 name을 Karol2로 변경 요청 ( name = Karol )
    • 하지만 Transaction 1에서 이미 shared Lock을 잡고 있기 때문에 Blocking
  4. Transaction_1 에서 트랜잭션 해제 (commit)
  5. Blocking 되어있었던 Transaction_2의 update 요청 정상 처리

 

  ㄴ. 낙관적 락(optimistic lock)

  • DB에서 제공해주는 특징을 이용하는 것이 아닌 Application Level에서 잡아주는 Lock

  1. A가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )
  2. B가 table의 Id 2번을 읽음 ( name = Karol, version = 1 )
  3. B가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol2, version = 2 ) 성공
  4. A가 table의 Id 2번, version 1인 row의 값 갱신 ( name = Karol1, version = 2 ) 실패
    • Id 2번은 이미 version이 2로 업데이트 되었기 때문에 A는 해당 row를 갱신하지 못함
  • 같은 row에 대해서 각기 다른 2개의 수정 요청이 있었지만 1개가 업데이트 됨에 따라 version이 변경되었기 때문에 뒤의 수정 요청은 반영되지 않게 되었습니다.
  • 이렇게 낙관적락은 version과 같은 별도의 컬럼을 추가하여 충돌적인 업데이트를 막습니다. version 뿐만 아니라 hashcode 또는 timestamp를 이용하기도 합니다.

  ㄷ. 롤백(Rolback)

  • 만약 업데이트를 하는 테이블이 1개가 아니라 2개의 테이블이며 2번째 테이블을 업데이트하다 이와같은 충돌이 발생했다면 하나의 수정 요청에 대해서는 롤백이 필요하게 된다.
  • 비관적 락
    • 하나의 트랜잭션으로 묶여있기 때문에 수정이 하나 실패하면 db 단에서 rollback이 일어나게 된다. 
    • 만약 theTable이 실패했다고 생각한다면 transaction이 실패한 것이기 때문에 트랜잭션 전체에 자동으로 rollbakc이 일어나게 된다.
 - SELECT id, `name`
       FROM theTable
       WHERE id = 2;
 - {새로운 값으로 연산하는 코드}
 - BEGIN TRANSACTION;
 - UPDATE anotherTable
       SET col1 = @newCol1,
           col2 = @newCol2
       WHERE id = 2;
 - UPDATE theTable
       SET `name` = 'Karol2',
       WHERE id = 2;
 - {if AffectedRows == 1 }
 -     COMMIT TRANSACTION;
 -     {정상 처리}
 - {else}
 -     ROLLBACK TRANSACTION;
 -     {DB 롤백 이후 처리}
 - {endif}
  • 낙관적 락
    • transaction으로 잡지 않는다. 
    • 충돌이 발생하여 수정을 못한 부분에 대해서는 롤백에 대한 책임을 applicaiton단에서 지며 직접 롤백을 수동으로해줘야한다.
 - SELECT id, `name`, `version`
       FROM theTable
       WHERE iD = 2;
 - {새로운 값으로 연산하는 코드}
 - UPDATE theTable
       SET val1 = @newVal1,
           `version` = `version` + 1
       WHERE iD = 2
           AND version = @oldversion;
 - {if AffectedRows == 1 }
 -     {정상 처리}
 - {else}
 -     {롤백 처리}
 - {endif}
  • 각각의 경우에서 효율적인 상황

낙관적 락은 트랜잭션을 필요로하지 않습니다. 따라서 성능적으로 비관적 락보다 더 좋습니다 그리고 낙관적 락은 트랜잭션을 필요로 하지 않습니다. 이 두가지가 비관적 락에 비해 가지는 낙관적 락의 최대 강점입니다. 트랜잭션을 필요로 하지 않기 때문에 아래와 같은 로직의 흐름을 가질때도 충돌 감지를 할 수 있습니다. 만약 비관적 락이라면 1번에서 3번사이의 트랜잭션을 유지할 수가 없습니다.

  1. 클라이언트가 서버에 정보를 요청
  2. 서버에서는 정보를 반환
  3. 클라이언트에서 이 정보를 이용하여 수정 요청
  4. 서버에서는 수정 적용 ( 충돌 감지 가능 )

또한 성능적으로 비관적 락보다 좋습니다. 때문에 충돌이 많이 일어나지 않을 것이라고 보여지는 곳에 사용하면 좋은 성능을 기대할 수 있습니다.

하지만 낙관적 락의 최대 단점은 롤백입니다. 만약 충돌이 났다고 한다면 이를 해결하려면 개발자가 수동으로 롤백처리를 한땀한땀 해줘야합니다. 비관적 락이라면 트랜잭션을 롤백하면 끝나는 작업이지만 낙관적 락은 그렇지 않습니다. 수동으로 롤백처리는 구현하기도 까다롭지만 성능적으로 보더라도 update를 한번씩 더 해줘야합니다. 따라서 결과적으로 비관적 락 보다 좋지 않을 수 있습니다. 이러한 단점 때문에 낙관적 락은 충돌이 많이 예상되거나 충돌이 발생했을 때 비용이 많이 들것이라고 판단되는 곳에서는 사용하지 않는 것이 좋을것으로 보입니다.

 

 

 

 

 

 

 

3.  참조

 1)postman working-directory 설정 : https://serpiko.tistory.com/838

 

POSTMAN - File 전송시 Working Directory 에러

POSTMAN 에서 POST Method로 body의 form-data 전송시 파일도 보낼 수 있는건 알고있는데 working directory 에러로 파일 전송이 되지 않아 공식홈에 가보면 설정에서 디렉토리 설정하라는데 도무지 어디서 설

serpiko.tistory.com

 2)jsonIgnore : https://m.blog.naver.com/sosow0212/222683535438

 

[스프링부트 + JPA] @JsonIgnoreProperties, cascade 알아보기

[스프링부트 + JPA] @JsonIgnoreProperties, cascade 알아보기 JPA를 사용하다보면, @JsonIgnor...

blog.naver.com

3)ThreadLocal :https://backtony.github.io/java/2021-12-24-java-41/

 

Java - 동시성 문제와 해결책

Java, JPA, Spring을 주로 다루고 공유합니다.

backtony.github.io

4)낙관적/비관적 락 : https://sabarada.tistory.com/175

 

[database] 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)

안녕하세요. 오늘은 낙관적 락과 비관적 락의 개념에 대해서 알아보는 시간을 가져보도록 하겠습니다. DB 충돌 상황을 개선할 수 있는 방법 database에 접근해서 데이터를 수정할 때 동시에 수정이

sabarada.tistory.com