동시 대여/반납 문제

Siwon Choi·2022년 11월 7일
0

42Cabi-#Troubleshooting

목록 보기
3/3
post-thumbnail

🪨 개요


해당 문서는 Cabi 서비스의 핵심이라고 할 수 있는 사물함 대여/반납 기능을 구현중 트랜잭션 관리 문제로 발생했던 이슈와 그 해결과정을 정리한 것이다.

Cabi 서비스는 기본적으로 다수의 사용자가 존재한다. 즉, 한 번에 여러명이 동시에 요청을 하는 경우가 발생할 수 있다. 그래서 아래 2가지는 반드시 지켜져야 한다.

  1. 동시에 여러 사용자가 대여나 반납을 수행한다고 하더라도 동작이 서로간 중첩되어선 안된다. 먼저 들어온 요청을 모두 처리하고 난 후 다음 요청을 처리해야 한다.
  2. 하나의 요청을 처리하다가 문제가 발생(대여에는 성공했는데 캐비넷의 상태 변경에 실패 등)했을 때 데이터의 무결성을 지키기 위해 해당 트랜잭션 자체를 롤백시켜야 한다.

사물함의 상태에 관한 자세한 설명은 아래 링크에서 확인하실 수 있습니다.
https://www.notion.so/hyunja/a947c2498af543e4ba4b9f7ac3c2ff4f

따라서 대여/반납하나의 트랜잭션 단위로 동작되도록 구현하였으며 시스템의 대여 절차는 다음과 같다.

기존 대여 절차

도표에는 생략된 절차 (공유 사물함 인원이 다 찰 경우, 대여 이후 캐비넷의 상태를 SET_EXPIRE_FULL로 업데이트 하는 등)가 있지만, 핵심 로직은 위와 같다.

처음 대여, 반납을 설계할 때에는 트랜잭션에 대한 이해가 부족해 위의 절차(와 생략된 부가 절차) 를 단순히 하나의 트랜잭션으로 묶어서 처리하면 위에서 적은 1번과 2번을 준수하며 문제 없이 처리될 것으로 기대했었다.

그러나…

🫠 동시 대여시 문제 발생


기능 구현 후 실제로 테스트 해 본 결과 공유 사물함을 대여할 때 제한된 인원수보다 많은 인원이 한 사물함을 대여할 때 인원수를 초과해 대여하는 경우가 발생하였다.

위 사진처럼 3명이 이용 가능한 공유 사물함에 동시에 6명이 하나의 사물함을 대여하는 요청을 날릴 때 6명 모두 다 대여가 된 것이다..!
이러한 문제가 발생한 원인을 파악하다 보니, 단순히 대여의 과정을 하나의 트랜잭션으로 묶는다면 아래와 같은 문제가 발생할 수 있다는 것을 알게 됐다.

위 상황은 클라이언트 1, 2가 잔여 자리가 하나만 남은 사물함을 동시에 빌릴 때 발생할 수 있는 상황이다.

  • 클라이언트 1의 트랜잭션이 커밋되기 전에, 클라이언트 2가 대여하고자 하는 사물함 이용자 수를 조회한다.
  • 이 이용자 수에는 클라이언트 1이 포함되어 있지 않기 때문에 클라이언트 2는 잔여 자리가 있는 것으로 판단한다.
  • 클라이언트 1의 트랜잭션이 커밋된다.
  • 클라이언트 2에서 lent 테이블에 값을 추가하고 해당 트랜잭션이 커밋된다.
  • 정원보다 많은 이용자가 사물함을 대여하게 된다..!

단순히 어떠한 작업을 하나의 트랜잭션으로 묶으면 여러 요청이 동시에 들어오더라도 독립적으로 처리할 것으로 기대했지만, 실제로는 그렇게 간단한 일이 아니었다...
조사 결과 Cabi팀이 기대하는 대로 트랜잭션을 독립적으로 구성하기 위해서는 트랜잭션 격리 수준이라는 것을 설정해야 한다는 것을 알게되었다.

팀원 sichoi님께서 관련 내용을 조사하여 이슈로 남겨주셨습니다.
https://github.com/innovationacademy-kr/42cabi/issues/474#issuecomment-1287243004

📖 트랜잭션의 격리 수준


ACID 원칙

트랜잭션의 원칙 중 ACID라는 것이 있다. ACID 원칙은 다음과 같다.

  • Atomicity
    트랜잭션 내의 작업이 일부만 성공하는 일이 없도록 보장하는 원칙이다.
    ex) 사물함의 정원만큼 대여가 발생했는데 사물함의 상태가 SET_EXPIRE_FULL 로 설정이 되지 않는 경우는 없어야 한다.

  • Consistency
    트랜잭션이 끝날 때 정책적으로 정한 DB의 제약 조건에 부합하는 상태를 보장하는 원칙이다.
    ex) 2명이 대여중인 사물함에 3번째 사람의 대여 요청 트랜잭션이 커밋되면 해당 사물함의 상태는 SET_EXPIRE_FULL 로 설정되어야 하고, 그 때 모든 대여자의 만료 시간이 설정되어야 한다.

  • Isolation
    트랜잭션이 진행되는 도중의 데이터를 다른 트랜잭션에서 Read할 수 없도록 보장하는 원칙이다.
    ex) 아직 대여가 진행중인데 다른 트랜잭션에서 해당 사물함을 대여중인 인원을 읽어서는 안된다.

  • Durability
    트랜잭션이 성공했을 경우 해당 결과의 영속성이 보장되어야 하는 원칙이다.
    ex) 대여라는 트랜잭션이 성공한 경우 서비스가 갑작스럽게 멈추거나 장애가 생기더라도 대여 결과가 영구적으로 적용되어야 한다.

Isolation Level(격리 수준)

하지만 ACID 원칙을 타이트하게 지키면 퍼포먼스가 떨어지므로 Database Engine은 보통 트랜잭션의 격리 수준을 여러 단계로 지원한다. 때문에 사용 목적에 맞게 트랜잭션의 격리 수준을 유동성 있게 적용해야 한다.

표준으로 정의된 트랜잭션의 격리 수준은 다음과 같다. (모두 InnoDB 기준입니다)

  • READ UNCOMMITTED
    트랜잭션 내에서 SELECT 쿼리 실행시 다른 트랜잭션에서 커밋되기 전 업데이트 된 데이터를 가져올 수 있다. 트랜잭션에 문제가 생겨 업데이트 내용이 롤백이 될 경우 DIRTY READ 현상이 발생할 수 있다.

  • READ COMMITTED
    트랜잭션 내에서 SELECT 쿼리 실행시 커밋이 완료된 결과만 조회할 수 있다.
    그렇기 때문에 DIRTY READ 현상이 발생하지 않는다. 커밋이 완료되기 전의 기록이 담길 수 있는 실제 테이블에서 값을 가져오지 않고 UNDO 영역에 백업된 데이터를 가져오기 때문이다.
    이 격리 수준에서는 PHANTOM READ가 발생할 수 있다.

  • REPEATABLE READ
    하나의 트랜잭션에서 SELECT 쿼리 실행시 그때 당시의 조회 결과를 해당 트랜잭션의 커밋이 끝날 때까지 유지한다. 해당 트랜잭션에서 읽어오는 값은 다른 트랜잭션의 영향을 받지 않는다.
    ex) 하나의 트랜잭션에서 1번 사물함을 대여중인 유저의 수를 2라고 읽었다면 몇 번을 다시 조회해도 트랜잭션이 종료되기 전까지는 2라고 읽는다.

    이 격리 수준에서는 PHANTOM READ가 발생하지 않는다.

  • SERIALIZABLE
    하나의 트랜잭션에서 SELECT 쿼리 실행시 그 트랜잭션이 끝날때까지 다른 트랜잭션에서 SELECT 쿼리를 실행한 테이블에 업데이트를 못하게 막는다.

    이 격리 수준에선 S LockX Lock 이라는 것이 있다.

    • S Lock : Shared Lock의 줄임말로, Read만 가능하게 Lock을 거는 것이다.

    • X Lock : Exclusive Lock의 줄임말로, 특정 트랜잭션에서 데이터를 변경할 때 다른 트랜잭션에서 데이터를 읽거나 쓰지 못하게 Lock을 거는 것이다. (일종의 뮤텍스)

      SELECT 쿼리 수행 시, S Lock이 걸리게 되고, UPDATEINSERT 쿼리 수행 시, X Lock이 걸리게 된다.

      해당 격리 수준에서는 Lock이라는 기능 때문에 잘못된 쿼리를 짜면 데드락이 발생할 수 있다.

격리 수준을 설정하지 않으면 Database Engine의 default로 설정된 격리 수준을 따르게 된다.
Cabi팀은 MariaDB를 사용했기 때문에 MariaDB의 기본 격리 수준인 REPEATABLE READ로 설정이 됐었던 것이었다.

🤔 문제 해결 과정


🫠 SERIALIZABLE 설정

위의 격리수준 단계가 있다는 것을 팀원들끼리 공유 및 인지 후, 여러 트랜잭션이 발생할 때 커밋 반영 전 데이터를 가져오기 때문에 문제가 발생했던 것으로 확인했다.

따라서 트랜잭션끼리 순서대로 처리할 것을 명시하는 의도로 SERIALIZABLE 격리 수준을 적용하였다.
이 격리 수준을 적용하면 동시에 여러 요청이 들어오게 되면 요청이 조금이라도 빨리 들어온 순서대로 트랜잭션을 구성하고, 공유 자원에 Lock을 걸 것이기 때문에 다른 트랜잭션의 요청이 중간에 끼어들지 않을 것이라고 생각했다.

즉, 정원보다 많은 인원이 사물함을 대여하는 일은 발생하지 않을 것이라고 판단했다. 실제로 수동으로 여러 클라이언트가 하나의 사물함을 대여할 때 테스트를 수행하면 정상적으로 동작하는 것으로 보였다…!

하지만 동일한 테스트를 몇번 더 수행해보니 이전과는 다른 에러가 발생하였다. 이전에는 논리적으로 문제 가 발생했었지만 조치 후 발생한 문제는 위에서 우려했었던 데드락이 발생 에러였다...

🫠 대여 로직 리팩토링

데드락이 발생한 상황 하에서 실행했던 쿼리 로그를 살펴볼 때 처음 생각했던 문제는 다음과 같다.

격리 수준은 적절하게 정리하였는데 트랜잭션이 균일하게 적용되지 않고, 중복된 쿼리문이 많아 문제가 발생한 것이다.

이렇게 판단한 이유는 아래 2가지이다.

  1. 디버깅 과정에서 사물함 상태나 유저 상태를 가져올 때 불필요하게 중복으로 데이터를 가져오거나 불필요한 테이블 조인을 하게 구성이 되어있었던 것을 확인했다.

  2. 또한 Cabi팀은 기획 단계에서 Domain Driven 방식으로 설계하고자 했기 때문에 최대한 기능을 잘게 쪼개어 Lent 모듈에서 User, Cabinet 모듈을 호출하는 방식으로 기능 구현을 했다.
    이 때문에 하나의 Repository 메소드에서 모든 대여 로직이 처리되는 것이 아닌 다른 Service 메소드를 호출하는 방식으로 로직이 동작했다.

팀원 joopark님의 의견으로 대여 처리는 Domain 단위로 분할하는 것을 조금 포기하되, 원활한 트랜잭션 관리를 위해 최대한 Lent 모듈에서 대여를 처리하고 다른 모듈에서 불러오는 메소드가 불필요한 테이블 조인을 할 경우 해당 메소드는 새로 만드는 것으로 조치를 하였다. 그리고 중복된 테이블 조회와 핸들링되지 않는 예외상황 처리 등의 조치를 하였다.

위와 같이 대여 로직을 리팩토링하고 나서 다시 테스트를 해 본 결과 문제가 발생하지 않는 것으로 보였다.

🚨 동시 반납시 문제 발생

사물함 반납 과정에 대해서도 SERIALIZABLE 설정을 하고 나니 새로운 문제가 발생하였다. 동시에 여러 사용자가 동시에 반납을 하도록 테스트를 하였더니 약 10번 중 1번 꼴로 데드락이 발생했다...

팀원들끼리 의아해 했던 점은 대여와 반납 모두 동일하게 SERIALIZABLE 처리를 하였는데 반납에 대해서만 데드락이 발생했다는 점이었다.
따라서 동시에 대여/반납 요청을 할 수 있도록 스크립트를 작성하고, 다시 동시에 대여 요청에 대해 테스트를 진행하니, 대여를 할 때도 데드락이 발생했다. 위에서 진행했던 대여 로직 리팩토링으로는 문제를 해결하지 못했던 것이었다.
이 때문에 Cabi팀은 SERIALIZABLE 격리 수준에 대해 다시금 고민을 하게 되었다..!

🔎 원인은 S Lock과 X Lock

문제 직면 후 앱 단에서 문제를 재현하는 것이 아닌 실제로 실행된 쿼리문을 3개의 클라이언트에서 트랜잭션을 적용하여 실행해 보는 실험을 진행했다.

실험 결과, 데드락이 발생하는 조건은 다음과 같다.
1. 클라이언트 1이 사물함을 대여중인 인원 확인을 위해 lent 테이블에 S Lock
2. 클라이언트 2이 사물함을 대여중인 인원 확인을 위해 lent 테이블에 S Lock
3. 클라이언트 1이 lent 테이블에 insert를 하기 위해 X Lock 시도 (S Lock이 걸려있으므로 대기 상태에 빠짐)
4. 클라이언트 2이 lent 테이블에 insert를 하기 위해 X Lock 시도 (S Lock이 걸려있으므로 대기 상태에 빠짐)
5. 데드락 발생!!!

두개의 클라이언트가 하나의 대상에 대해 S Lock을 건 상태에서, 두 클라이언트 모두 X Lock을 시도하여 모두 대기 상태에 빠졌기 때문에 이러한 일이 발생했다. 이를 변환 교착(Conversion Deadlock)이라고 한다.

🫠 Read에서도 X Lock을 걸도록 수정

InnoDB의 Lock

  • Row-level Lock
    각 테이블의 row 마다 걸리는 행 단위 Lock이다.
    - S Lock(Shared Lock)
    Read에 대한 Lock
    SELECT … FOR SHARE 로 Read 작업을 수행할 때 각 row에 S Lock을 건다.
    - X Lock(Exclusive Lock)
    Write에 대한 Lock
    SELECT … FOR UPDATE, UPDATE, DELETE 등의 Write 작업을 수행할 때 각 row에 X Lock을 건다.

Lock과 관련된 규칙은 다음과 같다.

1. 여러 트랜잭션이 동시에 한 row에 S Lock을 걸 수 있다. (여러 트랜잭션이 동시에 하나의 row를 읽을 수 있음)
2. S Lock이 걸려있는 row에 다른 트랜잭션이 X Lock을 걸 수 없다. (다른 트랜잭션이 read하고 있는 row에 대해 write 작업을 할 수 없음)
3. X Lock이 걸려있는 row에 다른 트랜잭션이 S Lock/X Lock을 걸 수 없다. (다른 트랜잭션이 write하고 있는 row에 대해 read/write 작업 모두 불가)
  • Record Lock
    row 단위로 lock이 걸리는 것이 아닌 DB의 Index Record에 걸리는 lock이다.
    Row-level Lock에서와 마찬가지로 S LockX Lock이 존재한다.

SERIALIZABLE 격리 수준에서는 Row-level Lock이 걸린다.
클라이언트 1에서 SELECT 문으로 사물함을 대여중인 인원수를 얻기 위해 lent 테이블을 조회할 때 해당 테이블의 모든 row에 S Lock을 걸게 된다.
따라서 클라이언트 2도 클라이언트 1의 요청이 끝나기 전에 lent 테이블의 모든 row에 S Lock을 걸어 접근할 수 있다.
이와 같이 또다른 트랜잭션에서 lent 테이블에 접근이 가능했던 것이 변환 교착을 일으키는 원인이었다.

따라서 중간에 다른 트랜잭션이 접근하지 못하게 막도록 SELECT .. FOR UPDATE 쿼리문을 이용하여 lent 테이블에 Lock을 걸 때 X Lock을 걸도록 수정하였다.

🫠 Read에서도 X Lock을 걸었지만...

select 쿼리문을 실행할 시 X lock이 걸리도록 명시해주었기 때문에 다른 트랜잭션에서 동일한 row를 접근 하지 못하게 lock이 걸릴 것을 기대했으나 아래와 같은 상황이 발생했다.

아래와 같이 user_id=1인 유저 joopark가 cabinet_id=1인 공유 사물함을 대여 중이고 해당 lent_id=1이고,
user_id=2인 유저 sichoi가 cabinet_id=1 인 공유 사물함을 대여 중이고 해당 lent_id=2이고,

user_id=3인 유저 eunbikim이 cabinet_id=1인 공유 사물함을 대여 중이고 해당 lent_id=3이라고 가정하겠다.

테이블의 상태는 아래와 같다.

User 테이블

user_idintra_id
1joopark
2sichoi
3eunbikim

Lent 테이블

lent_idlent_user_idlent_cabinet_id
111
221
331

Cabinet 테이블

cabinet_id
1

이때 쿼리문이 수행된 결과는 다음과 같다.

  1. A 트랜잭션에서 user_id=1인 유저가 빌린 lent와 cabinet 정보를 조회한다.
    🔐 A 트랜잭션에서 lent_id=1인 row에 X Lock을 건다.
    🔐 A 트랜잭션에서 cabinet_id=1인 row에 X Lock을 건다.

  2. A 트랜잭션에서 cabinet_id=1인 사물함을 대여중인 lent의 개수를 조회한다.

    A 트랜잭션에서 lent_id=1인 값을 확인한다.

    🔐 A 트랜잭션에서 lent_id=2인 row에 X Lock을 건다.

    A 트랜잭션에서 lent_id=2인 값을 확인한다.

    🔐 A 트랜잭션에서 lent_id=3인 row에 X Lock을 건다.

    A 트랜잭션에서 lent_id=3인 값을 확인한다.
    A 트랜잭션에서 lent의 개수로 3을 얻는다.

  3. B 트랜잭션에서 user_id=2인 유저가 빌린 lent와 cabinet 정보를 조회한다.
    ⛔️ A 트랜잭션에서 lent_id=2인 row에 X Lock을 걸었으므로 unlock할 때까지 기다린다.

  4. C 트랜잭션에서 user_id=3인 유저가 빌린 lent와 cabinet 정보를 조회한다.
    ⛔️ A 트랜잭션에서 lent_id=3인 row에 X Lock을 걸었으므로 unlock할 때까지 기다린다.

  5. A 트랜잭션에서 lent_id=1인 값을 삭제하고 반납 프로세스를 처리한 다음 commit한다.
    🔓 A 트랜잭션에서 lent_id=1, 2, 3인 row에 걸었던 lock을 해제한다. 
    🔓 A 트랜잭션에서 cabinet_id=1인 row에 걸었던 lock을 해제한다.

  6. lent_id=3인 row와 cabinet_id=1인 row에 걸린 lock이 해제되었으므로
    user_id=3인 유저가 빌린 lent와 cabinet 정보를 조회하기 위해
    🔐 C 트랜잭션에서 cabinet_id=1인 row에 X Lock을 건다.
    🔐 C 트랜잭션에서 lent_id=3인 row에 X Lock을 건다.

  7. B 트랜잭션에서 user_id=2인 유저가 빌린 lent와 cabinet 정보를 조회하기 위해
    🔐 B 트랜잭션에서 lent_id=2인 row에 lock을 건다.
    ⛔️ C 트랜잭션에서 cabinet_id=1인 row에 X Lock을 걸었으므로 unlock할 때까지 기다린다.

  8. C 트랜잭션에서 cabinet_id=1인 사물함을 대여중인 lent의 개수를 조회한다.

    ⛔️ B 트랜잭션에서 lent_id=2인 row에 X Lock을 걸었으므로 unlock할 때까지 기다린다.

7번에서 B 트랜잭션은 C 트랜잭션이 cabinet_id=1인 row에 걸었던 X Lock을 해제할 때까지 기다리고,

7번에서 C 트랜잭션은 B 트랜잭션이 lent_id=2인 row에 걸었던 X Lock을 해제할 때까지 기다리므로 deadlock이 발생한다...!

SERIALIZABLE 레벨에서는 SELECT가 자동으로 FOR SHARE, 즉 S Lock이 되기 때문에 UPDATE와 같은 수준의 X Lock 을 걸어주면 해결될거라 생각했었다.
하지만 이처럼 발생할 수 있는 경우를 충분히 고려하지 않으면 단순히 X Lock을 거는 것만으로는 데드락 문제를 피할 수 없었다...

문제 상황을 다시 겪은 후 팀원들끼리 기존 코드를 보며 로직에는 문제가 없는지 확인했다.
그 결과 lent 테이블은 다른 테이블과의 조인이 불필요하게 많이 일어난다는 사실과
꼭 트랜잭션 내에 포함되지 않아도 되는 쿼리문이 있다는 사실을 깨닫게 되었고,
트랜잭션 내에서 동일한 row를 한 번만 접근하여 정보를 가져올 수 있는 방법에 대해서도 생각해낼 수 있었다.

🤔 근본적인 문제 해결을 위한 해결책들

해당 문제를 해결하기 위해서 근본적인 해결책이 필요하다 판단해 다음과 같은 대안들을 생각해냈다.

대안

  • 반납 혹은 대여 로직에 대해 변경하는 테이블에 대해 삽입하기 전 조회를 하지 않기

    • 해당 방법은 현실성이 부족하다. 현재 상태를 알아야 동작에 따라 다음 상태를 정의할 수 있기 때문이다.
  • SQL 키워드 활용(eunbikim님이 고안해주신 방법)
    변환 교착을 방지할 수 있는 SQL 키워드를 사용하는 방법이다.

    • 장점은 기존 로직의 변경을 줄이고 적용할 수 있다.
    • 단점은 SQL에 의존적이며 ANSI SQL이 아닌 경우 추후 RDB를 바꿀 일이 생길 때 문제의 여지가 있으며, 현재 ORM을 사용중인데 관련 키워드를 ORM에서 제공해야 한다.
  • lent 테이블 접근 순서 변경

    • 현재 방식
      user_id를 받아 lent 테이블에서 cabinet을 찾아가는 순서
    • 변경 순서
      1. 입력받은 cabinet_id에 해당하는 모든 lent row에 X Lock을 건다.
      2. Lock을 건 row들 중에서 입력받은 user_id와 일치하는 row를 반납 처리한다.

SQL 키워드를 이용하여 교착 상태에 빠지지 않게 방지하는 방법
https://kuaaan.tistory.com/100

💡 최종 해결 방법

  • 트랜잭션 단위를 최소화
    대여중인지 아닌지는 lent 테이블에서 대여중인 cabinet_id를 통해 판단하고, 판단하는 로직은 트랜잭션 밖으로 빼낸다.
  • 불필요한 테이블 조인 최소화
    캐비넷 정보를 가져올 때 불필요한 user 테이블에도 조인을 하지 않도록 수정한다.
  • 트랜잭션 내에서 테이블 조회 최소화
    트랜잭션 내에서 딱 한 번만 조회가 발생하고, 조회를 할 때 반납에 필요한 모든 자원에 X Lock을 걸도록 한다.

자세한 도표는 아래와 같다.

유저가 대여중인 캐비넷이 있는지 확인을 트랜잭션 밖에서 진행하고 대여중인 사물함이 존재하면 해당 cabinet_id를 얻게 된다.

여기서 얻은 cabinet_id를 바탕으로 트랜잭션 안에서 단 한 번만 SELECT 문으로 조회한다.

이때 cabinet_id에 해당하는 row와, 해당 캐비넷을 대여중인 모든 lent_id에 해당하는 row에 X Lock이 걸린다. (위에서 설명한 예시로 하면 cabinet_id=1인 row, lent_id=1, 2 ,3인 row에 X Lock이 걸린다.)
트랜잭션 내에서 조회가 한 번만 발생하기 때문에 특정 row에 Lock을 건 상태에서 다른 row에 걸린 Lock이 해제되기를 기다리는 상태가 발생하지 않게 된다.

대여 로직도 비슷한 방식으로 수정하니 이제 정말 대여/반납 과정에서 개요에서 언급했던 반드시 지켜야할 2가지 를 지킬 수 있게 되었다.

  1. 동시에 여러 사용자가 대여나 반납을 수행한다고 하더라도 동작이 서로간 중첩되어선 안된다. 먼저 들어온 요청을 모두 처리하고 난 후 다음 요청을 처리해야 한다.
  2. 하나의 요청을 처리하다가 문제가 발생(대여에는 성공했는데 캐비넷의 상태 변경에 실패 등)했을 때 데이터의 무결성을 지키기 위해 해당 트랜잭션 자체를 롤백시켜야 한다.

📔 이슈를 해결하면서 느낀점


sichoi

내가 사용하고 있는 기술에 대해서 정확히 이해하고 사용하는게 얼마나 중요한 것인지 깨닫게 된 것 같습니다.
데이터베이스와 트랜잭션에 대해서 거의 이해하지 못한 상태로 사용법만 익히고 접근하니, 예상하지 못한 문제가 발생했고 그 발생 원인 조차 제대로 파악하기가 어려웠습니다.

뿐만 아니라 발생한 원인을 모른 채로 이런 문제에 직면했을 때 혼자였으면 문제 원인 파악 및 해결을 위해 굉장히 오랜 시간을 쏟았어야 할 것입니다.

하지만 같이 문제 원인을 파악할 동료가 있고 서로 이 문제를 해결하기 위해 필요한 개념들을 공유하다보니 빠르게 이슈를 해결할 수 있었던 것 같습니다! 꼭 이 이슈 해결 뿐만 아니라 프로젝트를 같이 진행하면서 정말 많은 도움을 받은 것 같아 항상 감사합니다 ㅎㅎ 🥹

joopark

트랜잭션이라는 것이 알아서 격리를 잘 해줄것이라는 막연한 믿음을 가지고 있었습니다. 아마 트랜잭션의 격리 수준과 같은 내용은 책에서 훓어 지나갔던 거 같았습니다. 당시엔 그런게 있구나 정도로 넘어갔던 것으로 기억합니다.

실제로 여러 사용자가 접근해 DB의 내용을 수정하는 사례를 마주치고, 엄밀한 설계 없이 기능을 구현하여 버그가 발생함을 확인하니 기본기가 중요하다라는 생각을 다시금 깨닳게 되었습니다.

그리고 문제가 발생할 때 문제에 대한 로그 기록을 남기는 일이 중요하다고 생각했습니다. 만약 쿼리 로그를 남기지 않았다면 디버깅할 때 원인을 찾기 어려웠을 것이었습니다. 문제 해결 방식에 대해서도 다시금 생각해보게 되었습니다. 어떤 현상이 발생할 때 그 현상에 대한 원인을 탐구하는 방식에 대해 고민하게 되었습니다.

그리고 좋은 팀원들과 디버깅을 하며 여러 지식들을 공유하게 되서 좋았습니다! 😁

eunbikim

이 문제를 접하기 전에는 트랜잭션에 대해서도 완벽한 이해 없이 도입하고, 코드 리뷰를 했던 것 같습니다. 하지만 이런 문제가 생김으로 인해 팀원분들과 트랜잭션에 대해(격리 수준 등)에 대해 내용을 정리해 문서로 남기거나 직접 설명해주면서 이해도가 높아지고, 필요한 부분에 제대로 도입을 할 수 있게 되었습니다.

혼자서 문제를 해결할 때는 제가 생각하는 방법으로만 문제를 해결했겠지만, 팀원분들과 회의하며 문제 해결을 진행하니 여러 아이디어가 나오고 어떤 방법이 가장 최적일지에 대해 생각하며 문제를 해결하다 보니 문제에 대해 더 깊게 파악하고 좋은 방법을 찾을 수 있었습니다.

실제 사용자가 있는 사이트다 보니 상당히 다양한 경우에 대해 처리해야 하는 부분이 많았는데 이를 통해 많이 배울 수 있었고 테스트의 중요성도 알 수 있었습니다.

열정 있고 실력 좋은 팀원들과 함께하여 제가 혼자 했더라면 적당한 수준에서 타협했을 문제들을 제대로 해결하고 다양한 지식과 경험을 얻을 수 있었습니다! 감사합니다👍

✏️ Written By...

joopark
sichoi
eunbikim

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

0개의 댓글