컴퓨터 공부/📗 CS

[데이터베이스] 12장 - stored procedure를 백엔드 실무에서 쓰기에 조심스러운 이유!

letzgorats 2024. 1. 15. 22:59

3-tier architecture 에서 stored procedure의 의미

: 오늘날의 IT회사들은 일반적으로 client-server architecture 의 한 종류인 three-tier architecture 모델로 서비스를 개발한다.

3-tier architecture
3-tier architecture 동작하는 구조

 

그럼, 비즈니스 로직이란 무엇일까? 당근마켓을 예로 들어보자.

당근마켓의 비즈니스 로직

 

이런 비즈니스 로직들을 통해 파생되는 데이터들이 있을 텐데, 그러한 데이터들이 데이터 티어에 저장된다.

당근마켓의 데이터 티어

 

10장에서 Stored Procedure 는 RDBMS에 저장되고 사용된다고 했는데, 주된 사용 목적은 비즈니스 로직 구현이라고 했다. 

즉, stored procedure을 사용한다는 것은 data tier에 비즈니스 로직이 존재할 수 있다는 의미이다!

비즈니스 로직이 logic tier 에도 있고 data tier에도 있게 된다.

 

즉, 비즈니스 로직이 Logice 티어에도 있을 수 있고, Data 티어에도 있을 수 있게 된다는 뜻이다.

 

그럼, 비즈니스 로직이 stored procedure 에 의해서 Data 티어에 있게 됐을 때, 어떤 장점이 있고 단점이 있는지 살펴보자.


stored procedure의 장점

 

 

1. application에 대해 transparent 하게 동작할 수 있다.

 

간단하게 설명하기 위해서 DB가 1대라고 가정해보자.

 

각 application(인스턴스)들은 같은 소스코드를 기반으로 동작을 할 것이기 때문에, 같은 소스코드 즉 같은 비즈니스 로직을 가지고 있는 셈이다.

그런데, 이런 비즈니스 로직을 수정할 일이 생겼다고 하면, 개발자가 소스코드를 열심히 수정한 뒤에, 배포를 해줘야 한다. 컴파일을 시키고 배포파일을 만든 뒤에, 기존의 인스턴스는 내리고 새로운 인스턴스를 띄워줘야 한다.

이 때, 4개의 인스턴스를 동시에 바꿔버리게 되면 안 된다. 실제로 서비스는 실시간으로 계속 트래픽을 받고 있을 것이기 때문에, 동시에 다 바꿔주는 동안은 트래픽을 처리할 수 없게 되기 때문이다. 즉, 나눠서 바꿔줘야 한다.

 

즉, 새로운 로직을 가지는 application 하나로 바꿔주고 새롭게 재기동을 해야 한다.

 

그런 뒤에, 다시 그 다음 서버를 재기동을 하고, 또 그 다음 서버를 순차적으로 재기동 해줘야 하는 것이다.

 

 

이처럼, 비즈니스 로직을 바꿀 때마다 컴파일을 새로 하고, 빌드해서 배포파일을 만든 뒤에, 한 대 한 대 새로운 인스턴스로 재시작을 해주는 것이 번거로워 보인다.

 

하지만, 만약 비즈니스 로직이 Procedure단에서 관리가 된다면, 새로운 로직으로 바꿔줄 때 어떻게 하면 될까?

그냥 각 인스턴스에서는 procedure을 호출만 하면 되고, 로직이 바뀌면 procedure의 body부분만 바꾸면 되는 것이므로, 굉장히 간단히 끝날 수 있다.

 

즉, 뭔가를 바꿔도 바뀌기전에 사용하고 있었던 부분들이 직접적으로 바뀌지 않고도 그 내용을 바꿀 수 있을 때, transparent 하다고 하는데, "application에 대해 transparent 하게 동작할 수 있다." 는 것은 stored procedure가 application에 대해 transparent 할 수 있다는 장점이 있는 것이다.

 


 

2. network traffic을 줄여서 응답 속도를 향상 시킬 수 있다.

 

logic이 application server에 있을 때의 network traffic

 

비즈니스 로직이 logic tier, 즉 application server 에 있을 때는 logic 안에 여러 개의 SQL 문을 수행할 때마다, network traffic이 발생한다.

 

logic이 procedure 에 있을 때의 network traffic

 

비즈니스 로직이 data iter, 즉 procedure 에 있다면, application 서버에서는 call logic() 으로 프로시저를 호출만 하면 된다. 그리고, 프로시저 내에서 로직을 다 처리를 해주고, 응답을 주면 끝이다. network traffic이 왔다갔다 딱 2번 발생한다.

 


 

3. 여러 서비스에서 재사용이 가능하다.

 

logic이 application server에 있을 때 똑같은 로직을 각각 구현해야 한다는 번거로움

 

같은 DB에 대해서 여러 서비스가 사용할 수 있다. 3개의 서비스가 logic tier에 똑같은 로직을 구현한다고 해야 한다면, service A는 자바 기반으로 코드를 구현해야 하고, service B는 파이썬 기반으로 코드를 구현해야 하고, service C는 JS 기반으로 코드를 구현해야 하는 번거로움이 존재한다. 

 

logic이 procedure에 있을 때 똑같은 로직을 한 번만 구현하고 가져다 쓰기만 하면 된다.

 

만약 이런 비효율적인 과정을 procedure 에 로직을 구현한다면, 효율적이게 된다. data tier, 즉 RDBMS에 있는 procedure에 로직을 구현하고 사용한다면, 각 서비스는 단순하게 그 로직을 호출만 하면 그 서비스를 사용할 수 있게 된다.

 


 

4. 민감한 정보에 대한 직접적인 접근을 제한할 수 있다.

 

회사 내의 개발자나 임직원이 사용자의 주민등록번호나 신용카드 정보 등의 민감한 정보를 가지고 있는 데이터베이스에 접근하는 것을 우선 막는다.

대신에, 개발은 해야하므로 이 때, stored procedure 를 사용한다. stored procedure는 데이터베이스에 접근하는 것이 가능하고, 개발자가 procedure에 접근하는 것을 허용함으로써 procedure를 통해 로직을 작성할 수 있도록 허용하게 된다. 즉, 직접적인 접근은 막지만, 프로시저를 통해서 서비스 로직을 구현할 수 있도록 하는 그런 장점이 있는 것이다.

 


 

 stored procedure의 단점 & 실무에서 쓰기에는 조심스러운 이유

 

 

1. stored procedure를 쓰게 되면 유지관리 보수 비용이 커진다.

 

일부 비즈니스 로직은 소스 코드 상에서 관리가 되고, 일부 비즈니스 로직은 프로시저를 통해서 관리가 된다.

유지관리 보수 비용이 커진다.

 

이렇게 되면, 소스도 봤다가 프로시저도 봤다가 왔다갔다 하면서 봐야하니까 아무래도 제대로 된 로직을 파악하는데 시간도 오래 걸리고 불편하게 된다.

뿐만 아니라, 버전관리도 해줘야 한다. 소스코드의 버전관리도 해줘야 하고, 프로시저의 버전관리도 해줘야 한다. 물론, 오늘날의 소스코드 버전관리는 너무나도 잘 되지만, 프로시저의 버전관리와 코드관리는 상대적으로 빈약하기 때문에, 이러한 측면에서 오는 불편함도 있다.

 

또, 소스코드 상에서 관리를 한다면 지금은 스프링이므로 자바 언어만 알면 되는데, 프로시저 단에서 관리를 하게 된다면 프로시저 문법도 또 알아야 하는 추가적인 학습비용도 발생한다.

 

추가적인 기능이 나올 때도, 비단 프로시저에서 추가하고 배포하는 것이 아니라 소스 코드상에서도 호출하게 되는 코드를 또 넣어줘야 하니까 코드수정도 필연적일 수 있다. 그렇게 되면, 추가적인 서버 재시작과 배포도 동반될 수 있다.

 


 

2. stored procedure를 쓰게 되면 DB 서버를 추가하는 것이 간단한 작업이 아니다.

 

보통, 그림처럼 RDBMS의 cpu 사용량application server의 cpu 사용량보다 더 많을 것이다.

프로시저에서의 cpu 사용량

 

그리고 서비스를 하다보면, 여러이유들로 traffic이 폭발할 때가 있을 것이다. 그럼 전반적으로 cpu 사용량이 증가할 텐데, 이 때, application의 cpu 사용량은 RDBMS의 cpu사용량에 비할바가 안 된다.

traffic이 몰릴 때, 프로시저에서의 cpu 사용량

 

특히 RDBMS 의 cpu 사용량이 거의 90%를 찍었다고 해보자. (당연히, procedure가 비즈니스 로직을 다 들고 있으니까 할 일이 많아서 cpu사용량이 폭발 할 수 밖에 없다.) 거의 이정도면 정상적으로 동작을 못하고 있다고 봐도 된다. 뭔가 버벅거리고 늦어질 우려가 있는 것이다. 즉, 서비스의 피해를 주게 된다.

 

traffic이 몰릴 때, DB 추가하는 모습

 

그럼, 이 상황을 해결하기 위해 개발팀이 RDBMS에 몰리는 부하를 줄이고자 새로운 DB를 추가해줬다고 해보자. 하지만, 이렇게 추가를 해봤자 바로 해결이 안 된다. 왜냐하면, 지금 data는 기존 DB에만 다 있기 때문에 당장 새로운 DB를 추가해준다고 한들, 당장 아무런 쓸모가 없는 DB에 불과하기 때문이다.

 

traffic이 몰릴 때, 새로운 DB에 데이터를 언제 다 복제하냐!

 

그럼, 기존 DB에 있는 데이터를 다 복제해줘야 하는데, 언제 데이터를 다 복제하고 서비스를 재개할까? 이처럼, DB 서버를 추가하는 것은 간단한 작업이 아니다.

.

..

...

※ 그럼 비즈니스 로직소스코드를 통해서 관리하게 되면, 즉 logic tier 에 두게 되면, 어떤 장점이 있을까?

 

logic tier에 비즈니스 로직이 있을 때, traffic

 

지금은 비즈니스 로직을 application server에서 관리를 하기 때문에(지금 3대의 서버로 관리를 하고 있으니까), 어느정도 cpu 사용량이 분산이 될 것이다. 마찬가지로 DBMS에서도 더 이상 비즈니스 로직을 관리하지 않기 때문에 부담이 줄어들어서 cpu 사용량도 적어질 것이다.

 

이런 상황에서 트래픽이 폭발을 하게 된다면, 전반적으로 cpu 사용량은 증가하게 된다.

logic tier에 비즈니스 로직이 있을 때, traffic이 증가할 때 상황

 

비즈니스 로직이 application server에 있기 때문에, logic iter에 있는 서버의 cpu 사용량이 많이 늘어난 것을 볼 수 있다.

이 상황에서 트래픽이 더 늘어날 때의 문제상황을 대비해 아래와 같이 신규서버를 투입할 수 있다.

 

logic tier에 비즈니스 로직이 있을 때, traffic이 증가할 때, 신규 서버 투입

 

요즘에는 클라우드가 잘 되어 있기 때문에, 이렇게 application server를 투입하는 것은 대단히 쉽다. (ex.auto scailing)

 

logic tier에 비즈니스 로직이 있을 때, traffic이 증가할 때, 신규 서버 투입

 

기존에 3대의 application server가 받고 있던 traffic을 이제 5대가 나눠서 받게 된다. traffic이 분산이 되는 것이므로 그 만큼 각 application server에서 cpu사용량이 줄어들게 되는 장점이 있다. (cpu, 메모리 부하를 쉽게 분산시킬 수 있는 장점!)

 

이렇듯, logic tier에서는 데이터를 들고 있지 않기 때문에, 비교적 application server 투입이 간단하다. 

 

 

 


 

3. stored procedure가 언제나 transparent 인 건 아니다.

 

이렇게 allu_old() 라는 이름의 프로시저가 있고 각 인스턴스에서 호출을 하고 있다.

allu_old()

 

만약, 프로시저의 이름을 allu_age() 로 바꿔주고 싶다고 가정해보자.

allu_age()

 

그럼, 프로시저의 이름만 바꾼다고 해서 서비스가 정상적으로 운영되지 않는다. 각 인스턴스에서는 기존의 프로시저 이름으로 호출을 하고 있기 때문이다.

 

그래서, 바로 프로시저 이름을 바꿔주는 것이 아니라 바꾸고 싶은 이름의 프로시저를 새로 만들어주고,

소스코드상에서도 바꿔준 이름의 프로시저를 서버가 잘 호출할 수 있도록 호출부분을 수정해줘야 한다.

그렇게 call allu_old() 부분을 call_allu_age() 로 코드를 다 바꿔준 다음에,

바꿔준 코드로 새로 빌드를 해서 서버 하나하나를 다 재시작하고 오류가 발견되지 않으면(제대로 동작하면), 이제 기존 allu_old() 프로시저를 삭제하면 된다.

 

이런 작업도 상당히 번거로운 작업이다. 그래서 stored procedure로 비즈니스 로직을 구현하게 되면, 어떤 경우에 있어서는 transparent 하지 않기 때문에, 그런 경우에는 소스코드에 로직이 있는 것보다 오히려 손이 더 많이 갈 수 있는 단점이 있다.

 

 

※ transparent 하다는 것이 무조건 좋은 것은 아니다.

 

프로시저의 logic 의 이름이 아니라 내부 body 부분을 바꾸고 싶을 때는, transparent 하게 바꿀 수 있는 상황이다. 

 

 

프로시저 logic 의 body 부분을 수정했는데, 이 때 예상치 못했던 버그가 있었다고 가정해보자.

 

그래서, 빠르게 다시 이전 버전으로 롤백을 했다고 하자. 하지만, 프로시저가 수정된 시점으로부터 다시 롤백을 한 시점까지 그 사이의 트래픽들은 버그로 인해서 피해를 본 상황이다.

 

그럼, 프로시저 단이 아니라, 소스코드 단계에서 비즈니스 로직을 관리를 했었다고 하면, 어떨까?

1번 인스턴스의 로직만 수정하고 재시작

 

소스코드에서 로직을 수정하고, 컴파일을 하고, 배포를 하고 application 인스턴스를 새롭게 재시작을 해줄텐데, 각 인스턴스마다 순차적으로 재시작을 해줄 수 있다.

 

가령, 1번 인스턴스만 재시작을 하고 모니터링을 했다고 해보자. 그렇게 테스트를 했는데, 간헐적으로 버그가 발생됐고, 이는 1번 인스턴스에서의 버그인 것을 알아냈다. 

수정한 1번 인스턴스의 로직에서 버그 발생한 것을 발견

 

그래서, 다시 1번 인스턴스의 로직을 이전 버전의 로직으로 롤백을 시켜주고 다시 정상적으로 돌아왔다고 해보자.

이 때, 전체 서버 4대중에서 1대의 서버에서만 문제였기 때문에, 잠시라도 피해를 본 트래픽은 전체 로직을 사용하는 트래픽 중에서 4분의 1만 영향을 받았을 것이다. 즉, 프로시저 단에서의 피해가 1이라고 하면, logical tier 단에서는 4분의 1에 해당하는 수치인 셈이다.

이것도 결국 안 좋은 영향이긴 하지만, application 서버에 배포를 하는 것이 좀 번거롭긴 해도 예상치 못한 문제의 영향을 최소화했다는 장점도 있다.


 

4. 재사용 가능하다는 것이 양날의 검이 될 수도 있다.

 

아까 재사용이 가능하다는 장점의 예시를 가져와보겠다.

프로시저를 serverA 가 막 호출

 

한 서비스(서버)가 프로시저를 막 호출한다고 하면 DBMS로 가는 트래픽이 엄청 늘어나서 DBMS 의 cpu사용량이 거의 90%를 찍었다고 해보자.

 

그 정도 되면, 정상적으로 응답하지 못하므로, 세 개의 서비스 모두 프로시저를 사용하고 있었기 때문에, 세 개의 서비스 모두에서 문제가 발생할 수 있는 셈이다. 즉, 통제되지 않는 사용으로 모두에게 문제가 발생할 수 있다.

 

 

이러한 상황은 아래와 같은 아키텍처로 만들어줘야 한다. DB 앞단에 데이터에 접근할 수 있는 서비스를 만들고, 3개의 서비스들이 Data Service를 통해서 원하는 로직을 사용할 수 있도록 해야 한다.

Data Service

 

Data Service 는 어떤식으로 3개의 서비스에 인터페이스를 제공해야 하냐면, API를 통해서 인터페이스를 제공해야 한다.

API 를 통해 데이터 접근

 

만약 이 상황에서 서비스 A가 또 엄청나게 API 를 호출한다면 어떻게 될까?

어느 한 서비스가 비정상적으로 많이 호출한다고 판단되면, Data Service에서는 그 서비스의 호출을 막아버린다. 이 때, 해당 서비스에는 문제가 생기겠지만, 프로시저를 공유하는 다른 서비스는 정상적으로 된다. 즉, 모두가 망하는 상황은 막는 것이다.

 

이렇게 되면, 개발팀에서도 procedure 단에서 말고, 차라리 각 서비스의 logic tier단에서 비즈니스 로직을 관리를 하자고 선택할 수 있다. 즉, 재사용이 가능하다는 것이 무조건적으로 좋은 것만은 아닐 수도 있다.

 


 

5. 비즈니스 로직을 소스코드에 두고도 응답 속도를 향상 시킬 수 있다.

 

아까 비즈니스 로직을 logic-tier 에 구현하면 network traffic이 많이 발생한다고 했다.

logic이 application server에 있을 때의 network traffic

 

근데 만약, 여기서 INSERT 와 UPDATE 를 동시에 진행하는 것이 가능하다면 어떨까? 그러면, 굳이 순차적으로 호출할 필요가 없어진다.

insert와 update가 동시에 진행가능할 때의 network traffic

 

thread pool 을 사용을 하든, non-block I/O를 사용을 하든, 동시에 DB 서버로 요청을 보내는 것이다. 그러면, 당연히 동시에 요청을 보냈으니까 전체 응답시간이 줄어들 것이다.

그래서, 굳이 순차적으로 진행할 필요가 없는 경우라면(동시에 진행이 가능한 경우라면), 동시에 호출을 함으로써 응답속도를 향상시킬 수 있다.

 

 

 

또 응답속도를 향상시킬 수 있는 경우를 살펴보자.

코드 상에서 관리가 되고 있는 getPoint() 라는 로직이 있다고 해보자. 이 로직안에 SELECT 를 2번 사용하는데, 각 SELECT는 서로 다른 테이블을 바라보고 있고 이 두 SELECT는 반드시 순차적으로 진행되어야 한다고 가정하자. 그런 경우를 그림으로 표현하면 아래와 같을 것이다.

getPoint() 로직

 

 

이러한 경우, reddis 와 같은 cache 를 사용해서 응답속도를 향상시킬 수 있다.

그럼, 로직 부분을 아래와 같이 바꿔줘야 한다.

 

코드부터 살펴보자.

getFromCache는 getPoint 라는 로직이 파라미터로 id를 받으면, 그 id의 point를 가져오는 것이다.

 

어떤 id 에 대해서, 그 id 에 대한 point가 cache에 있는지 먼저 확인을 한다. 맨 초기에는 당연히 cache에 아무것도 없을 테니, 이후 로직을 타게 된다. 그래서, 두 번의 SELECT를 수행을 하게 된 것이다.

 

그리고 나서 그 결과를 바탕으로 point 를 계산한 뒤에, 그 point를 cache에 넣어준다.(setCache)

즉, id에 대해서 계산된 point를 key-value 형태로 reddis cache 에 저장을 한다. 이때, 얼마동안 cache에 저장을 할 것인지 lifetime도 전달해준다. (위 코드에서는 60초라고 전달을 했다.)

그리고 마지막으로 계산된 point를 return 을 한다.

 

처음에는 과정이 복잡해져서 네트워크를 더 타게 된다. 하지만, 그 이후는 훨씬 응답속도에 이득을 보게 된다. 

 

처음 이후에 같은 id 에 대해서 getPoint() 로직을 호출하게 된다면, 1차적으로 cache를 확인할텐데, 이제는 reddis cache에 그 id 에 대한 point가 있기 때문에 바로 return 을 할 수 있게 된다. 즉, id에 있는 point 정보가 cache에 있는 동안에는 DB단 까지 갈 필요가 없기 때문에 훨씬 그 응답속도가 빨라지게 되는 것이다. 이는 응답속도를 향시키는 것은 물론, DB 부하까지도 줄일 수 있다.

실제로, application 서버와 DB 단 사이에 cache를 두는 것은 실무에서 정말 많이 사용되는 패턴이다.


 

6. stored procedure가 민감한 정보에 대한 접근을 완벽히 대체할 순 없다.

 

아까 개발자들이 procedure을 통해서 간접적으로 DB에 접근을 한다고 했다. 이는 보안상에 조금이라도 더 좋은 영향을 끼치는 것은 맞지만, 개발자 분이 억한 심정을 품고 민감한 정보를 접근하고 싶다고 하면, stored procedure에서 민감한 정보를 반환하도록 만들어주면, 적어도 개발자들은 민감한 정보에 접근을 할 수 있게 된다.

억한 심정을 품은 개발자가 민감한 정보에 접근할 수 있다.

 

그렇다고, 개발자분들이 프로시저에만 접근할 수 있도록 허용하고 DB에는 직접적으로 접근하지 못하도록 막아 놓는다면, 개발 속도가 떨어질 수 있고, CS(customer service)업무의 신속함이 떨어지게 된다.

 

담당자나 개발자에게만 DB 혹은 테이블 권한을 부여하고, 민감한 정보는 암호화해서 저장하는 게 좋다. 또, 보안서약서 등을 통해 정책적으로도 보안을 강화하는 것이 좋다.


 

이 외에도 아래와 같은 이슈로 인해 실무에서 자주 쓰기 조심스럽다.

- procedure로는 복잡하고 유연한 코드를 작성하기 어렵다는 점

- 오늘날의 프로그래밍 언어는 훨씬 다양하고 강력한 기능을 제공한다는 점

- procedure는 가독성이 떨어진다는 점

- procedure는 디버깅이 어렵다는 점


참고자료

- 유튜브 쉬운코드

반응형