#1 soft delete 적용 1
https://bbeomgeun.tistory.com/176
현재 상황
저는 현재 서버 개발 중, Delete 요청 시 실제로 데이터를 삭제하는 Hard Delete 방식이 아닌 해당 데이터의 Status를 변경해서 관리하는 Soft Delete 정책을 사용 중입니다.
그러나 요구 사항 중, 이메일 또는 닉네임이 중복되지 않아야 하는 조건이 있었습니다.
탈퇴한 유저의 데이터가 그대로 DB가 남아있는데, 중복이 허용되지 않는 필드와 동일한 값의 새로운 회원가입 요청이 오면 어떻게 할까요?
Insert시 어떻게?
만약 이메일이 중복되지 않아야 한다면
1. 이메일의 unique 제약 조건을 삭제한다. (DB 제약조건 삭제)
2. 탈퇴한 유저의 이메일 값을 변형시켜 중복을 피한다.
3. isDeleted (boolean)을 deletedAt (timestamp)로 변경 후, 이메일과 deletedAt과 복합 unique 제약 조건을 적용한다.
무엇이 좋을까
저는 3번을 선택했습니다. 왜냐하면 unique 제약 조건을 그대로 적용시킬 수 있으면서, 데이터의 변형도 없기 때문입니다. 또한 기존의 필드를 boolean에서 timestamp로만 바꾸면 적용 가능하기 때문입니다!
어떻게 적용할까
UNIQUE (email, deletedAt)
MySQL에서 unique 제약조건은 유일성을 만족하고, NULL을 허용합니다.
따라서 활성화된 데이터의 경우 deletedAt에 NULL을 저장하고, 삭제되는 경우 삭제된 시점을 저장합니다.
1. test@naver.com 회원 가입 (deletedAt = NULL)
2. test@naver.com 회원 가입 요청 ---> 중복 오류!
3. test@naver.com 회원 탈퇴 (deletedAt = 2022-10-27 02:47:22 변경)
4. test@naver.com 회원 가입 요청 ---> 정상 처리!
1. test@naver.com의 이메일을 가진 회원이 있다고 하면, 아직 삭제되지 않았으므로 deletedAt은 NULL입니다.
2. 당연하게도, 동일한 요청이 들어오게 된다면 unique 제약 조건에 위배됩니다. (test@naver.com, NULL) - 중복
3. 이후 회원 탈퇴를 진행하면, deletedAt이 now()로 update 됩니다. (test@naver.com, 2022-10-27 02:47:22)
4. 다시 동일한 요청이 들어온다면, 정상적으로 처리됩니다.
복합 Column의 비교로 (test@naver.com, NULL <-> test@naver.com, 2022-10-27 02:47:22) 이 중복되지 않습니다.
이후 또 회원 탈퇴를 하더라도, 탈퇴된 데이터끼리 역시 중복되지 않습니다. timestamp 값이 다르기 때문입니다.
조회 시
그럼 이제 Boolean에서 timestamp로 변경됐으니, 이전에 적용했던 @Where 조건 역시 변경해야 할 것입니다.
삭제되지 않은 데이터는 deletedAt이 NULL이므로 NULL인지 아닌지로 데이터를 가져오면 될 것 같습니다.
@Where(clause = "deleted_at IS NOT null")
개인적으로 궁금한 인덱스에 대해
soft delete의 유니크 제약 조건을 적용하다가 인덱스에 대해 궁금한 점을 작성해봤습니다.
(크게 연관이 없을 수도 있습니다)
기본적인 user 테이블을 생성했습니다.
alter table user
add constraint uniqueUser UNIQUE(userName);
create index uniquePassword on user(password);
show index from user;
unique 제약 조건과 보조 인덱스(세컨더리 인덱스)를 생성 후 해당 테이블의 인덱스 정보를 확인해봤습니다.
(인덱스는 생성한 column의 의미와 관계없이 적용해봤습니다)
기본적으로 MySQL innoDB는 Primary 키에 Clustered Index를 적용함을 확인할 수 있었습니다.
추가로 unique 제약 조건을 붙인 userName Column에는 유니크 인덱스가, 일반 인덱스를 추가한 password에는 보조 인덱스가 적용되어 있는 것을 확인할 수 있었습니다.
(unique 제약 조건을 붙이면 unique 인덱스가 적용되는지 궁금했습니다.)
추가로 생각해볼 점
해당 내용은 Real MySQL을 공부하면서 혼자 생각 및 추론해본 내용이라 정확하지 않을 수 있습니다. (추가 공부 필요)
1. 다중 Column 인덱스의 경우 인덱스가 어떻게 걸리는지 확인해야 할 것 같습니다. 왜냐하면 인덱스의 유무에 따라 데이터의 삽입, 삭제, 수정 시 레코드에 Lock이 걸리는 범위가 달라지기 때문입니다.
2. MySQL InnoDB 테이블에서 기본키는 클러스터드 인덱스로, 매번 정렬되어 있습니다. 만약 이메일을 기본키로 설정할 경우 매번 이메일이 insert 될 때마다 새로 들어온 이메일의 위치가 정렬되는 추가 cost가 발생할 것입니다.
또한, 이메일 값에 unique 제약 조건으로 인해 인덱스가 걸릴 경우 세컨더리 인덱스와 같이 동작할 것입니다.
(유니크 인덱스와 유니크하지 않은 일반 세컨더리 인덱스는 사실 인덱스의 구조상 아무런 차이점이 없다고 합니다.)
(물론 실행계획은 다르게 적용된다고 합니다)
유니크 인덱스와 세컨더리 인덱스의 성능 비교
1. 인덱스 읽기
유니크 인덱스가 빠르다고 생각할 수 있지만, 실제로는 성능이 크게 차이 나지 않습니다.
유니크 인덱스는 1건만 읽으면 되고, 유니크하지 않은 세컨더리 인덱스는 레코드를 한 건 더 읽어야 하기 때문에 더 오래 걸린다?
사실상 유니크하지 않은 세컨더리 인덱스에서 한 번 더 해야 하는 작업은 디스크 읽기가 아니라 CPU에서 Column값을 비교하는 작업이기에 이는 성능상 크게 영향을 주지 않는다고 합니다.
그저 중복된 값이 허용되어 읽어야 할 레코드가 많아서 느린 것이지, 인덱스의 특성 때문에 느린 것이 아닙니다.
2. 인덱스 쓰기
새로운 레코드가 INSERT 되거나 인덱스 Column의 값이 변경되는 경우에 인덱스 쓰기 작업이 필요합니다.
유니크 인덱스의 키값을 쓸 때는 중복된 값이 있는지 없는지 체크하는 과정이 한 단계 더 필요하기 때문에 유니크하지 않은 세컨더리 인덱스의 쓰기보다 느립니다.
또한, MySQL에서 유니크 인덱스에서 중복된 값을 체크할 때는 읽기 잠금을, 쓰기를 할 때는 쓰기 잠금을 사용하는데 이 과정에서 데드락이 빈번히 발생합니다. 그리고 InnoDB 스토리지 엔진에는 인덱스 키의 저장을 버퍼링 하기 위해 체인지 버퍼가 사용되는데, 유니크 인덱스는 반드시 중복 체크를 해야 하므로 작업 자체를 버퍼링 하지 못해서 유니크하지 않은 인덱스에 비해 인덱스의 저장이나 변경 작업이 빠르게 처리되지 못합니다.
결론
결론적으로 유일성이 꼭 보장돼야 하는 Column에 대해서는 유니크 인덱스를 생성하되, 꼭 필요하지 않다면 유니크 인덱스보다는 유니크하지 않은 세컨더리 인덱스를 생성하는 방법도 고려해보는 것이 좋다고 합니다.
'개발 공부 > 스프링' 카테고리의 다른 글
[SpringBoot X AWS S3] AWS S3 PresigendURL 적용하고 업로드해보기 (1) | 2022.10.11 |
---|---|
[Junit5, Mockito] Mockito를 이용해서 Void 메서드 Mocking하기 (0) | 2022.10.05 |
[Github Actions] CI 스크립트에 Redis 환경 추가하기 (0) | 2022.10.02 |
[SpringBoot x JPA] Soft Delete #1 Select (해당 Status 조건 추가 조회 방법) - @Where (1) | 2022.09.11 |
[SpringBoot x JPA] List 초기화에서 Builder 패턴 사용 시 NullPointerException (0) | 2022.08.08 |