[TIL #39] 탈퇴한 회원 관리 테이블 분리 VS 필드 트러블 슈팅

2025. 1. 12. 19:58·개발/내일배움캠프 TIL

아웃소싱 프로젝트를 진행중 요구 조건에 탈퇴한 사용자는 아이디를 재사용할 수 없다는 내용이 있었습니다.

이 요구 조건을 맞추기 위해 회원 테이블에 Enable, Disable 을 Enum 타입의 필드 주어 활성화, 비활성화 계정을 나누는 것과

탈퇴한 회원 아이디를 저장하는 별도의 테이블을 만들어 관리하는 것 두가지 방향이 제시되었습니다.

 

그리고 필드를 추가하면 유저를 조회할 때마다 회원이 활성화되있는 지 조건이 항상 붙어 성능적인 면에서 불리할 것 같다는 의견이 있어

두 케이스를 비교해보기로 하였고.  필드추가 방식과 테이블분리 방식 이라 부르겠습니다.

필드추가 방식 Entity

public class UserEntity extends BaseEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 50)
    private String email;

    @Column(length = 255)
    private String password;

    @Column(length = 255)
    private String address;

    @Column(length = 10)
    @Enumerated(EnumType.STRING)
    private UserRole userRole;

    @Enumerated(EnumType.STRING)
    private UserStatus status;

}

 

태이블분리 방식

회원 테이블은 위에서 UserStatus 필드를 제거하고 탈퇴한 회원 아이디를 관리할 테이블을 추가하였습니다.

public class WithDrawnEmailEntity extends BaseEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 255)
    private String email;

}

 

이렇게 구성하였을 때 API 를 개발하여 요청이 왔을 때 응답까지 얼마나 걸리는 지 비교하여 테스트를 진행해보았습니다.
테스트해볼 케이스를 다음과 같습니다.

  1. 회원가입 시
  2. 유저 목록 조회 시
  3. 회원 탈퇴 시

테스트 방식은 데이터베이스에 활성화된 유저 10만명, 비활성화된 유저 3만명을 미리 만들어두고,
포스트맨에서 각 API를 100번씩 실행했을 때 값을 측정하여 평균을 내는 것으로 진행헀습니다.


우선 프로젝트 구성상 유저 목록을 조회하는 API는 없기에 별도로 만들었습니다.

public interface UserRepository extends JpaRepository<UserEntity, Long> {

    Optional<UserEntity> findByIdAndStatus(Long id, UserStatus status);

    default UserEntity findByIdOrElseThrow(long id) {
        return findByIdAndStatus(id, UserStatus.ENABLED).orElseThrow(() -> new NdException(ErrorCode.USER_NOT_FOUND));
    }

    @Query("SELECT u FROM UserEntity u WHERE u.status = 'ENABLED'")
    List<UserEntity> findAllEnabledUsers();

}

 

필드추가 방식은 다음과 같이 유저를 조회할 때 WHERE 문으로 상태가 'ENABLED' 인 데이터를 필터링하도록 작성하였습니다.

 

회원가입 테스트

회원가입은 환경변수를 하나 정의하고 email 값에 환경변수를 넣고 pre-request 에 API를 호출될때마다 아이디값에 1++ 하게 세팅하여 중복된 이메일 검사를 통과하도록 하였습니다.

 

쿼리비교

필드 추가 방식

Hibernate: select ue1_0.id from nalda_users ue1_0 where ue1_0.email=? limit ?
Hibernate: insert into nalda_users (address,created_at,email,password,status,updated_at,user_role) values (?,?,?,?,?,?,?)

 

테이블 분리 방식

Hibernate: select ue1_0.id from nalda_users ue1_0 where ue1_0.email=? limit ?
Hibernate: select wdee1_0.id from with_drawn_emails wdee1_0 where wdee1_0.email=? limit ?
Hibernate: insert into nalda_users (address,created_at,email,password,updated_at,user_role) values (?,?,?,?,?,?)

 

응답 속도 비교

필드 추가 방식 테이블 분리 방식

 

 

 

유저 조회 테스트

필드 추가 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.status,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0 where ue1_0.id=? and ue1_0.status=?

 

테이블 분리 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0 where ue1_0.id=?

 

응답 속도 비교

필드 추가 방식 테이블 분리 방식

 

유저  목록 조회 테스트

필드 추가 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.status,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0 where ue1_0.status='ENABLED'

 

테이블 분리 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0

 

응답 속도 비교

필드 추가 방식 테이블 분리 방식

 

 

유저 탈퇴 테스트

유저 탈퇴의 경우 한번 탈퇴하면 데이터가 변경되어서 100번씩 여러 테스트하기 너무 번거로웠습니다.
다른 방법이 있을테지만 우선 수동으로 10번정도 진행하여 평균을 내는 방식으로 진행하였습니다.

필드 추가 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.status,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0 where ue1_0.id=? and ue1_0.status=?
Hibernate: update nalda_users set address=?,email=?,password=?,status=?,updated_at=?,user_role=? where id=?

 

테이블 분리 방식

Hibernate: select ue1_0.id,ue1_0.address,ue1_0.created_at,ue1_0.email,ue1_0.password,ue1_0.updated_at,ue1_0.user_role from nalda_users ue1_0 where ue1_0.id=?
Hibernate: insert into with_drawn_emails (created_at,email,updated_at) values (?,?,?)
Hibernate: delete from nalda_users where id=?

 

응답 속도 비교

필드 추가 방식 테이블 분리 방식

 

결론

종합적으로 보면 테이블 분리가 아주 근소하게 빠른 것을 확인할 수 있었습니다.

하지만 생각해볼 것은 일단 차이가 유의미하게 나지 않았다는 것.

그리고 테스트 방식이 이게 동일한 환경에서 올바르게 이루어진 것인지, 이런 방식으로 진행하는 게 맞는 지 정확하게 아직 모른다는 것입니다.

 

그럼 이제 두 가지 방식의 장단점에 대해 정리해보겠습니다.

 

필드 추가 방식

  • 로직이 비교적 간단하다.
  • 모든 유저 데이터가 한 테이블에 있어 관리가 편하다.
  • 실제 서비스를 대부분 활성화된 유저를 조회하기 때문에 유저관련된 조회에 항상 필터링을 해줘야한다.

테이블 분리 방식

  • 유저정보가 들어간 테이블이 두개로 분리되어 설계가 복잡해지며 잘못된 설계를 하면 데이터 정합성이 깨질 수 있다.
  • 데이터량이 더 많아지므로 리소스를 더 많이 잡아먹는다.
  • 활성화된 유저를 조회하기 편하다.

 

테이블 분리 방식을 구현하면서 유저 테이블과 탈퇴한 유저 테이블은 당연히 OneToOne으로 연관관계를 맺어야하지 않을까? 라는 생각을 했습니다. 그런데 탈퇴했을때 유저테이블에서 데이터를 완전히 삭제한다면 부모 데이터가 없어지는 것이기 때문에 그런 방식은 불가능해보였다. 연관관계를 맺지 않는다면 탈퇴한 사용자의 아이디가 양쪽에 중복으로 존재할 수 있어서 데이터 정합성이 깨져버립니다.

 

하지만 실제 서비스라면 탈퇴한 회원 이력은 언제 사용될 지 모르기 때문에 별도의 테이블로 관리하는 것이 좀 더 맞다고 생각이 들었고, 성능적으로 크게 문제되지 않는다면 둘 다 복합해서 사용하는 것이 맞는 것 같다는게 저의 생각입니다.

 

이 부분에 대해선 혹시나 내가 나중에 회원 기능 관련된 걸 또 맡게 된다면 좀 더 고민해보면서 탄탄한 설계를 해보고 싶습니다.

'개발 > 내일배움캠프 TIL' 카테고리의 다른 글

[TIL #42] [최종프로젝트] 채팅 서비스 개발2 - 채팅방 설계  (0) 2025.02.19
[TIL #40] 자바에서 데이터베이스 접근하기  (0) 2025.01.14
[TIL #38] Spring 심화 주차 과제 Lv 6 기능 개선하기  (0) 2025.01.06
Spring 뉴스피드 프로젝트 미니 발제 KPT 회고  (0) 2024.12.27
[TIL #37] JPA를 이용한 일정 과제를 하면서 배운 내용 + 트러블 슈팅 기록  (0) 2024.12.18
'개발/내일배움캠프 TIL' 카테고리의 다른 글
  • [TIL #42] [최종프로젝트] 채팅 서비스 개발2 - 채팅방 설계
  • [TIL #40] 자바에서 데이터베이스 접근하기
  • [TIL #38] Spring 심화 주차 과제 Lv 6 기능 개선하기
  • Spring 뉴스피드 프로젝트 미니 발제 KPT 회고
BigChoi93
BigChoi93
이곳은 저의 성장과정과 개인적인 생각을 담기 위한 공간입니다.
  • BigChoi93
    Donologue
    BigChoi93
  • 전체
    오늘
    어제
    • 분류 전체보기 (61)
      • 개발 (53)
        • Javascript (2)
        • 내일배움캠프 TIL (41)
        • 개발일기 (4)
        • Java (2)
        • Spring (1)
        • Sql (1)
      • 일상 (0)
      • 사진 (1)
        • 포토샵 (1)
  • hELLO· Designed By정상우.v4.10.1
BigChoi93
[TIL #39] 탈퇴한 회원 관리 테이블 분리 VS 필드 트러블 슈팅
상단으로

티스토리툴바