본문 바로가기

컴퓨터공학

Cascade에 대하여...

나는 처음 Spring JPA를 공부하다 다음과 같은 말들 들어본적이 있다.

cascade 속성을 설정해놓으면 오류가 발생하지 않는다.

나도 위 말은 신봉(?)했고, 아무 생각없이 모든 엔티티의 cascade 속성을 ALL로 해놓는 대참사가 일어났다. 이번 포스팅에서는 cascade 속성의 본질에 대해 이야기하고자 한다. cascade 속성이 왜 필요하고, 어떨때는 사용하면 안되는지 내 사례를 통해 이야기하겠다.


사례(Case)

오늘 포스팅에 참고할 데이터베이스의 구조는 위와 같다.

일단 팀(Team)은 리그(League)에 참가한다. 이때 팀은 여러개의 리그에 참가할 수 있고, 리그는 여러개의 팀을 가질 수 있다. 즉, 팀 테이블(엔티티)과 리그 테이블(엔티티)은 다대다 관계인데 이를 일대다-다대일 관계로 풀어주는 것이 기록(record) 테이블이다. 또한 기록테이블은 리그에서 각 팀의 전적(승패) 데이터를 가지고 있다.

 

---

참조적 무결성 (referential integrity)

Cascade에 대해 자세히 알아보기 이전에, DB의 참조적 무결성에 대해 알아보자. DB의 참조적 무결성은 2개의 관계되어 있는 테이블(또는 엔티티)사이의 일관성을 의미한다. 보통 두 테이블은 상위-하위의 관계가 형성되며 하위 테이블은 기본기 또는 후보키로 외래키를 강제한다.특히, 상위테이블에서 데이터를 삭제하려 할 경우, 참조적 무결성에 큰 문제를 발생시킨다.

 

예시를 통해 보면, 팀(team)은 여러개의 전적(record)을 가질 수 있다. 즉, 팀 테이블과 전적 테이블은 서로 관계를 가지고, 상위테이블은 팀 테이블이 되고, 하위 테이블은 전적 테이블이 된다. 이때 팀을 삭제했다고 해보자. 그러면 그 팀과 연관된 전적을 어떻게 될까? 전적 테이블에 저장된 외래키는 어떻게 될까? 위에서 언급했던 하위 테이블의 외래키를 강제 적용하는 무결성을 해치게 된다. 즉, DB는 참조적 무결성을 지키기위해 보통 상위테이블의 데이터를 삭제를 하는 것을 강제로 막는다.

 

상위 테이블의 데이터를 삭제하는 방법

DB는 참조적 무결성을 지켜야 한다. 하지만 우리는 때때로 상위테이블 정보를 삭제할 필요가 있다. 

일단 전제는 DB는 참조적 무결성을 지키기 위해 하위 테이블의 외래키를 강제한다는 것이다. 그리고, 당연히 참조할 수 없는 외래키를 가질 순 없다.

(물론, 전략에 따라 외래키의 null 값을 허용할 순 있다. 하지만 나의 사례에서는 불가능하다. 팀이 없는 전적데이터는 존재할 수 없다.)

 

그렇다면, 유일한 방법은 상위 테이블의 데이터를 삭제하면서 해당 데이터와 연관된 모든 하위테이블의 데이터를 지우는 것이다.

 

team table (삭제전략 : cascade)

id name  
1 team1  
2 team2  
3 team3 delete

 

record table

id teamId (fk) leagueId (fk) win loss  
1 1 ... 1 0  
2 2 ... 2 1  
3 3 ... 1 2 delete
4 3 ... 1 3 delete

다시 팀-전적 테이블을 보자 만약 team3을 삭제한다고 해보자. 그러면 record table에서 team3과 연관된 모든 전적을 모두 삭제해야 한다. 이러한 삭제전략을 cascade 전략이라고 한다.

 

cascade는 위험하다.

jpa는 상위 엔티티에 cascade옵션을 통해 삭제 전략을 cascade로 지정할 수 있다. 이렇게 되면 삭제 과정에서 아무런 오류도 발생하지 않는다. 다시 말하지만 삭제 과정에서 아무런 오류도 발생하지 않는다.

삭제는 그 자체로 위험하다. 데이터가 전부 지워지는 것이다. 아래와 같은 상황을 보자.

 

팀을 삭제한다고 해보자. 팀은 기록과 연관되어 있으므로 삭제된 팀의 기본키(pk)를 외래키로 갖는 모든 기록은 지워진다. 이것을 리그입장에 보자. 리그는  팀이 삭제되면서 영문도 모르게 자신관 연관된 기록 데이터들이 다 지워져 버린다! 정말 큰일인 것이다.

 

와닿지 않을 수 있으니 실예로 들어보자.

잉글랜드 프리미어리그의 역대 순위를 보여주는 서비스를 만들었다 해보자. 만약 epl에서 토트넘이 파산을 해체됐다고 해보자. 그래서 팀 DB에서  토트넘 팀을 삭제해주었다. cascade 전략에 의해 토트넘과 연관된 모든 기록 데이터도 지워진다. 그러면 역대 대회 순위표에서 순위를 조회할때 토트넘이 없는 순위표가 출력되게 된다.

 

삭제 해도 되는가?

삭제는 굉장히 위험한 행위이다. 잘못된 읽기, 생성, 수정은 다시 고칠수 있지만, 삭제는 다시 돌이킬 수 없다. 그렇기 때문에 삭제 메서드를 구현할때는 해당 데이터가 진짜 삭제해도 되는 데이터인지를 고민할 필요가 있다.

 

위 케이스에서 팀은 삭제해도 되는 데이터일까? 실제로 서비스 운영하다 팀이 사라질 수 있다. 하지만 해당 팀의 기록 데이터는 계속해서 쓰일 수 있다. 즉, 팀은 삭제되어서는 안되는 데이터이다. 팀을 삭제하기 보단 해당 팀이 더이상 리그에 참가할 수 없도록 하는 것이 더 좋은 방법이다.

 

Team Table (삭제전략 : default)

id name activate
1 team1 true
2 team2 true
3 team3 false

수정된 팀테이블을 보자. 일단 팀 테이블은 기본적인 삭제전략을 이용하므로 하위 테이블의 존재로 인해 삭제가 불가능하다. 대신 activate 칼럼이 팀 삭제 역할을 대신한다. activate가 false가 되면 더 이상 리그 참가가 불가능하다. 이를 통해 팀 삭제의 효과를 대신하는 것이다. 그리고 삭제된 팀이더라도 이전에 참가한 리그에 기록은 여전히 사용할 수 있게 된다.

 

오류가 없으면 좋은 것인가?

다시 처음 언급했던 구절을 다시 보자.

cascade 속성을 설정해놓으면 오류가 발생하지 않는다.

처음 개발을 하다 보면 복잡한 오류들이 나를 옥죄인다. 그럴때면 오류를 '단순히' 없애고 싶어진다. 오류를 없애면 문제가 해결되는가? 아니다! 오히려 더 큰 문제가 우리를 기다린다. 데이터 유실, 데이터 불일치와 같은 소프트웨어의 치명적인 상처를 입히는 것들 말이다. 오류는 어떻게 보면 소프트웨어의 고장을 막는 마지막 장치이다. 오류를 없애는 것이 중요한게 아니다. 오류의 원인을 없애는 것이 중요하다. 이때 오류의 원인이란, 소프트웨어의 무결성을 해치는 것들을 의미한다.

 

오류의 원인을 없애기 위한 다양한 방법이 존재한다. 사용자가 자연스럽게 무결성을 지키도록 서비스를 설계할 수 있을 것이고, 만약 무결성을 해칠수 있는 행위를 할 경우 경고창이나 알림창을 띄우는 것도 방법이다. 위 케이스에서는 아예 삭제메서드를 안만듬으로써 사용자가 무결성을 해치는 행위를 못하도록 막아버렸다.

 

'컴퓨터공학' 카테고리의 다른 글

CORS 정책에 의한 예외 처리하기  (0) 2023.02.28
[Network] Application 계층 - Socket Programming  (0) 2022.09.28
프록시 패턴 (Proxy Pattern)  (0) 2022.08.10
TCP에 대하여  (0) 2022.01.06