블랙홀에 빠진 유저 처리 - (1)

Siwon Choi·2022년 10월 20일
6

42Cabi-#Troubleshooting

목록 보기
1/3

Cabi는 42Seoul 카뎃(본과정 교육생)을 위한 서비스이다.

그렇기 때문에 42 자체 인트라넷인 42 intra를 통해 로그인이 가능한 유저만 서비스를 이용할 수 있도록 해야한다.

🔐 Cabi 서비스 인증 과정

PassportJSNodeJS 기반 어플리케이션에서 클라이언트가 서버에 요청할 자격이 있는지 인증할 때 주로 사용되는 모듈이자 미들웨어이다. 여러 서비스(카카오, 네이버, 구글 등)와 연계해서 서비스들의 인증 미들웨어로써 역할한다.
카카오 인증을 수행하는 모듈은 passport-kakao이고, 네이버 인증을 수행하는 모듈은 passport-naver이다.
42와도 연계하여 passport-42라는 모듈도 존재한다. Cabi는 바로 이 passport-42를 이용하여 인증을 수행한다.

passport-42를 사용하면 사용자가 Cabi 사이트에 로그인을 시도하면 자동으로 42 intra 로그인 페이지로 리다이렉트된다.
이곳에서 자신의 계정으로 로그인을 수행하여 인증에 성공하면 다시 Cabi 사이트로 리다이렉트되고, 이때 해당 사용자의 정보도 함께 받아온다.
Cabi에서 사용되는 사용자의 정보는 아래와 같다.

user_id: number; // 유저의 고유 식별 ID
intra_id: string; // 42에서 이용되는 유저의 고유 닉네임
email: string; // 42에 등록된 유저의 이메일

Cabi는 passport-42를 통해 42 intra 인증을 거친 유저만 사이트를 이용할 수 있도록 운영중이며 이때 받아온 정보는 Cabi DB에도 저장된다.

❓❗️문제 상황

🕳 블랙홀 시스템?

42에서는 Blackhole이라는 퇴학 시스템이 존재한다.
특정 날짜까지 과제를 통과하지 못한다면 블랙홀에 빠져 퇴학처리가 된다.
대신 과제를 통과하면 일정기한 블랙홀 날짜가 늘어나 목숨(?)이 연장된다.
퇴학 처리가 되면 약 1~3개월 후에 42 intra에서도 계정이 완전히 삭제된다.

42 intra와 Cabi는 서로 다른 서비스이다.

Cabi는 passport-42를 통해 42 intra에서 제공해주는 유저에 관한 정보인 user_idintra_id 등의 정보를 DB에 저장한다. 따라서 42 intra에서 관리하는 DB와 Cabi에서 관리하는 DB는 서로 다르다. 이 때문에 블랙홀에 빠져 퇴학처리가 된 유저의 정보가 Cabi DB에는 남아있게 된다.

그런데 블랙홀에 빠진 유저는 42 intra 로그인을 할 수 없기 때문에 Cabi 사이트에는 로그인이 불가능하다. 따라서 만약 해당 유저가 블랙홀에 빠지기 전에 대여하고 있던 사물함이 존재하고, 블랙홀에 빠지기 전에 사물함을 반납을 하지 않으면 영영 반납할 수가 없다.
이로 인해 사용되지 않은 채로 방치되는 사물함이 생기고, 해당 유저에게 연체 메일이 발송되는 문제가 발생했다..!

🪨 걸림돌

처음에 생각했던 해결 방법은 주기적으로 42 intra에 API 요청을 보내 블랙홀에 빠진 유저 정보를 확인하여 Cabi DB에서도 삭제하는 방법이다. 이때 해당 유저가 대여중인 사물함이 존재한다면 강제 반납 처리를 한다.

그런데 애초에 사실 유저가 DB에서 삭제된다는 상황 자체를 상정하지 않았었기 때문에, 만약 블랙홀에 빠진 유저를 강제 반납(대여중인 사물함이 있는 경우) 후 삭제 처리한다면 발생할 수 있는 문제가 있다.

어떤 유저의 대여 기록은 lent_log 테이블에 전부 담긴다.
Cabi는 이를 어드민 사이트에서 확인할 수 있도록 기능 구현이 되어있다.
그러나 이 테이블에는 user_id 필드만 존재하고 intra_id 필드는 존재하지 않는다. 어떤 유저의 대여 기록을 확인할 때는 user 테이블에 조인을 하여 판단하는데, 해당 유저가 user 테이블에서 삭제된다면 이제 더이상 해당 유저의 대여 로그는 확인이 불가능해진다.

그래서 만약 어떤 유저가 대여중인 사물함을 가지고 있고 이 사물함에 귀중품을 보관한 채로 블랙홀에 빠졌고, 블랙홀 처리 루틴이 동작해 해당 유저 정보가 삭제가 된다면, 나중에 이 유저가 데스크에 방문하여 보관하고 있던 물건을 찾더라도 어드민 사이트에서 본인 확인이 불가능해지는 문제가 발생할 수 있다.

따라서 유저를 DB에서 삭제 처리하는 것 자체가 문제가 일 수 있다는 고민도 들게 되었다...

그런데 ...
삭제를 하지 않는 것도 문제이다..❗️

♻️ 42 intra에서 삭제 처리된 유저가 사용하던 값은 재활용된다.

user_id: 4242
intra_id: sichoi

이런 유저가 42에 있다고 가정해보자.
이 유저가 블랙홀에 빠져 퇴학처리가 됐고, 42 intra DB에서도 삭제됐다고 가정해보자.
이때 3가지 문제 상황이 발생할 수 있다.

💡 user_id 재사용

  • 새로운 피씨너가 42 intra에 등록됐다. (본과정 교육생을 선발하기 위한 예비 과정을 Piscine이라고 하고 Piscine 참여자를 피씨너라고 부른다.)
  • user_id가 재사용되어 eunbikim이라는 intra_id를 가진 유저가 user_id를 4242로 할당받았다.
  • Cabi DB에서는 user_id가 primary_key로 설정되어 있다.
  • 이때 eunbikim이 42Cabi에 로그인한다.

이런 경우 primary_key 중복 에러가 발생할 것이다.
만약 값이 중복 에러 없이 삽입되었다면 그 자체로 Entity Integrity를 위반한 것이다.

💡 intra_id 재사용

  • intra_id는 교육생의 본명의 영어 이름을 따와서 자동으로 만들어진다.
  • 우연치않게 새로 들어온 유저가 sichoi라는 intra_id를 할당 받아 버렸다.
  • 그리고 이 유저는 user_id를 5000으로 할당받았다.
  • 그렇다면 Cabi DB에는 아래 두 명의 유저가 존재한다.
{
	user_id: 4242
	intra_id: sichoi
},
{
	user_id: 5000
    intra_id: sichoi
}

그런데 42 intra에서는 동시에 2명 이상의 동일한 intra_id를 가진 유저는 존재하지 않고, 카뎃이나 직원 관계자들도 당연히 없다고 여긴다.
어처피 블랙홀에 빠진 유저는 로그인을 못하니 Cabi 메인 서비스 홈페이지에서는 사용자 입장에서 크게 혼란이 갈만한 부분이 없긴 하지만 Cabi 어드민 서비스 홈페이지에서는 얘기가 다르다.

Cabi 어드민 서비스는 클러스터(42 교육장 이름)에 계시는 인포 데스크 직원분들이 캐비넷과 이용자 관리를 할 수 있도록 Cabi 팀에서 제작한 서비스이다.
intra_id로 특정 사용자의 대여 기록 등을 조회할 수 있도록 하는 기능이 있는데, 이때 같은 intra_id를 가진 2명 이상의 유저의 정보가 모두 나오게 될 것이므로 관리자분들이 혼란을 겪을 수 있다.
이 역시 일종의 Business Integrity를 위반한 것이다.(intra_id는 고유한 것이라는 약속을 지키지 않은 것이므로)

💡 user_id와 intra_id 모두 재사용

가능성은 매우매우 낮겠지만 이런 경우도 존재할 것이다.
새로 들어온 피씨너가 user_id로 4242를 할당받고, intra_id로 sichoi를 할당받았다.
이 유저가 Cabi 홈페이지에 로그인하면 Cabi 서버는 기존에 블랙홀에 빠진 유저와 동일한 유저로 판별할 것이다.

Cabi에는 특정 유저가 대여한 기록을 저장하는 lent_log 테이블이 있고, 사물함을 대여한 기한을 넘어서 연체한 사람들에게 패널티를 주기 위한 목적으로 사용되는 ban_log 테이블이 존재한다.
새로 들어온 유저는 자신이 빌리지도 않은 사물함의 대여 기록을 가지게 되고, 자신은 연체한 적이 없음에도 기존 유저가 연체 내역을 가지고 있었다면 그 유저가 저질렀던 잘못의 대가를 대신 치르게 되는 어이 없는 문제가 발생할 것이다... 🥲

🤔 해결 방안 모색

앞에서 블랙홀에 빠진 유저를 단순히 삭제를 하는것도 문제고 삭제를 하지 않고 방치하는 것도 문제가 있었다는 것을 알게 되었다. 그러나 분명 무언가 조치는 필요한 상황이다...

그래서 해결 방안으로 내세운 것이 블랙홀에 빠진 유저의 정보를 변경시키는 것이었다.
예를들어 intra_id가 sichoi이고 user_id가 4242인 유저가 블랙홀에 빠지게 되면
해당 유저의 intra_id를 [blacokholed]sichoi로 user_id를 -4242로 변경하는 것이다.
이렇게 하면 user_id로 4242를 재할당 받거나 intra_id로 sichoi를 재할당 받은 유저가 Cabi에 로그인하더라도 기존 유저와 구별이 가능하기 때문에 위에서 언급했던 문제가 발생하지 않을 것이다.

만약 새로 들어온 유저도 블랙홀에 빠지는 경우가 존재할 수도 있기는 하지만 시간이 충분히 많이 지났을 터라 이제는 정말로 앞서서 블랙홀에 빠졌던 유저를 삭제 처리 하면 문제가 없다.

이에 따라 아래와 같이 블랙홀 처리 루틴을 만들어두었다.

유저가 몰리지 않는 매일 새벽 2시에 동작하는 Cron 스케줄러를 달아두었다.
2시가 되면 DB에 저장된 모든 유저에 대해 42 intra API를 통해 해당 유저의 정보를 GET 요청해온다.
이때 블랙홀에 빠진것으로 판단되는 유저의 user_id와 intra_id를 업데이트 한다.

😇 그러나...

생각치 못한 치명적인 문제가 존재했다.

앞에서 유저의 대여 기록은 lent_log 테이블에 전부 담기고, Cabi는 이를 어드민 사이트에서 확인할 수 있도록 기능 구현이 되어있다고 언급했었다.
그러나 sichoi라는 유저가 블랙홀에 빠져 블랙혹 처리 스케줄러에 의해 intra_id가 [blackholed]sichoi로 변경된 상태에서, 어드민 사이트에서 sichoi라는 검색어로 검색을 하더라도 검색 결과가 나오지 않는다. DB에서 검색을 할 때 string 값이 정확히 일치하는 경우에만 결과가 나오게 설정되어 있기 때문이었다.
검색어가 부분 일치되는 결과를 모두 뿌려주면 해결되는 문제가 아닌가 생각을 했지만 단순한 문제가 아니었다.

profile
올바로 작동하지 않는다고 걱정하지 마라. 모든 게 잘 되었다면, 내가 할 일이 없어진다.

1개의 댓글

comment-user-thumbnail
2022년 10월 21일

와우 흥미진진하군여 지속적인 개선과정이 궁금합니다!

답글 달기