여름방학에 2달 정도 인턴을 했는데, 팀장님의 권유로 2학기에도 인턴을 계속 하게 되었다. 여름방학 인턴이 프론트 2명, 백엔드 3명, 데이터 1명이 뽑혔는데 그 중 데이터를 맡았던 분과 나만 2학기 연장 권유하셔서 같이 일하고 있다.
나는 2학기에 수업을 들어야해서 다른 회사의 인턴이나 다른 활동을 하기 애매하고, 민폐끼칠 것 같아서 회사에 먼저 지원을 하지 못했었고, 남는 시간에 부트캠프를 들으려고 했다. 하지만 팀장님께서 인턴 계속 연장하자고 말씀해주시고, 내가 이런 상황에 놓여있다고 말씀드렸더니 흔쾌히 회사와 학업을 병행할 수 있도록 도와주셔서 너무 감사했다. (진짜입니다..팀장님... 말씀은 직접적으로 못 드렸지만 너무 감사합니당🙂)
팀장님은 항상 인턴 2달은 너무 짧다고 말씀하셨는데, 나도 2달만 딱 하고 끝내려고 하니 아쉬웠다. 회사 내 중요한 프로젝트에 참여하기에는 인턴이라는 지위도, 2달이라는 시간도 너무 짧아서 어려웠다. 다행히 여름 방학 기간 중 마지막 3주~한달 정도 아까 말한 프로젝트에 참여하고 싶다고 말씀드렸더니 '진작에 말하지~ 알겠어. 물어보고 넣어줄게'라고 하셨고, 참여하게 되었다. 지금도 그 때 말하던 프로젝트에 참여해서 서버 개발을 담당하고 있다. 결과적으로는 여름 방학 2달과 2학기까지 계약하게 되었고, 6달정도 인턴을 진행할 계획이다.
다른 활동도 하고 있다. 계약 연장을 하고 나서 뭔가 더 해야지! 하고 신청한 게 아니다. (전공 필수 수업 1개만 들음) 학교를 다녀야하는 입장에서 시간이 많이 남았기 때문에, 동아리나 스터디, 부트캠프도 하려고 일을 벌려놓았다..ㅎㅎ 사실 이 중 지원은 했지만, 몇 개는 떨어질 것 같았는데 다행히 붙어서 여러 가지 진행하고 있다. 그래서 평일은 아침부터 열심히 살아가려고 노력하고 있다. 그래도 주말은 지켜냈어!!
회사에서 내가 맡은 일을 크게 2가지이다.
1. 보험 업무 자동화 시스템 구축을 위한 서버 개발
2. 회사 내 자체 프로젝트 서버 개발
두 가지 일을 하면서 성장을 많이 했다고 느낀다.
'A+ 에셋' 쪽 보험 업무를 자동화하려고 한다. 현재는 보험 계약을 진행하는 TFA가 있고, 이들을 관리하고, 전체적인 운영을 담당하는 본사가 존재한다. 현재 본사는 Excel을 기준으로 업무를 진행하고 있고, 그러다보니 DB 구축과 시스템 구축이 필요하다.
또한 현재 작업을 전부 수기(수동)으로 하고 있다보니 추석, 설, 명절 등 이벤트가 있을 때마다 기존 계약과 새로운 계약들에 대해 정보 업데이트 및 계산을 할 때 '일주일'이 넘는 시간이 소요된다. 하지만 각 이벤트마다 결국 하는 일은 같기 때문에 한 번 자동화를 해놓으면 업무가 간편해질 것이다.
내가 맡은 업무 이외에 다른 인턴이 맡은 업무도 하나의 서버에 들어가야 한다. 다른 업무 중 크롤링이 필요했고, 셀레니움을 사용하기 위해 FastAPI, AWS EC2, Lambda, RDS를 사용하여 작업을 진행하기로 했다. 사람이 수기로 작업을 진행했기 때문에 기존 데이터에 잘못된 값이 있었고, 이걸 개발자 입장에서 데이터를 활용하기 위해서는 분석이 필요했다.
데이터 분석을 진행하고, 일주일마다 본사쪽에 찾아가 회의를 진행하며 실제로는 어떻게 작업을 진행하는지 알아보고, 이걸 자동화하는 작업을 진행했다.
지난번에 글을 적었던 것 같아서 비교적 중요하지 않은 것은 빠르게 스킵하겠다.
자동화하는 업무, 테이블 설계하고 API 개발하는 업무, AWS EC2 서버와 현재 회사에서 백오피스로 돌아가는 서버와의 연결을 마쳤다.
결과적으로는 백오피스 페이지에서 Excel 파일을 업로드하면 해당 데이터를 가지고, 로직 돌리고, 결과 리턴하는 방식으로 구현하였다.
어떤 고민을 했고, 어떻게 해결을 했는지 시간순으로 작성하겠다.
학생 때는 대부분의 API가 CRUD에 그치고, 다루는 데이터도 많지 않아 고민을 한 적이 없었다. 처음 프로젝트를 맡았을 때도 성능에 관한 고민을 하지 않았고, 내게 주어진 데이터도 VIP명단을 받았기에 5000개 수준이었다.
성능을 고민하기 보다는 로직을 어떻게 작성해야 올바르게 결과물이 나올까 라는 구현에 대한 고민을 했다.
아직 5000개이기 때문에 엄청 오래 걸리지 않았다. API를 처음 돌리면 10분 정도 걸렸다. 로직 내에 외부 API를 데이터 갯수 * 2를 호출해야 되는 상황이었다. 해당 API call은 필수적이기 때문에 어쩔 수 없이 걸리는 시간은 있었다. 하지만 구현에 급급한 나머지, Group을 생성하는 알고리즘을 DFS로 작성하였다.
해당 문제는 DFS 형태와 비슷하지만, 중복이 가능하고, 중복된 곳을 방문하면 기존에 있던 경로를 모두 업데이트해야 한다는 조건이 있기에 DFS가 올바른 해결책은 아니었지만, 알고리즘 돌아가는 시간보다는 외부 API call에서 걸리는 시간이 더 길어서 문제를 알지 못했다.
본사에서 업무를 담당하고, TFA, 고객 등을 관리하는 사람을
" 현업 "
이라고 하겠다.
어느날 현업에서 VIP가 아닌 가격을 기준으로 더 많은 데이터를 돌려달라고 했다. 받은 데이터는 약 20만개... 물론 실제 기업에서 사용하는 데이터에 비하면 매우 귀여운 숫자이다. 다른 API 개발하면서 사용 중인 개발 DB에서 가져온 데이터는 500만개였으니까.
하지만 내가 작성한 API는 20만개를 돌리려고 하니, 2시간이 걸렸다. 외부 API에 들어간 시간도 30분 이상 걸렸지만 내가 짠 알고리즘이 너무 많은 시간을 잡아먹었다. 이제부터 그 과정을 알아보자.
미리 말하자면 나는 이걸 20분 이내로 단축시켰다. 심지어 외부 API에 들어간 시간을 제외하면 내가 작성한 코드가 돌아가는 시간은 5분 이내이다.
| 기존 | 개선
총 걸린 시간 | 약 2시간 --> 20분 이내
외부 API 소요 | 약 30분 --> 15분 이내
알고리즘 | 약 1.5시간 --> 5분 이내
문제가 되는 것은 Insert, Update 쿼리이다.
Postgres를 사용하고 있었고, Excel 데이터를 DB에 넣고, 로직을 돌리면서 Update하는 식으로 진행했다. 하나씩 데이터를 넣는 과정도 오래 걸렸지만, Update 과정에서 너무 많은 시간이 걸렸다.
Excel을 CSV 형태로 변환하고, Insert -> Copy로 변경하였다.
기존 10분이 넘는 Insert 과정이 단 몇초 이내에 끝이 났다. 대신 Copy를 할 때 올바르지 못한 데이터가 섞여있다면 에러가 발생한다.
예를 들어 테이블 컬럼과 CSV 컬럼이 다른 경우, 컬럼의 데이터 타입이 다른 경우, 컬럼의 타입은 같지만 실제 데이터가 타입과 맞지 않는 경우 등 여러 가지 제한 조건이 있지만, 이것은 데이터를 전처리하고 잘못된 데이터를 어떻게 표현할지 정하여 해결하였다.
그 과정도 쉽지만은 않았지만 Insert 시간을 없애기 위해서 Copy는 대량 데이터를 다루기에 매우 효율적이었다.
Update 쿼리는 원래 오래 걸린다.
Select, Insert와 Delete(삭제는 대부분 사용하지 않는다. soft delete를 사용하기 때문) 쿼리보다 Update는 오래 걸린다.
따라서 Update를 사용하지 않기 위해서 Join을 사용하였다. Join을 사용하기 위해서 (Join에 필요한 컬럼 + PK 컬럼만 담은) 새로운 테이블을 생성하였고, Left Outer Join을 진행하였다. 이 결과를 담아서 위에서 말한 Copy를 진행하였다.
Insert와 Update 쿼리 수정만 해도 많은 시간을 절약할 수 있다.
Index는 필수적이다.
학생 때는 개념만 알던 Index이지만, 필요성과 성능을 느끼고 난 후 Index는 필수적이라고 생각했다.
PK에 해당하거나 Where절에 많이 들어가는 컬럼에 대해서 Index를 설정하고, 쿼리 또한 커버링 인덱스를 활용하는 방법으로 수정하였다. Index 관련 내용은 아래 글을 첨부하였다.
Select는 다른 쿼리보다 비교적 빈번하게 사용하는데, Select 자체가 다른 쿼리에 비해 오래 걸리지 않다고 해서 그냥 넘어가면 안된다. 인덱스를 적용하는 것만으로도, 기존보다 몇배는 빠른 쿼리 응답속도를 볼 수 있다. 나는 테이블마다 다른 인덱스를 적용하였고, 실제 쿼리를 날려보면서 성능을 비교하였다.
DBeaver를 사용하여 Explain analyze 구문을 쿼리 앞에 적용하면 어떻게 쿼리가 계획되고, 어떤 방식으로 Scan 하는지 알 수 있다.
커버링 인덱스는 Index Only Scan을 하도록 도와주는데 글 보면 나올걸...??? 모르겠다. 써놨었나...?
알고리즘 자체를 바꾸기 - Union-Find 적용
위에서 말한 것 같이 기존은 "DFS + 되돌아가서 모두 업데이트" 이런 식으로 작성했었다. 하지만 그룹을 나누고, 새로운 그룹에 추가하거나, 합치거나 등등을 하기에는 Union-Find 방식이 적합하다는 것을 알게 되었다. 혁신적으로 시간이 개선되었다. 30분이 넘는 과정을 3분으로 단축했다.
외부 API Call은 보내는 데이터를 캐싱하기
외부 API로는 Kakao map API인데, 기존 데이터가 구주소, 신주소 등 체계가 잡혀있지 않고, 사람이 작성한 것이라 주소 체계가 잡혀있지 않았다.
따라서 모든 계약자와 피보험자의 주소를 신주소로 바꿔야했다.
그렇다면 API call하는 횟수 자체를 줄이면 어떨까라는 생각을 했다. 회사 -> 직원 같은 계약은 계약자가 회사이니까 주소가 겹치는 레코드가 많이 생성된다. 이 때 Set을 사용해서 같은 주소라면 API call 하지 않고 Set에 담겨진 Value값을 가져온다.
같은 주소라도 다르게 적힌 경우도 있고, 동, 호가 달라서 다르게 인식될 수 있지만, Set을 이용해서 캐싱을 적용하여 기존 외부 API에 소모하는 시간을 40%~50%정도 단축했다.
백오피스와 연결을 끝내고, 명시적으로는 작업이 끝났다. 하지만 작업을 하면서 여러 가지 수정 사항이 있었고 이를 빠르게 해결하기에 급급했다. 예를 들어,Excel을 받는데, 다른 이벤트 때문에 컬럼이 다른 Excel도 들어갈 수 있도록 수정해달라, 어떤 컬럼도 추가해달라, 어떤 정보도 계산해달라 등등...
당연히 일어날 수 있는 일이라고 생각하고, 빠르게 쳐내다보니 '인턴 기간이 끝나면 누군가는 받아야 할 코드이고, 업무인데 이걸 받았을 때 잘 알아볼 수 있을까?' 라는 고민이 들었다. 그래서 리팩토링을 진행하려고 한다.
쿼리를 바꾸는 게 아니라, 기존 로직은 유지하고 사람이 알아볼 수 있는 코드, 깔끔한 코드로 바꾸려고 한다. 안 쓰는 코드 지우고, 변수나 함수명도 알아볼 수 있도록 바꾸고, 폴더 구조 변경하는 방식으로 하려고 한다. 이런 것도 학생 프로젝트라면 안 했을텐데 인턴을 하면서 개발 뿐만 아니라 유지, 보수, 관리까지 생각을 하게 되었다.