brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Jun 01. 2019

소프트웨어 개발의 지혜(2) - 기술부채

"기술부채" 에 대한 경험 및 생각 정리

필자는, 작년 이맘때쯤 "소프트웨어 개발"에 대해서 평소에 생각했던, 

소프트웨어 개발에 대한 필자의 가치관, 생각에 대해서 글로 정리해서 공유했었다. 

https://brunch.co.kr/@springboot/35

1년이 지난 후, 이번 글에서는 "소프트웨어 개발의 지혜(2) - 기술부채" 라는 주제로 글을 작성하겠다. 지난번 글은 추상적인 개념 위주의 글이라면, 이번 글은 필자가 지난 6개월 동안 팀에서 수행했던 업무 경험을 바탕으로, 사례 중심의 글을 작성하였다. 생각의 흐름대로, 주저리주저리 작성했기 때문에 글이 매우 지루하고 재미가 없다. 하지만, 그냥 지나가지 말고 꼭 한번 읽어보길 바란다. 


--- 

회사에서는 프로젝트 완료보고를 상세하게 발표하였다. 하지만, 이 글에서는 어떤 서비스를 어떻게 개선했는지에 대한 회사 허락 없이 공개하기 어려운 내용은 전부 제외하였다. 시스템 아키텍처 및 회사 비즈니스 관련 내용 역시 이 글에서는 전부 제외하였다. 


또한, 이 글은 소스 코드 한줄 없다. 



1. "기술 부채" 에 대해서


소프트웨어 및 기술부채 에 대한 필자의 생각을 먼저 정리해보겠다. 


1.1 소프트웨어는 어떻게 유지되어야 하는가?


필자가 생각하는 "소프트웨어"에 대한 생각은 아래와 같다 


"소프트웨어"는 이전과 동일하게 동작하도록 유지하는 것이 아니라, 변화하는 환경 속에서도 항상 쓸모 있도록 유지하는 것이다. 단, 너무 많은 변화는 오히려 소프트웨어의 품질을 떨어뜨릴 수 있다. 어려운 일이지만, 항상 Useful 하도록(사용성 높은) 균형 잡힌 소프트웨어를 항상 유지해야 한다. 


소프트웨어는 오랫동안 방치해서도 안되지만, 너무 많은 변화도 좋지 않다. 항상 사용성 높게 유지하는 것이 중요하며, 세상의 변화에 빠르게 대응할 수 있는 유연한 소프트웨어를 만드는 것이 좋다.


만약, 두 가지 소프트웨어가 있다면 어떤 소프트웨어가 좋을까?


- 완벽한 기능을 제공하지만 변경하기 어려운 소프트웨어

- 완벽하진 않지만 변경하기 쉬운 소프트웨어


물론, 완벽하면서도 변경하기 쉬운 소프트웨어가 가장 좋겠지만, 

굳이 둘중에 하나만 선택해야 한다면, 


완벽하진 않더라도 "변경하기 쉬운 소프트웨어"가 더 좋다고 생각한다. 


1.2 "기술부채"


"기술부채(Technical debt)"란 말그대로 기술적인 빚 이다. 일반적으로 소프트웨어를 만들 때 일정을 앞당기고자, 일정에 쫓겨서 소프트웨어를 빠르게 만들기 위해, 코드를 불완전하게 작성하는 경우 "기술부채"가 발생한다. 또한, "기술부채"는 좀 더 넓은 의미로 문서 부족, 낮은 수준의 아키텍처 설계, 리팩토링을 게을리 해서 쌓인 꼬여버린 구조, 낮은 테스트 커버리지, 적절하지 못한 객체 모델링 등 을 포함한다. "기술부채"가 많을수록 우리는 불가피하게 많은 "이자"를 감수해야 한다. 유지보수 비용이 증가하고, 신규 개발 속도가 저하된다. 즉, 생산성이 떨어진다. 이런 문제를 해결하기 위해서(즉,"이자"를 갚지 않기 위해서는) "기술부채"를 "상환"해야 한다. "상환"을 하기 위해서는 "리팩토링", "시스템개선", "재설계" 등 다양한 방법이 있다. 하지만, "기술부채"를 갚는 일은 생각보다 쉽지가 않다. "기술부채"를 해결하는 일이 쉽지 않은 이유를 생각하기 전에 먼저 "기술부채"의 일반적인 특징에 대해서 생각해보자. 


기술부채 특징

일반적으로 "기술부채"는 아래와 같은 세가지 특징을 가진다. 


첫째, "기술부채"는 빌린 사람과 갚는 사람이 일치하지 않는다. "기술부채"를 빌린 최초 개발자가, "기술부채"를 상환하는 경우는 별로 없다. 보통 최초 개발자가 팀을 떠난 이후 시간이 한참 지난 후에 다른 개발자가 "기술부채"를 상환하게 된다. 


둘째, "기술부채"는 소프트웨어 사용자에게는 직접적인 가치를 제공하지 않는다. 그래서, 부득이하게 "기술부채"는 관리 업무에서 소외되는 경우가 많다. 


셋째, "기술부채"는 측정하기 어렵다. 개인의 취향에 따라서 느끼는 정도가 다르고, 정량적으로 판단하기 쉽지 않다. 그래서, "기술부채"로 인해서 팀의 생산성이 떨어진다는 것을 수치화해서 증명하기가 매우 어렵다.


"기술부채" 를 이해하고 분석하는 시간이 많이 필요하다. 또한, "기술부채"를 진행하기 위해 사용하는 인력 리소스를 경영진에서 쉽게 허락하지 않을 가능성도 있다. 왜냐면, "기술부채"는 정량적으로 판단하기 어렵기 때문에, "기술부채"로 인해서 팀의 생산성이 떨어진다는 사실을 이해하지 못하는 경영진이 꽤 많다. 


이런 특징으로 인해서, "기술부채"를 갚는 일은 쉽게 진행될 수 있는 일이 아니다. 


"기술부채" 반드시 갚아야 하는가? 

서비스 오픈 초기에는 빠르 서비스 오픈을 위해서 부득이하게 "완벽하진 않은 시스템"을 빠르게 구축한다. 빠른 서비스 오픈을 위해서 "기술부채"를 만들어 내는 것이다. 이런 모든 "기술부채"는 반드시 갚아야 하는가? 사실, 꼭 그렇지는 않다. "기술부채"를 상환하기 위해서 사용하는 "인력" 과 신규 사업에 투자해야 하는 "인력"을 비교하고 더 중요한 것을 선택하면 된다. 하지만, 일반적으로는 신규 사업을 더 중요시 하는 경우가 많기 때문에 "기술부채"는 지속적으로 쌓이게 될 것이다. 


하지만, 너무 늦지 않은 시기에 "기술부채"를 상환해야 한다. 


"기술부채"를 상환하지 않아도 되는 경우가 있을까? 

배포가 거의 없고 중요도가 낮은 애플리케이션은 굳이 "기술부채"를 개선하지 않아도 된다. 앞으로 변경사항이 거의 없는 소프트웨어를 굳이 힘들게 개선해서 남는 이득이 많지가 않다. 하지만, 이런 경우를 제외하고 대부분의 소프트웨어의 "기술부채"는 언젠가는 반드시 갚아야 한다. 다른 신규 사업을 진행하기 위해서 잠시 "기술부채"를 보류할 수 있지만, 너무 늦지 않은 적절한 시기에 "기술부채"를 갚아야 한다. "기술부채"를 너무 오랫동안 유지하면, 어느 시점이 되면 시스템은 심각한 한계에 부딪히게 될 것이다. 즉, "기술부채"를 더이상 갚지도 못하는 상황이 발생한다. 소프트웨어는 파산하게 될 것이다. 


https://martinfowler.com/bliki/DesignStaminaHypothesis.html

기술부채를 관리하지 않고 악취나는 코드를 계속 유지한다면 개발 생산성은 0에 가까워질 것이다. 소프트웨어 품질이 저하되고, 비즈니스 기능 추가도 어려워질 것이다. 반대로, 기술부채를 잘 관리해서 품질 높은 코드를 유지한다면 개발 생산성은 증가할 것이다. 생산성이 증가하면 소프트웨어 품질은 향상되고, 비즈니스 개선 속도가 빨라질 것이다. 즉, 적절한 시점에서 우리는 "기술부채"를 "상환"해야한다. 하지만, 적절한 시점을 제대로 판단할 수 있는 개발자는 흔치 않다. 팀 기술역량, 회사의 비전, 개발자의 생산성, 제품담당자와의 커뮤니케이션 등 전반적으로 현명하게 판단해서 적절한 시점에서 기술부채를 개선해야 하는데, 그 시점을 판단하는 것이 참 어렵기는 하다. 사실, 좀 큰회사에서는, 내 마음대로 할 수 있는 것도 아니다. 의사결정 과정이 필요하기 때문이다.


"기술부채" 의 범위

[소프트웨어 악취를 제거하는 리팩토링 36page - 길벗출판사] 에서는 기술부채의 범위를 아래와 같이 정의한다. 

코드 부채 : 정적 분석 도구 위반과 일관성 없는 코딩 스타일

설계 부채 : 설계 악취와 설계 규칙 위반

테스트 부채 : 테스트 부채, 불충분한 테스트 커버리지, 부적절한 테스트 설계

문서 부채 : 주요 관심사의 문서 누락, 형편없는 문서화


"기술부채" 를 초래하는 원인, 관리하는 방법

[소프트웨어 악취를 제거하는 리팩토링 36page - 길벗출판사] 에서는 기술부채를 초래하는 원인, 기술 부채를 관리하는 방법에 대해서 아래와 같이 설명한다. 


#기술 부채를 초래하는 원인

일정 압력

좋은, 숙달된 설계자 부족

설계 원칙의 응용 부족

설계 악취와 리팩토링에 대한 인식 부족


#기술 부채를 관리하는 방법

점진적인 기술 부채 자각

기술 부채의 감지와 상환

기술 부채의 누적 방지




1.3 나쁜 코드 - 클린 코드의 중요성


필자의 책장에 있는, 로버트 C.마틴의 [클린코드 1장 - 인사이트출판사]를 다시 읽어보았다. 


나쁜 코드가 쌓일수록 팀 생산성은 떨어진다. 관리층에서는 인력을 추가하지만, 새로운 인력은 시스템 설계에 대한 조예가 깊지 않다. 새 인력과 팀은 생산성을 높여야 한다는 압력에 시달리며, 나쁜 코드를 더 많이 만들어 낸다. 생산성은 거의 0이 된다.  결국 팀은 혐오스러운 코드로는 더 이상 일하지 못하겠다며 관리층에게 재설계를 요구한다. 관리층은 재설계에 자원을 쏟아붓기 싫지만 생산성이 바닥이라는 사실을 부인할 도리가 없다. 결국은 팀이 요구한 대로 원대한 재설계를 허락한다. 새로운 "타이거팀"이 구성된다. 모두가 새로운 "타이거팀"에 합류하고 싶어한다. 새로운 프로젝트이고, 처음부터 시작해 아룸다운 작품을 창조할 기회니까. 하지만 초창기 "타이거팀"은 모두 팀을 떠났고... 


새로운 시스템을 재설계해서, 기존 시스템의 모든 기능을 100% 완벽하게 구현하는 일은 때로는 쉽지 않을 수 있다. 그래서, "로버트 C.마틴" 은 그의 저서 "클린 코드" 에서 이렇게 말한다. 


"소프트웨어를 항상 깨끗한 코드로 유지하는 일은, 소프트웨어 운영 비용을 절감하는 최선의 방법이다."




필자가 작년에 "클린코드"에서 예시로 든 "타이거팀" 신규프로젝트와 유사한 경험을 했다. 필자가 팀이동으로, 신규 팀에 합류하기 전부터 신규 시스템 재설계 및 개발이 진행 중이었다. 필자는 팀에 합류하자마자 아무것도 모르는 상황에서 신규 시스템 재설계에 투입되었다. (구)시스템에 대해서 팀원들의 불만이 심각한 수준이었고, 모든 개발자가 신규 시스템이 반드시 구축되어야한다고 믿고 있었다. 필자 역시 그렇게 믿었고, 필자는 비록 합류한지 얼마되지 않았지만 신규 프로젝트에 대한 의지가 매우 강했다. 하지만, 문제는 (구)시스템이 매우 불안했다는 사실이다. (구)시스템은 매일같이 장애가 발생하고 있었다. 기존 팀원들이 할 수 있는 장애 대응은 애플리케이션 재시작 방법 뿐이었다. 신규 프로젝트에 투입되었던 팀원들은 (구)시스템 장애를 대응하기 위해서 신규프로젝트와 장애대응 사이에서 반복하였다. 그런데 필자가 팀에 합류하자마자 한두명씩 이직을 하기 시작했다. 팀에 합류 후 두세달쯤 지났을 때... 신규 프로젝트 팀원들 모두 팀을 떠나고 없었다. 그렇게 (구)시스템의 담당자들은 신규시스템을 만들다가 전부 이직하게 되었고, 해당 시스템을 맡아서 운영했던 사람은 단 한명도 남지 않은 최악의 상황이 발생하였다. 해당 (구)레거시 시스템은 팀에 합류한지 세달 밖에 되지 않은 필자가 맡게 되었다. 


장애는 매주 발생하고 있고, 인수인계는 전혀 받지 못한 상황이었다. 필자는, 해당 시스템을 잘 모르는 상황이라서 막막했다. 인수인계라도 잘 해줬으면 좋았을텐데...

 


2. 레거시 시스템 개선 사례(1) - 빠른 개선


레거시 시스템을 최대한 빠르게 안정화시켜야 한다. 


신규시스템이고 나발이고, 일단... 급한불 먼저 끄자


2.1 레거시 시스템 분석


해당 시스템은 10개 전후의 컴포넌트로 구성된 시스템이다. 대부분, 스프링 프레임워크 기반으로 구축이 되었고, 사업(비즈니스)에서 매우 중요한 역할을 담당한다. 해당 시스템은 초기 개발한지 8년이 되었는데, 8년전 당시 잘못 작성한 코드가 아직도 존재하는 심각한 레거시 시스템이다. 필자는 해당 시스템을 빠르게 전반적으로 분석해서 진단을 내렸다. 


당시 상황 요약

매주 장애가 발생하고 있다. 

파트 개발자들은 한,두달 사이에 전부 퇴사했다.

해당 개발자들의 장애 대응 방법은 시스템 재시작 뿐이었다.

최근 2년 동안 코드 관리를 전혀 하지 않았다.

문서가 거의 없고 인수인계가 제대로 되지 않았다.

대부분 애플리케이션 자바 버전이, JDK 1.6, JDK 1.7  이다. 

대부분 애플리케이션 스프링 버전이, 3.X 이다. 

컴포넌트(애플리케이션) 간에는 HTTP 동기 호출을 하는데 병목현상이 심각하게 발생한다. 

데이트베이스 커넥션이 가끔 끊기는데, 기본적인 커넥션풀 설정이 없는 상황이다.

데이터베이스 마스터-슬레이브 기본 구성이 되어있지 않다.

배치 서버는 무리하게 TASK 를 수행 중이며, TASK 분산 수행이 전혀 되지 않는다. 

컨텐츠 API 서버는 휘발성 메모리 자료구조에 컨텐츠 정보를 저장하기 때문에, 재시작하면 캐시 데이터가 전부 사라진다.

일부 서버에는, 2개의 애플리케이션이 하나의 물리 WAS(톰캣)에 연동되어 있다. 

JPA 를 잘 모르는 상황에서 사용해서 성능 이슈가 심각하게 발생한다.

메소드 네이밍이 엉망 진창이다. 특히, 맞춤법 틀린게 너무 많다.

하드코딩 되어있는 비즈니스 관련 숨겨진 기능이 많다.


나는 어떤 선택을 할 수 있는가?

1. 새로운 시스템을 신규로 구축한다. 

2. 레거시 시스템을 조금씩 개선해나간다.

3. 기존 퇴사자를 따라서, 필자도 그냥 퇴사(이직)한다. 

 

필자는 두번째 방법으로, 

레거시 시스템을 조금씩 개선하기로 결정하였다. 


단, 간결하게, 가치있게, 하나씩 완성하기 전략으로 진행하였다. 


#간결하게, #가치있게, #하나씩 완성하기



2.2 레거시 시스템 vs 기술부채


해당 레거시 시스템이 "기술부채"의 범위에 포함되는지에 대해서 여러번 생각을 해봤지만 명확하게 결론을 내리지 못했다. 어떤 의미에서는 "기술부채"가 맞는것 같기도 하지만, 또 어떤 의미에서는 "기술부채"가 아닌것 같기도 하다. "기술부채"의 사전적 의미를 알아보자. 


"기술부채" 란

임기응변식 소프트웨어 아키텍처와 여유 없는 소프트웨어 개발의 결과에 대한 비유


"기술부채"의 범위가 추상적이라서 애매하지만, 필자가 진행했던 작업이 "기술부채" 개선 사례가 맞다는 가정하에 이 글을 작성하겠다. 혹시라도 다른 의견이 있다면 댓글로 피드백을 남겨주길 바란다. 



2.3 우선순위를 정하고 

가치있게,하나씩,간결하게 해결해나간다.


사업 초기에는 적은 비용으로 최대한의 효과를 낼 수 있는 계획을 세울 것이다. 왜냐면, 회사는 투자 비용 대비 최대 이윤을 창출하는 것이 목적이기 때문이다. 하지만 회사의 서비스는 점점 확장을 하게 될 것이고, 확장된 서비스는 당연히 회사에게는 좋은 일이지만 초기에 적은 비용으로 빠르게 구축한 시스템은 "기술부채"가 되어서 개발자의 생산성을 지속적으로 하락시킬 것이다. 물론, 서비스 품질도 같이 하락한다. 당장 장애가 발생하고 있는 애플리케이션부터 하나씩 개선을 해보자. 


이 글은 클라우드 환경에서의 경험이 아닙니다. 오토스케일링이 지원이 되지 않는 환경입니다. 클라우드 환경에서의 개선 사례를 기대하셨다면 이 글은 크게 도움이 못되실 수도 있습니다. 


애플리케이션 로그 수집

장애 포인트를 찾기 위해서, 필요한 로그를 수집하기 시작하였다. 중앙집중형 로그 수집은 아니었지만 ,각 애플리케이션 의심이 되는 포인트에 로그를 남겨서, 장애를 찾기 위한 힌트를 찾기 시작하였다.


외장 톰캣 분리

애플리케이션 A 와 B는 동일한 물리서버 및 톰캣에 연동되어 있었다. 톰캣은 블록킹IO 기반의 웹서버이고, Netty 서버처럼 소수의 쓰레드로 많은 작업을 수행할 수 없기 때문에, 트래픽이 몰리는 상황에서는 Thread Pool 을 아무리 늘려도 웹서버가 감당을 못하는 상황이 발생할 수 있다. 만약, Thread Pool 이 다 차버리면 두 애플리케이션 모두 장애가 발생하는 상황이 생긴다. 하나의 서버(톰캣)에 두 개 이상의 애플리케이션으로 연동되어 있는 경우에는 장애가 전파될 수 있다. 즉, 하나의 애플리케이션 장애는 다른 애플리케이션으로 장애가 전파될 것이다. 해당 "기술부채"를 개선할 수 있는 가장 빠르고 안전한 방법은 별도의 톰캣 서버로 분리하고, 각각의 역할에 맡게 톰캣 Thread Pool 설정을 해주는 방법으로 운영을 해야 한다. 


필자는, 애플리케이션을 별도의 톰캣 서버로 분리하였고 Thread Pool Size 를 적절하게 설정하였다. 해당 작업으로 인해서 애플리케이션의 장애가 전파되지 않게 되었고, 독립적으로 확장 가능한 시스템으로 운영할 수 있게 되었다. 



DB Master-Slave 환경 구성

일부 애플리케이션에서 DB Connection 에러가 종종 발생을 하고 있었는데, 확인해보니 DB Master-Slave 환경이 제대로 구축이 안되어 있었다. Write 보다 Read 수행을 압도적으로 많이 실행하는 애플리케이션이라서 반드시 Master-Slave 환경으로 구성되어야 하지만, JDBC연동 및 Update,Select 로직이 모두 Master 서버로 집중되고 있는 상황이었다. 그래서, 레거시 애플리케이션은 Write, Read 트래픽이 집중되는 순간에 DB 작업에 무리가 발생해서 장애를 유발시킨다. 해당 레거시를 개선하기 위해서, Read(Select) 조회는 Slave 서버에서 수행할 수 있도록 개선하였고 Slave 장비도 2대로 늘려서 가용성을 높였다. 단, Slave 서버를 무작정 늘리는 것은 오히려 성능이 안좋아질수도 있다. 왜냐면, 리플리카 비용이 있기 때문이다. 너무 많은 Slave 서버는 주의가 필요하다.



RestTemplate 풀 사이즈 조정

RestTemplate 이란, Spring 3.0 부터 지원하는 Http 통신에 사용하는 템플릿이다. 반본적인 코드를 깔끔하게 정리해주고, Restful 원칙을 지키며 Json, Xml 등 쉽게 요청/응답을 받을 수 있다. RestTemplate 은 Http를 사용하는 범용 라이브러리인, HttpClient 를 추상화해서 제공한다. 레거시 시스템은 애플리케이션(컴포넌트) 사이에 통신을 위해 RestTemplate를 사용하는데, 문제는 RestTemplate 의 커넥션 풀 설정이 제대로 되어 있지 않았다. 트래픽이 몰리는 순간에는 기본으로 설정된 RestTempalte Pool 사이즈로 인해서 최대 성능을 끌어내지 못하고 있었다. 즉, RestTemplate Pool 로 인해서 애플리케이션 사이에 통신에서 병목현상이 발생하고 있는 것이다. 일단, RestTemplate 를 기능별로 분리를 하였고, 각각의 역할에 맞는 RestTemplate @Bean을 생성하도록 구성하였다. 그리고, 각각의 역할에 맞도록 RestTemplate 커넥션 풀을 설정하여 병목현상을 최소화할 수 있는 최대,최소 수치를 찾기 위해서 노력하였다. 단, 풀 사이즈를 너무 높게 잡으면 오히려 장애를 유발할수 있기 때문에 주의가 필요하다. maxConnTotal, maxConnPerRoute 등의 설정을 유의해서 설정해야 한다. 최상의 Pool 사이즈를 찾기 위해서 커넥션풀 로그를 지속적으로 확인하였다. leased  및 pending 수치를 확인하면서 부하가 심한 시간대에 최대로 수용할 수 있는 풀 사이즈가 어느정도인지 지속적인 모니터링을 하면서 수치를 조정해 나갔다. 


Tasks Job 부하 분산

배치Job 서버에서 수행하는 Tasks Job은 시도때도 없이 실행되고, 특정 시간에 집중되어 순간적으로 폭발적인 트래픽을 유발시킨다. 해당 상황을 개선하는 가장 좋은 방법은 리액티브 시스템을 구축해서 메시지브로커에서 큐 분산을 하는 방법이 좋다고 생각한다. 하지만, 관리 포인트가 늘어나고 복잡도가 증가하고, 개발리소스가 많이 필요하다는 단점 때문에 결국 진행하지는 못했다. 대신 필자는 임시로 해결책으로 특정 서버에서 집중적으로 수행하던 Tasks Job을 다른 서버로 분산 수행하도록 배치 부하 분산 작업을 진행하였다. 기존에는 특정 시간에 수많은 Task 실행으로 많은 장애가 발생했었는데, 개선 이후 Task 분산 처리를 수행함으로써 장애를 최소화할 수 있었다. 해당 작업에 대해서 이 글에서 자세하게 공개하기는 어렵지만, 레거시 개선 작업 전체 중 가장 효과가 컸던 작업이다. 물론, 부하를 제어할 수 있는 성능 개선 또는 리액티브 시스템으로의 전환은 아니기 때문에 완벽한 작업은 아니었지만, 당장 발생하는 장애를 줄일 수 있었다.  


Thread Pool Size 조정

애플리케이션의 성능에서 가장 중요한 요소는 Thread Pool Size 이고, 최대 성능을 내기 위해서는 Thread Pool Size 를 적절하게 조정해야 한다. Thread Pool 을 너무 작게 설정해도 안되고, 반대로 너무 높게 설정해도 성능에 악영향을 끼칠 수 있다. Pool 사이즈를 설정하는 것은 필자에게는 가장 어려운 기술 중 하나라고 생각한다.  idle Thread 가 너무 많이 발생하면, 프로세스가 제대로 역할을 못하는 경우가 발생하는데, [자바 성능 튜닝 - 스캇오크스 지음] 에서는 쓰레드 최대 개수 설정하는 방법에 대해서 아래와 같이 나와 있다. 


특정 하드웨어에 주어진 작업 부하에 대한 적절한 스레드의 최대 개수는 몇 개인가? 이에 대한 간단한 답은 없다. 작업 부하와 실행되는 하드웨어의 특성에 달려 있다. 특히 최적의 쓰레드 개수는 각 개별 태스크가 얼마나 자주 대기 상태가 되는 가에 달려 있다.  [자바 성능 튜닝 335page - 스캇오크스 지음]


레거시 시스템의 애플리케이션은 일부 작업에서 "대기 상태"가 자주 발생하고 있었는데, 이 부분을 개선하기 위해서 각 서버에서 수행하는 Thread Pool 사이즈를 적절하게 조정하여 시스템 안정화를 이끌어 낼 수 있었다. 물론, 이 방법이 최선의 방법이라고 말할 수는 없다. 더 좋은 방법은 애플리케이션에서 성능이슈가 발생하는 지점을 하나하나 찾아내서 코드 개선을 하는 것이 바람직 하다. Thread Pool Size 로 해결하는 것은 임시방편일 수 있다. 하지만, 필자는 해당 시스템에 대한 히스토리를 하나도 모르기 때문에 코드를 수정해서 해결하는 일은 쉽지 않은 일이었다. 현재 상황에서 리스크 없이 최소한의 노력으로 성능을 이끌어 내는 방법은 Pool Size 를 조정하는 방법이었다. 


DB 커넥션풀 설정

일부 애플리케이션은 종종 DB 커넥션이 끊기는 문제가 자주 발생을 하고 있었다. 원인을 찾아보니 애플리케이션에 DB 커넥션풀 설정이 전혀 되어있지 않았다. 일반적으로 "DB 커넥션풀"은 미리 커넥션 객체를 생성하고 해당 커넥션 객체를 관리를 한다. "커넥션풀" 은 풀 속에 커넥션이 생성되어 있기 때문에 커넥션을 생성하는 비용을 줄일 수 있고, 커넥션을 재사용하기 때문에 생성 되는 커넥션 수가 많지 않다. 아마도, 해당 레거시 시스템의 서비스 초기에는 커넥션풀을 설정하지 않아도 크게 문제가 되지 않았지만, 서비스가 점점 확장되면서 DB 커넥션이 중요한 시점이 된 것이다. 필자는 "커넥션풀"설정을 추가하였고, 해당 작업 이후에 커넥션이 끊기는 문제는 사라졌다. 최고의 성능을 유지할 수 있도록 DB 연동 로직은 지속적인 관심이 필요하다. 다른 업무로 신경을 쓰지 못하는 사이에, 갑자기 DB 커넥션 문제가 발생할 수도 있고, 해당 문제는 시스템에 심각한 장애를 유발시킬 수 있다. 


공용 캐시 분리

컨텐츠 제공 API 는 모든 컨텐츠 데이터를 백업 DB 에 저장하고, 빠른 서비스를 위해서 메모리에 저장한 데이터를 바로 꺼내서 서비스를 제공한다. 하지만, 해당 메모리는 휘발성으로 재시작하면 모든 데이터는 사라지며, 백업DB 를 통해서 전부 리로드 해서 메모리에 생성해야 하는 구조이다. 해당 애플리케이션의 메모리 데이터를 공용 캐시 레이어로 분리하는 작업을 계획하였지만 개발리소스가 많이 필요한 작업이었다. 그래서, 해당 작업은 2차 작업으로 진행하였다. 해당 작업에 대해서는 글 아래 다시 상세하게 작성하겠다. 


JDK1.8 업그레이드, 스프링 부트 전환

레거시 시스템의 애플리케이션을 전부 스프링 부트 전환로 완료하였다. 또한, JDK 버전을 1.8 로 업그레이드 하였다. 기존 레거시 시스템은 대부분 JDK 1.7 이고, 일부 애플리케이션은 심지어 1.6 이었다. 2018년인데 Java 1.6 이라니.... 너무 절망스러웠다. 스프링 버전은 대부분 3.X 초기 버전이었다. 모든 애플리케이션은 관리가 거의 안되었고, 배포하기에도 불편한 환경이었다. 조금이라도 배포환경이 개선될 수 있도록 스프링부트로 전환하였고, 스프링 부트로 전환하면서 동시에 스프링 버전을 모두 4.X 버전으로 업그레이드 하였다. 스프링부트 버전은 1.5.X 버전으로 맞췄다. 가능하다면 스프링부트 2.X 버전으로 올리고 싶었지만 너무 많은 변화는 시스템에 예상치 못한 리스크가 발생할 수 있어서 욕심내지 않고 적당한 수준인 1.5.X.RELEASE 버전으로 맞췄다. 일부 XML 컨피그는 그대로 남겨놨는데, Java컨피그로 변경하지 못한 XML 은 @Import 어노테이션을 적용하였고, 기존 Maven 도 굳이 Gradle 로 바꾸지는 않았다. 스프링 버전이 3.X 에서 4.X 로 변경되었기 때문에 일부 패키지가 변경이 되었는데, 꼼꼼히 확인하면서 전부 찾아서 변경하였다. 스프링부트 전환 관련해서는 필자의 예전 글을 참고하길 바란다. 

https://brunch.co.kr/@springboot/91


문서화

문서화 작업은 많이 진행은 못했는데, 기본적인 개발/운영 가이드 문서를 작성하였다. 



2.4 "기술부채"를 갚아나가면서 발생한 이슈


"기술부채"를 갚아나가면서 겪었던 이슈들에 대해서 정리하였다. 필자가 생각나는 것만 작성하였다. 필자의 역량부족으로 발생한 이슈도 있었고, 히스토리가 없어서 어쩔수 없이 발생한 이슈도 있었다. 같은 이슈를 반복하지 않기 위해서 글로 남겨본다. 혹시라도, 비슷한 작업을 하는 개발자가 있다면 참고하길 바란다. 


Java Heap Memory 에 대한 안일한 생각 

일부 애플리케이션을 전환하는 과정에서 Java Heap Memory 설정을 꼼꼼하게 확인하지 않아서 장애가 발생하게 되었다. 기존 환경에서 설정되어있는 Java Heap Memory 를 따르지 않고, 필자의 임의대로 설정한 값으로 인해서 OutOfMemory 에러가 발생하였다. 필자의 안일한 생각으로 인한 장애로서 필자의 명백한 실수이고, 다시는 이런 일이 발생하지 않도록 주의해야 한다.


비록 애플리케이션이 알수 없는 이유로 메모리를 너무 많이 사용하고 있는 상황이었다. Heap Dump 를 분석해보는 방법도 좋을 듯 하다. 필자는 일단 Java Heap Memory 설정을 변경하여 빠르게 해결을 하였다. 


서버마다 다른 설정으로 인한 혼란

같은 역할을 하는 서버인데, Properties 설정이 다른 서버가 꽤 많았다. 설정이 다른 이슈로 인해서 애플리케이션을 신규 서버로 전환하는 과정에서 일부 장애가 발생하였다. 히스토리가 없었지만 조금만 더 꼼꼼하게 확인했었다면 리팩토링 전환 배포 중 장애를 최소화했을 것이다. 


비록, 히스토리를 모르는 필자는 어쩔 수 없이 당했지만, 그럼에도 명백히 필자의 실수라고 생각한다.. 더 꼼꼼하게 확인했어야 했다..


nginx 도입으로 인한 이슈

레거시 시스템은 별도의 웹서버 없이 WAS 만 실행이 되어있었다. 필자는 시스템을 전환하는 과정에서 스프링 부트로 전환하여 임베디드 톰캣으로 단일 배포 가능하도록 구성하였고, WAS 앞단에 Nginx 웹서버를 도입하였다. 즉, Nginx 에서 Reverse Proxy 되는 구조로 개선한 것이다. 하지만, 일부 Request 에서 대용량 데이터를 요청하는 케이스가 있었고 필자는 해당 부분을 놓쳤다. Nginx 에 유입되는 요청 중 너무 큰 Request Body 가 요청되면 WAS 로 Proxy 하지 않고 에러 리턴이 되었다. 해당 문제는, Nginx 설정을 수정하여 해결할 수 있었다. 해당 이슈도 필자가 좀 더 꼼꼼히 확인했어야 했다. 


얼마나 더 꼼꼼히 확인해야 하는가... 정말 답답한 상황이었다. 



2.5 시스템 개선 사례(1) 성과


"기술부채"를 하나씩 개선해 나가면서, 시스템은 아주 조금씩 안정화 되었고, 장애는 눈에 띄게 줄었다. 덕분에, 매주 장애 대응에 투입된 개발자의 리소스 낭비도 많이 줄어들었다. 매주 장애가 발생할 때마다 우리는 애플리케이션을 재시작하였지만 이제 그런 작업은 하지 않아도 된다. 


하지만, 최후의 마지막 작업이 남아있었다. 바로, 공용 캐시 분리 작업이다. 해당 작업을 진행하는 과정이 쉽지 않았다. 해당 작업을 진행하기 전에, 많은 사람들을 이해시키고, 반드시 해야 하는 작업이라고 수차례 설득을 하였다. 하지만, 많은 지원을 받지 못했다. 모든 작업을 필자 혼자서 진행하게 되었는데, 그마저도 다른 업무를 병행했기 때문에, 온전하게 해당 작업에 올인하진 못했다. 


많은 사람들을 설득시키고, 다른 업무랑 병행하면서, 

힘들고 어렵게 스트레스 받으면서 꼭 해야했던 작업이었을까?


만약 당시 이 작업을 필자가 포기했었다면, 시스템은 파산되었을지도 모른다. 

아니면, 필자가 없었어도 어떻게든 시스템을 잘 돌아갔을려나?? 



3. 레거시 시스템 개선 사례(2) - 공용 캐시


레거시 시스템의 제일 앞단에는 컨텐츠를 사용자에게 제공하는 Rest API 서버가 존재한다. 해당 애플리케이션은 서비스에 직접적으로 영향을 주는 핵심 컴포넌트이다. 하지만, 해당 애플리케이션은 심각한 기술부채를 갖고 있었는데, 바로 애플리케이션이 너무 많은 메모리를 사용하고 있다는 것이다. 해당 메모리 이슈를 해결하기 위해서 공용 캐시를 구축하는 작업을 진행하였다. 



3.1 애플리케이션 공용 캐시 분리


해당 애플리케이션은 전형적인 3-Tier 아키텍처로 구성되어 있다. 

(사실, 회사 시스템은 조금 특이하다. 회사에서의 특이한 구조는 이글에서 다루지 않겠다. 일반적인 아키텍처를 기반으로 글을 작성하였다.)


3-Tier Architecture

시스템은 3 Tire 아키텍처로 구성되어 있다. 

프리젠테이션 Tier : 사용자에게 컨텐츠를 보여주는 프론트 레이어

어플리케이션 Tier : 비즈니스 로직을 포함하고 있는 어플리케이션 레이어

데이터 Tier : 데이터를 저장하고 접근하기 위한 레이어


3 Tier 아키텍처는 가장 보편적이고 이해하기 쉬운 시스템 구성이다. Frontend, Backend 역할을 분리할 수 있고, 다른 계층에 영향을 끼치지 않고 독립적으로 확장이 가능하다. 일반적으로 웹서비스 아키텍처에서 가장 중요한 요소는 "캐시"이다. 사용자의 요청 시 매번 DataBase 를 조회하는 일은 매우 비효율적일 수 있다. 그렇기 때문에 우리는 Application Tier 에 "캐시" 를 구성한다. DataBase 의 조회를 최소화해서 성능을 극대화 시킬 수 있다. 캐시를 구현한 아키텍처는 아래와 같다.

하지만, 해당 시스템은 명백하게 "Scale Up" 기반의 애플리케이션이다. 서비스가 확장일 될 수록 캐시 영역은 점점 커지게 될 것이다. 



그래서 뭐가 문제인가? 


해당 시스템은 몇가지 심각한 문제가 발생한다. 


- Cache 메모리 영역이 점점 커지면서 하드웨어의 메모리를 증설해야 하는 상황이다. 하지만, 이미 몇번 증설했고 더이상 증설하기 어려운 상황까지 왔다. 당시 서버 1대당 64G 메모리 까지 수차례 증설했던 상황이었다. 

- 각각 물리서버(애플리케이션)에서의 Cache 영역은 서로 동기화되지 않는다. 즉, Cache가 일치하지 않는 데이터 정합성 문제가 발생하여, 서버마다 다른 데이터를 제공하는 경우가 존재한다.  

- 애플리케이션 초기화 시 수행하는 Cache 메모리 초기화 작업은 너무 오랜 시간이 걸린다. Data Tier 를 기반으로 캐시를 초기화하는 작업은 너무 많은 시간이 걸린다. 당시 전체 데이터를 애플리케이션에 로드하는데 1시간 정도 소요가 되었다.


애플리케이션 초기화 및 배포가 어려운 상황이되자 개발자들은 해당 애플리케이션을 배포하는 것을 두려워했다. 코드를 수정하지도 않았고 재시작도 하지 않으려고 했다. 그렇기 때문에 가능하면 해당 애플리케이션을 수정하지 않는 방법으로, 다른 시스템에 지저분한 기능을 추가하기 시작하였다. 


문서에는 "재시작하지 마시오" 라고 작성되어있었다. 


기술부채가 심각한 상황에서, 제품 담당자의 요구사항을 해결하기 위해서 온갖 편법을 다 사용하였고, 지저분한 방법으로 개발했기 때문에 시스템의 구조는 지속적으로 악화되었다. 그렇게 시간을 낭비하는 사이에.... "기술부채"를 갚아야 하는 시점을 이미 놓쳐버렸다.


필자는 판단했다. 이미 몇번 늦었지만, 더 늦기 전에 지금이라도 해당 애플리케이션을 개선해야 한다. 


참고로 신규 프로젝트로 새롭게 만들 수 있는 상황은 아니었다. 


공용 캐시 구축 및 기술선택

현재 상황에서 가장 현명한 해결책은 공용 캐시를 구축해서, 캐시 영역을 따로 분리하는 것이다. 캐시는 Scale Out 할 수 있는 환경으로 구축하고, 애플리케이션의 영향을 받지 않고 개별적으로 확장 가능하도록 구축해야 한다. 


기술 요구사항을 검토하여 다양한 오픈소스를 검토하였고 최종적으로 Redis 를 사용하기로 결정하였다. 핵심 이유는 아래와 같다. 


애플리케이션에서의 비즈니스 로직은 Key-Value 기반에 적합하다.

Key-Value 기반 캐시 시스템을 구축하기에 성능,가용성 등 가장 안정적인 오픈소스 솔루션이다.

사내에 Redis 구축사례가 많아서 참고자료가 많다.

스프링 부트 환경에서 연동이 쉽다. 

짧은 일정안에 구축할 수 있다. 


필자 혼자서 분석/설계/개발/인프라 등 모든 과정을 혼자서 진행해야 하기 때문에, 리소스에 대한 부담이 컸다. 어쩔수 없이 가장 익숙한 솔루션을 도입을 하는 것이 좋겠다고 판단하였다. IMDG 등 다른 기술도 잠시 검토하였지만, Redis 가 최선이라고 판단하였다.  


Redis Sentinel Auto Fail Over 

가용성 높은 캐시 인프라 환경을 위해서, 필자는 Redis Cluster, Redis Sentinel 두가지 방법을 비교 검토하였다. 


Redis Cluster

Redis Sentinel


장단점을 검토한 결과, 굳이 Redis Cluster 로 진행하지 않아도 되겠다는 결정을 내렸다. 기본적인 Master-Slave 구성에 Sentinel 인스턴스를 구축해서 가용성을 확보하는 방향으로 진행하였다. Sentinel 관련해서는 공식 레퍼런스를 참고하길 바란다. 

http://redisgate.jp/redis/sentinel/sentinel.php

Redis Sentinel 를 구축했기 때문에, 해당 시스템은 갑작스런 장애 상황에서, Auto Fail Over 가 실행이 될 것이다. 아래와 같은 환경을 초기 구성하였다. 

갑작스럽게 Master 장비에 장애가 발생했다고 가정해보자. Sentinel 은 자동으로 마스터 장비의 Down 을 감지한다. 그리고, 아래 그림과 같이 Master Node를 신규로 선택한다. Server 2 가 신규 Master 로 선택되었다. 

죽었다가 다시 살아난 Server1 장비는 Slave 의 역할을 수행하게 된다. 

Sentinel 인스턴스는 홀수로 구축해야 한다. 더 자세한 내용은 필자의 글을 참고하길 바란다.

https://brunch.co.kr/@springboot/151

참고로, 일반적으로 Sentinel 인스턴스는 별도 서버에 구성을 하는게 좋지만, 부득이하게 Redis 서버 에 Sentinel 인스턴스를 띄우는 방식으로 진행하였다.


스프링 캐시 추상화

스프링은 애플리케이션의 캐싱을 투명하게 구현하기 위한 기능을 지원한다. @EnableCaching 어노테이션으로 캐싱 기능이 활성화 되면, 스프링부트에서 캐싱 인프라 구성을 자동으로 연동해준다. 캐시 추상화 관련해서 상세한 내용은 생략하겠다. 공식 레퍼런스를 참고하길 바란다. 

https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html


코드 리팩토링

불필요하게 모듈화 되어있던 부분을 제거하는 등 일부 리팩토링 작업을 진행하였다. 아쉽지만, 이번 작업에서는 리팩토링은 거의 진행하지 못했다. 


스프링 부트 전환 및 스프링 버전 업그레이드

기존 애플리케이션은 JDK 1.7, 스프링 3.X 환경이다. 이번 작업에서 JDK 1.8, 스프링 4.X 으로 업그레이드 하였다. 또한, 스프링부트로 전환하였다. 스프링부트로의 전환으로, 기존에 사용하던 외장톰캣은 제거하였고, 임베디드 톰캣 환경으로 구성되었다. 즉, 스프링부트 단일 애플리케이션을 배포하면 바로 서버가 실행할 수 있도록 심플하게 서버가 구성했기 때문에 빠른 배포가 가능해졌고, 애플리케이션을 효율적으로 개발/운영 할 수 있는 환경이 되었다. 


협업 및 고려해야할 사항

해당 프로젝트는 제품담당자와 협업을 하지 않으면 절대 성공할 수 없는 프로젝트이다. 테스트 과정이 꽤 길게 진행될 것이고 서비스 이슈가 발생할 가능성이 있기 때문이다. 제품 담당자(기획팀)에게 해당 프로젝트에 대해서 사전에 리뷰를 진행하였고 전체일정 동안 수행해야 하는 업무들에 대해서 자세하게 안내를 하였다. 또한, 테스트 과정 중 서비스 장애가 발생하지 않도록 꼼꼼하게 전환 시나리오를 구성하였다. 단, 필자가 너무 꼼꼼하게 시나리오를 구성해서 제품담당자의 일정을 너무 많이 사용하게 되었다. 협업에 대한 사전 동의를 얻고 천천히 함께 진행하였는데, 매우 고맙게 생각한다. 나중에 밥한번 샀는데, 오히려 제품담당자 분들께서 고맙다고 해서 보람을 느꼈다. 또한, 경영진에게는 해당 프로젝트의 중요성을 전달하고, 해당 프로젝트를 위한 신규 장비를 품의하였다. 해당 프로젝트는 오히려 서버 장비 비용을 절감하는 아키텍처라서 서버 장비 품의는 쉽게 진행되었다. 마지막으로, 필자의 소중한 개발리소스가 들어가는 일이기 때문에, 어떻게 하면 필자의 리소스를 적게 사용하고,  장애 없이 안정적으로 전환을 완료 할수 있을지 많은 고민을 했다. 



너무나도 다행히... 큰 장애 없이 작업을 완료하였다. 



3.2 일정상, 해결하지 못한 작업


일정상 진행하지 못한 몇가지 이슈가 있다. 여러가지 이슈가 있었는데 가장 중요했던 이슈만 정리를 해서 공유한다. 


캐시 추상화, 단일 Key 조회

(필자가 알고 있는 상식으로는)스프링 캐시 추상화는 단일Key 조회만 가능하다. 물론 Multi Key 조회가 가능하도록 별도의 메서드를 구현할 수는 있지만, 결국 내부 로직에서의 Redis Request 는 각 Key 개별적으로 호출하게 될 것이다. "우아한형제들" 기술블로그에도 비슷한 사례를 대응한 내용이 있는데, 정확한 해결책은 아닌 것으로 보인다. Multi Key 조회를 한 번의 리퀘스트로 응답받을 수 있도록 구현해서, 네트워크 리퀘스트 비용을 줄일 수 있어야 한다. 당장 서비스에 지장은 없는 상황이라서, 해당 이슈는 추후 작업으로 진행하기로 결정하였다. 


로컬 개발 환경 셋팅 미흡

필자는, 작업 진행 전부터 애플리케이션의 테스트 코드를 믿지 못하는 상황이었다. 오랫동안 코드 관리가 되지 않은 상황이었고 테스트 코드 빌드는 실패하는 상황이다. 리팩토링을 진행하기 전에 테스트 코드가 안정적으로 구성이 되어있어야 마음 편히 리팩토링을 진행할 수 있지만, 그렇지 못한 상황이라서 개발을 하면서도 마음이 편하지 않았다. 테스트를 위해 Redis 를 임베디드 환경으로 구성해야 좋지만, 해당 작업은 진행하지 못했다. 관련해서는 "창천향로"님의 글을 참고하길 바란다.

https://jojoldu.tistory.com/297



3.3 레거시 시스템 개선 사례(2) 성과


공용 캐시를 구축하여, 레거시 시스템의 "기술부채"를 갚음으로써 얻은 이득은 아래와 같다. 


- 애플리케이션 배포 및 재시작 시간이 단축되었다. 기존 1시간 --> 1분

- 장애가 발생할까봐 불안에 떨지 않아도 된다.

- 마음 편하게 애플리케이션을 수정해서 배포할 수 있는 환경이 되었다.


해당 애플리케이션은 서비스 최앞단에 위치하는 매우 중요한 API 서버이다. 하지만, 애플리케이션의 구조적인 문제로 인해서 코드 수정을 거의 진행하지 못하고 있던 상황이었다. 애플리케이션을 배포하면 1시간이라는 시간을 기다려야 하는 매우 심각한 레거시 시스템이었는데, 이제 배포시 1분이면 완료할 수 있다. 애플리케이션 배포 라는 것은 너무 당연한 작업이지만, 해당 레거시 시스템은 배포하지 않고 재시작하지 않고 그대로 놔두는 것이 당연시 되는 상황이었다. 필자는 그런 나쁜 상황을 개선하고자 노력했던 것이다.


"소프트웨어는 세상의 변화에 빠르게 대응할 수 있어야 한다."


완벽하지만 변경하기 어려운 소프트웨어를 만드는 것보다, 조금은 불안정하지만 변경하기 쉬운 소프트웨어를 만드는 일이 더 바람직하다. 세상은 빠르게 변하고, 요구사항은 지속적으로 요청이 되기 때문에, 소프트웨어는 유연하고, 변화에 빠르게 대응할 수 있도록 운영해야 한다. 그런 의미에서, 해당 작업은 의미가 큰 작업이라고 생각한다. 기존에는, 배포하는 것도 부담서러운 상황이었지만, 이제는 코드 수정을 하고 배포할 수 있다는 소박한(?) 희망이 생겼다. 그동안 방치했기 때문에 발생한 치명적인 "빚"을 이제서야 갚게 된 것이다. 물론, 필자가 진 "빚"은 아니지만, 해당 시스템을 담당하고 있는 필자가 갚아야 하는 "빚"이 된 것이다. 



시스템 개선 사례 (1),(2) 작업을 진행함으로써 레거시 시스템은 안정화 되었고, 장애는 많이 줄어들었다. 물론, 아직 장애가 주기적으로 발생하고 있지만 하나씩 개선해나가면 될 것이다. 사실, 필자가 해당 시스템의 히스토리를 잘 모르는 상황이라서, 진행하는데 많은 어려움이 있었다. 만약, 시스템 애플리케이션이 항상 "클린코드" 를 유지했었다면 필자의 작업은 훨씬 수월했을 것이다. 필자가 생각하는 "소프트웨어 개발의 지혜" 철학에 맞게 진행하였는데, 짧은 일정으로 인해서 많은 작업을 하지는 못했고, 여건상 진행하지 못한 아쉬운 내용이 많다. 


아쉬운 점에 대해서 간단하게 정리하였다. 



4. 아쉬움


해당 시스템의 가장 큰 문제는 HTTP 동기 프로토콜로 애플리케이션이 서로 복잡하게 섞여 있는 점이다. 백엔드 시스템에서의 HTTP 동기 프로토콜은 병목현상이 자주 발생시키고, 시스템을 확장하기에도 좋지 않다. 해당 아키텍처를 개선하기 위해서는 여러가지 방법이 있지만, 필자가 생각하는 가장 좋은 방법은 리액티브 시스템으로 전환하는 것이다. 



4.1 리액티브 시스템으로의 전환


우리의 상황

해당 시스템의 상황을 이해하기 위해서, "기업 통합 패턴 - 그레거호프,바비울프" 의 384page 를 참고하였다. 


일반적으로 시스템은 수많은 애플리케이션을 연결한다. 그런데 개별 애플리케이션들이 각자의 채널로 서로를 연결하게 하면, 결국 채널은 시스템이 관리하기 어려울 정도로 급속하게 폭발적으로 늘어나게 되어, 시스템은 마치 통합 스파게티인 것어럼 꼬이고 만다. (그림 참조)

포인트 투 포인트 연결이 만든 통합 스파게티 - 기업통합패턴 384page

그림처럼 시스템을 구성하면, 개별 애플리케이션들 사이를 직접 연결한 채널들은 채널 폭발을 야기한다. 애플리케이션들이 서로 일대일로 통신해야 한다면, 시스템을 유지보수하기가 엄청나게 힘들어 질 것이다. [기업통합패턴 384 page]


메시징, 메시지 브로커

비동기 메시징은 분산 시스템에 등장하는 많은 문제들에 대한 해결책이 될 수 있다. 메시징 패턴을 위해서 아래와 같이 "메시지 브로커" 를 구축해야 한다. 

메시징 시스템은 애플리케이션간의 결합도를 낮추고, 느슨한 연결을 할 수 있게 해준다. 하지만, 메시징 패턴이 항상 정답은 아니다. "메시지브로커" 라는 관리 요소가 추가 되어야하고, 복잡도가 증가할 수도 있다. 필자는 해당 작업을 진행하고 싶었지만, 팀상황, 개발리소스, 팀역량 등 전반적인 상황으로 인해서 진행하지 못하였다. 아쉽게 생각하지만, 혼자서는 절대 진행할 수 없는 일이었다. 



4.2 Cloud & MSA 로의 전환

검토 하였지만 진행하지 못했다. 



4.3 분산 환경 모니터링 시스템 구축

역시 잠시 검토하였지만 여건상 진행하지 못했다. 꼭 하고 싶었는데 아쉽게 생각한다. 



5."기술부채" 에 대한 필자의 생각 정리


주저리주저리 작성하다보니 글이 매우 길어졌다. 재미없는 글을 여기까지 읽어줘서 고맙게 생각한다. 여기까지 글을 읽은 개발자가 몇명이나 될지는 모르겠다. 아마 거의 없을 것 같지만, 혹시라도 읽어준 개발자가 있다면 진심으로 감사하게 생각한다. "기술부채"에 대한 필자의 생각을 최종적으로 정리하고 글을 마무리하겠다. 


"새로 만들면 되지..." 라는 말

"기술부채"를 개선하기 위해서 신규시스템을 구축하는 프로젝트를 진행하였지만, 해당 멤버는 모두 팀을 떠나고 없는 상황이다. 소프트웨어는 새로 만들던, 만들지 않던 항상 사용성 높게 유지하는것이 좋다. 당장 장애가 발생하고 있는 상황에서, "새로 만들면 되지..." 라고 안일하게 생각하면 안된다. 새로 만들고 있는 인력이 떠나면 어떻게 되는가? 모두 떠나 버리면, 남아 있는 딱 한명의 개발자는 어떻게 되는가? 


결국엔 사람

소프트웨어를 만드는 주체는 바로 "사람"이다. 소프트웨어를 복잡하게 하는 것도 "사람"의 욕심 때문이다. 소프트웨어를 제대로 관리하지 않아서 발생하는 "기술부채"는 결국 "사람"이 갚아야 하는 빚이다. "기계"가 대신 갚아줄 수가 없다. 소프트웨어를 제대로 관리하지 않으면, 소프트웨어도 문제이지만, 해당 소프트웨어를 다시 만들어야 하는 사람 역시 문제라는 사실을 깨달아야 한다. 


대단한 작업을 한것은 아니다. 하지만, 개발자의 책임감이 필요하다.

필자가 진행한 작업이 엄청 대단한 작업을 한 것은 아니다. 필자는 개발을 오랫동안 쉬고 다시 개발자로 돌아왔고, 개발 실력이 뛰어나지도 않다. 필자가 수정한 코드를 보고, "별로 큰 작업도 아닌데, 생색냈네..." 라고 생각할 수 도 있다. 하지만, 별거 아닌 작업이었는데 왜 그동안 매일같이 장애가 발생하게 방치했는지 되묻고 싶다. 작업의 난이도가 높고낮음을 떠나서, "클린 소프트웨어"를 위해서 어떤 노력을 했는지 되묻고 싶다. 신기술만 쫒아가면서, 새로운 기술만 배울려고 하지 않았나? 시스템이 장애가 계속 나고 있는데도 다들 알고리즘 공부만 하면서... 이직 준비만 하다가 전부 떠나지 않았나? 필자의 작업은 비록 대단한 작업은 아니었지만, 개발자로서 최소한의 책임감을 갖고 소프트웨어를 사용성 높게 유지하기 위한 노력이었다.


"기술부채"를 개선하는 일은 중요하다. 하지만, "기술부채"를 개선하는 작업을 Top-Down 방식으로 팀원에게 무작정 강요하지 마라. 

레거시 시스템을 개선하는 일을 팀원에게 무작정 강요하면 안된다. 해당 시스템을 왜 개선해야 하는지에 대해서 사전에 서로 이해를 해야하고, "기술부채"를 개선하는 일이 어떤 의미가 있는지 이해해야 한다. 관리자의 욕심으로 인해서, 성과를 내기 위해서 되지도 않는 개선 작업을 요구한다면, 해당 팀원은 오래 버티지 못하고 팀을 떠날 가능성이 높다. 그리고, 그런 관리자는 아마도 "기술부채"를 어느 시점에서 개선해야 하는지 정확히 판단하지 못하는 관리자일 가능성이 높다. 수평적인 시각으로 검토를 해라. 그렇게 해야 해당 작업을 진행하는 팀원이 동기부여를 갖고 주도적으로 진행할 수 있다. 자율성과 책임이 주어질 때 프로젝트에 임하는 개발자의 동기부여가 커진다는 점을 알아야 한다. 


"기술부채"를 개선하는 일은 중요하다. 하지만, 신기술이 모든 걸 해결해주지 않는다.

"기술부채"를 개선하기 위해 새로 만든 아키텍처는 신기술 중심의 아키텍처 일 수도 있다. 개발자는 항상 새로운 기술을 배우길 원하고, 필자 또한 신기술에 대한 관심이 항상 많다. 하지만, 신기술이 모든 걸 해결해주지 않는다는 사실을 반드시 깨달아야 한다. 단지, 배우고 싶다라는 이유로 신기술을 도입하는것에 대해서는 절대적으로 반대하는 입장이다. 아마, 이부분에서 일부 개발자는 필자를 "꼰대"라고 생각할 수도 있을 것이다. 관리자와 개발자를 모두 경험해본 필자의 생각으로는 신기술 도입은 신중해야 하는것이 좋다는 생각이다. 하지만, 필자가 관리자일 때 일부 팀원의 신기술 중심의 설계를 보고 반대 의견을 하지는 않았었다. 왜냐면, 해당 팀원이 자율성과 책임감을 갖고 동기부여를 갖는 것이 더 중요하다고 생각했기 때문이다. 하지만, 지금은 필자도 조금 생각이 바뀌어서, 잘못된 설계는 바로잡아주는것이 좋겠다는 생각이다. 해당 팀원의 욕심으로 인해서 팀 전체가 피해가 될 수도 있기 때문이다. 


협업의 중요성

기획팀 도움이 없이 절대 성공할 수 없는 작업이다. 필자는 프로젝트 진행 전에 기획팀에 리뷰를 진행하였고 협의를 통해서 진행하였다. 해당 작업을 진행하는 과정에서 발생하는 리스크에 대해서 충분히 설명을 하고, 테스트 지원에 대한 협업을 진행하였다. 혼자서 할 수 없는 작업이었다. 기술부채에 대해서 제품담당자 또는 기획팀에서는 정확하기 인지를 못할 가능성이 높다. 여유를 갖고 설득을 시켜야 하고, 커뮤니케이션 역량을 극대화 해야 한다. 원활한 커뮤니케이션은 필수이다. 


마무리하면서

생각의 흐름대로 작성한 글이라서, 글이 많이 길어졌다. 이 글을 처음부터 끝까지 전부 읽은 개발자가 몇명이나 될지 모르겠지만, 여기까지 읽어준 개발자가 있다면 진심으로 감사하게 생각한다. 또한, 필자의 글이 조금이라도 도움이 되었다면 다행으로 생각한다. 혹시라도, 필자의 생각과 다른 부분이 있을 수 있다. 소심한 필자를 너무 욕하지 않기를 바란다. 서로의 생각이 다를 수 있고, "기술부채"를 바라보는 시선도 조금씩 다를 것이다. 하지만, 서로 생각은 다를 수 있지만, "소프트웨어"를 잘 만들고 싶고, "소프트웨어"를 잘 운영하고 싶다는 생각은 모든 개발자가 똑같을 것이다. "기술부채"로 인해서 힘들어하는 개발자가 어딘가에는 분명 있을 것이다. "소프트웨어 개발의 지혜"에 대한 정답은 없지만, 항상 정답을 찾기 위해서 서로 노력하며 소통하면 조금씩 IT 환경이 개선 될 것이다. 주저리주저리 작성하다보니 글이 너무 길어졌는데 이정도로 마무리하겠다. 내년 이맘때쯤 필자는 또 어떤 글을 쓰게 될까?  내년 벚꽃이 피는 이맘때가 되면 "소프트웨어 개발의 지혜" 의 세번째 글을 쓸 수 있는 날이 올 것이다. 그날을 기다리면서 앞으로 1년도 건강하게 열심히 달려가겠다. 

매거진의 이전글 [서평]Clean Architecture

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari