Contents

MySQL에서 이메일 재사용 가능하게 하기- Soft Delete와 Unique를 함께 활용하다.

 
 

개발환경

  • Javascript 런타임 플랫폼: Node.js
  • 언어: TypeScript
  • 프레임워크: Express
  • DB: MySQL
  • ORM: TypeORM  

현재 조건 상황

  • 회원가입시, 이메일과 패스워드가 필수 입력입력인데, 이메일의 경우 mySQL에서 unique 처리
  • 회원가입 과정에서 이메일 중복 여부를 확인하고 통과했을 경우에만 회원가입 가능
  • 회원 삭제시, 실제 회원정보를 DB에서 지우지 않고 TypeORM의softDelete 방식으로 deleted_at 컬럼에 삭제일시가 기록되는 방식 (일정기간 이후 삭제할 요량으로 단기간 데이터 보존)  
     
Question
이미 회원 삭제된 정보의 이메일로 재가입하려는 경우,
해당 이메일은 이미 mySQL에서 unique로 입력되어있는 이메일이기 때문에 이메일 중복확인에서 통과되지 않는다.
해당 이메일을 재사용하려면?

 
 

3가지 방안들

  1. mySQL - entity - email column 에서 unique 조건을 제거하고, 중복여부를 필터링하는 코드를 추가하는 방법으로 진행한다.
  2. DB에서의 unique 조건을 유지시키기 위해 삭제 로직 중, email을 변경하여 저장한다.
  3. 삭제된 email로 회원가입이 들어올 경우, 복구 과정을 추가한다.  
     

1번의 경우

mySQL의 unique 제약 조건을 제거하면 이메일 중복 확인 로직을 코드에서 추가적으로 수정하여 처리해야 한다.
즉, deleted_at 컬럼이 null인지 아닌지에 대한 확인이 필요하기에 Users DB에서의 조건이 실행되어야 한다.
이 경우, 데이터베이스에서의 조회 로직에 Indexing이 들어간 Unique 컬럼 조회와 더불어 전체스캔을 필요로 하는 MySQL의 Is Null절을 추가적으로 사용해야하며 , 이로 인해 성능에 약간의 영향을 미칠 것으로 예상된다.
또한 개발 도중의 문제로 deleted_at 컬럼의 데이터에 문제가 생긴다면 걷잡을 수 없는 혼란을 야기할 수도 있다.
 

그러나 이 방법은 프로그래밍적으로 더 유연하게 사용할 수 있고, 사용자의 이메일을 변경하지 않아도 되기에 데이터의 원형을 유지하면서도 새 사용자가 이전에 사용되었던 이메일을 재사용할 수 있다는 이점이 있다.
 
 

2번의 경우

예를 들어, 사용자가 삭제되면 해당 사용자의 이메일을 "[email protected]"와 같이 변경하여 mySQL에서의 unique 조건을 그대로 유지할 수 있다.
이 방법은 데이터베이스의 무결성을 유지하면서도 이메일 재사용 문제를 해결할 수 있다.
 
하지만 이 경우 사용자의 원래 이메일을 변경해야 하므로 데이터의 원형을 유지하는 데 어려움이 있을 수 있다.  
 

3번의 경우

복구과정을 추가할 시, 애초에 이메일을 오타로 입력하여 등록이 되어버리면 이미 이 이메일은 이전 회원의 소유로 귀속되기에 사실상 원래 이메일의 소유자가 회원가입을 하려고 할 때 불가능해진다.
따라서 이 방법은 패스!
 
 


고려사항

1번과 2번의 방안을 두고 저울질을 해봤을 때,

  • 1번 : unique 조건을 해제하고 코드를 추가하여 개발 유연성을 확보하면 상시적으로 시스템의 성능에 영향이 간다.
  • 2번 : unique 조건을 유지시키고, 회원 삭제시 데이터 원형을 변형시키는 방법을 하였을 때에는 추후 복구시 다시 데이터 원형을 살릴 수 있는 코드가 추가되어야 한다.
     

즉, 추가코드는 필수적이지만 상시적으로 해당 코드를 운용할 것인가? 간헐적인 사용시 해당 코드를 운용할 것인가의 문제로 귀결되면서 2번의 방법을 사용하기로 결정하였다.
그리고 롤백시 데이터 원형을 살릴 수 있으면서 알아보기 쉽도록 deleted.현재시간 이라는 규칙을 가진 string을 추가하여 변형된 email 주소를 업데이트 하기로 했다.
또한 이 방법은 추후 deleted_at 컬럼의 내용에 대한 백업 역할을 기대해볼 수도 있다.
 

이렇게 하면 추후에 .deleted 이후의 문자를 모두 날려버리는 간단한 아래 코드만으로 복구가 가능해진다.

const recoveredEmail = email.replace(/\.deleted\.\d+/, '');

 

이리하여 변경된 프로젝트의 코드는 다음과 같다.
 

변경 전

  // user.service.ts의 userDelete 함수 중, 
  // ... 이전 코드
  
  // 사용자 정보의 유효성 검사 함수를 불러온다.  
  await findUserInfoByUserId(userId);

  // transaction을 시작한다.
  const queryRunner = dataSource.createQueryRunner();
  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {
    // 사용자의 User entity를 삭제한다.
    await queryRunner.manager.softDelete(User, userId);

  // ... 이후 코드

 

변경 후

  // user.service.ts의 userDelete 함수 중, 
  // ... 이전 코드
  
  // 사용자 정보의 유효성 검사 함수를 불러온다.  
  const userInfo = await findUserInfoByUserId(userId);

  // transaction을 시작한다.
  const queryRunner = dataSource.createQueryRunner();
  await queryRunner.connect();
  await queryRunner.startTransaction();

  try {
    // 사용자의 email을 변경한다. 추후 해당 email의 재사용을 위한 고민중 230607 추가
    const email = `${userInfo.email}.deleted.${Date.now()}`;
    // 객체 리터럴 단축구문으로 email의 변경내용을 간략하게 표현한다. 230607 추가
    await queryRunner.manager.update(User, userId, { email });

    // 사용자의 User entity를 삭제한다.
    await queryRunner.manager.softDelete(User, userId);

  // ... 이후 코드

 

변경 후, 실행결과 출력

/images/Pasted%20image%2020230607050924.png