Simple한 얘기를 해보자.
- 전역 변수 - 파일 내 어디서든 접근 가능하며, 변하는 값(변수)
엄청나게 유용해 보인다. 소스 파일 내의 어디서는 접근이 가능하다니!!
하지만
'변수'라는 상태가 문제다.
소스 파일내 어디서든 이 값을 변경할 수 있다는 것이다.
만약 프로그램이 One Thread의 순차처리 방식이라면 당연히 문제가 없겠지만
Multi Thread일 경우 누가 어느 시점에 이 값을 바꿀지 모른다.
[ 프로그램 동작 전제 ]
- Non-Block Packet Receive로 지속적으로 메시지를 수신하여 동일 로직을 처리한다.
- 사용자는 1개의 세션을 통해 연결 후, 모든 Transaction이 끝날 때까지(즉, End 메시지를 전송할 때까지) 메모리에 데이터를 유지한다.
- 세션관리자는 사용자의 Transaction 진행 여부를 알 수 있다.
- 세션 연결 후, 첫 메시지 처리 과정에 사용자 정보를 구조체 배열 메모리(Context Index 기준)에 저장 후, 두 번째 이후 메시지 부터는 사용자 번호를 기준으로 Context Index를 가져와 사용자 정보를 조회/갱신 한다.
- Transaction 종료 시, 구조체 배열 메모리 및 세션 관리에서 데이터를 삭제한다.
위의 케이스는 전에 다니던 회사에서 겪은, 실제 운영 시스템에서 발생한 장애 상황을 심플하게 줄여서 정리한 것이다. (실제로는 훨씬..무지막지하게 복잡하다...)
한 시점에 여러명의 사용자가 지속적인 Transaction 처리 중인 상태가 될 수 있기 때문에, Non-Block 상태에서 여러 Transaction을 처리하는 프로그램의 세션, Context 관리란 정말 골치 아프다.
A 사용자가 Transaction을 진행하고 있다. a라는 메시지를 시작으로 30번의 Context ID를 할당받고, b라는 메시지를 전달했을 때, 세션관리자가 A 사용자라는 것을 인지하고 30번의 Context ID를 알려주어 아주 빠른 메모리 접근으로 작업을 처리하도록 했다.
문제는 세션관리자가 Context ID를 'Global Index'에 넣어주고 Process #1은 이 값으로 메모리에 접근하게 되어 있었다는 것이다. (아마 처음 이 프로그램을 만든 사람은...a라는 메시지를 처리하는 함수와 b라는 메시지를 처리하는 메시지가 별도의 파일로 관리되고 있어 전역 변수를 선언해서 extern 명령어로 어디서든 접근하기 쉽게 하려고 했던것 같다.)
만약 A 사용자가 a 메시지를 처리하고 b 메시지를 전달후 b 메시지에 대한 함수를 타기 바로 전에 B 사용자가 전송한 새로운 a 메시지가 먼저 처리되면...?
A 사용자의 b 메시지를 처리하기 바로 직전에 Global Index는 A 사용자의 Index 30에서 B 사용자의 Index 25로 변경될 것이다. 그럼 A 사용자의 b 메시지를 처리할 때는 당연히 Index 25에 접근하게 되어 잘못된 데이터를 처리하게 될 것이다.
이것이 얼마나 심각한 문제일지는 겪어본 사람은 알겠지...
거대한 운영 시스템에서 원인 분석만 꼬박 일주일을 잡아 먹은적도 있을 정도다.
장애 이후 전역변수를 없애는 일 또한 엄청난 곤욕이다.
오늘부로 아래 3가지만 실천해 보자.
- 모든 전역'변수'는 제거한다.
- 변수는 반드시 함수의 Parameter(인자)로 넘겨줘서 사용한다. Call-by-Value든 Call-by-Reference든 상관없다.
- 전역으로 선언할 수 있는 것은 오직, 값을 변경할 수 없는 '상수'뿐이란 것을 명심하자.
다음에는 위 문제로 새로 만들었던 'Context 관리 라이브러리'를 포스팅 해봐야겠다.
'Developer' 카테고리의 다른 글
[Developer] Input Validation - 입력값 검증 (0) | 2020.05.26 |
---|---|
[Developer] 개발할 때가 좋았는데..... (0) | 2020.05.25 |