2026.09 — 현재회사 프로젝트
VGOLF 경기관제 PM30
태블릿 경기관제 앱을 소형 단말(PM30)로 옮기며 구조를 다시 세운 작업
- 맡은 일
- Frontend 단독 — 저장소 분리 · 리팩토링 · 태블릿 기능 이관
- 기간
- 2026.09 — 현재
- 소속
- 플럭시티클라우드팀
반년 동안 유지보수하며 쌓은 신뢰로 경기관제 앱의 개편까지 맡게 된 작업입니다. 골프장 경기관제 앱은 캐디용 태블릿과 모바일 코드가 한 저장소에 섞여 있었습니다.
현장 단말이 세로로 고정된 소형 기기(PM30)로 바뀌면서, 모바일만 떼어낸 저장소를 만들고 태블릿에만 있던 기능을 옮기는 일을 맡았습니다. 분리 이후 커밋 68건 중 66건이 제 작업입니다(나머지 2건은 배포 설정).
기능을 옮기기 전에 구조부터 손봤습니다. 5초마다 849KB를 다시 쓰던 저장 방식을 고쳤고, 운영 번들에 섞여 있던 개발용 데이터를 찾아 분리했고, 테스트가 하나도 없던 코드에 162개의 테스트를 붙였습니다.
다음 사람이 이어받을 수 있도록 구조·데이터 흐름·GPS 판정 원리·이관 목록을 문서 13개로 남겼습니다.
i.Problem
태블릿용으로 만든 앱을 작은 PM30 단말로 옮겨야 했는데 화면이 버벅이고 기기가 뜨거워졌습니다. 처음에는 기기 사양 문제로 보였지만, 들여다보니 0.5초마다 849KB 를 통째로 다시 쓰고 있었습니다. 운영 중인 앱이라 멈추고 다시 짤 수도 없었고, 테스트는 한 개도 없었습니다.
ii.Action
한 번에 갈아엎지 않고 조금씩 바꾸고, 테스트가 통과해야 다음 작업으로 넘어갔습니다. 저장소를 분리하되 분리 때문에 새로 죽은 파일만 골라 지웠고, Recoil 상태 98개를 쓰기 빈도와 크기로 다섯 칸에 나눠 zustand로 옮기면서 필요한 것만 쓰도록 바꿨습니다. 파일 구조도 기능 단위로 다시 짰고, 운영 번들에 통째로 들어 있던 개발자 모드 목업도 찾아 분리했습니다.
iii.Result
상태 저장 한 번이 7.89ms 에서 0.058ms 가 됐고, 진입 번들은 1,485KB 에서 652KB 가 됐습니다. 느림과 발열이 사라졌고, 실시간 처리도 매끄러워져 운영처에서 좋은 반응을 받았습니다. 테스트는 0에서 162개가 됐습니다.
핵심
- 01
저장소 분리
태블릿 코드 328파일·34,827줄을 걷어내되 '분리 때문에 새로 죽은 파일'만 골라 지움. JS 번들 1,991 → 1,557KB
- 02
Recoil → zustand 5
98개 상태를 쓰기 빈도×크기 기준 5개 저장 칸으로 나누고 직렬화를 몰아서 처리. 상태 저장 1회 7.89ms → 0.058ms
- 03
운영 번들에 통째로 들어 있던 개발자 모드 전용 목업 데이터를 찾아 분리
진입 청크 1,485 → 652KB(−56%)
- 04
테스트 0 → 162개(22파일). 코드를 일부러 깨서 테스트가 실제로 잡는지 확인하는 변형 검증까지
나머지 1개 더 보기접기
- 05
실기기 2대로 재현한 팀 스코어 버그
157ms 차이로 합류한 두 기기가 서로가 빠진 명단을 받던 원인을 찾아 수정
화면
Pl. 2of9
테스트 클럽과 테스트 계정으로 PM30 실기기에서 찍은 화면입니다. 기기 식별자는 가렸습니다.
자세히
궁금한 항목을 펼치면 그때 무엇이 문제였고 어떻게 판단했는지 나옵니다.
vgolf-app(태블릿+모바일)에서 모바일만 떼어낸 저장소. 히스토리를 그대로 승계해 git blame·bisect가 분리 이전 커밋까지 동작
삭제 기준
원래 죽어 있던 파일은 손대지 않고(분리와 무관한 기존 부채), 태블릿 제거로 새로 죽은 파일 중 살아남는 파일이 하나라도 참조하면 제외하는 고정점 계산으로 판정. 아직 화면에 붙지 않은 모바일 작업물은 그대로 보존
태블릿 폴더에 섞여 있던 공용 코드(GPS 좌표 변환 554줄 등)는 shared로 옮기되 하위 경로 깊이를 유지
태블릿 저장소의 커밋을 cherry-pick할 때 경로 치환만으로 대응되게
모바일 식음 화면 7개가 태블릿 번역 사전을 쓰고 있던 것을 발견
그대로 지웠으면 주문 화면 문구가 4개 언어 전부 깨질 상황. 태블릿에만 있던 키 44개를 4개 언어로 이관하고 모든 번역 키가 해석되는지 전수 검증
TS/TSX 589 → 342파일, JS 번들 1,991 → 1,557KB, CSS 778 → 623KB, 의존성 29 → 22개
recoil-persist는 75개 상태가 localStorage 키 하나를 공유해, GPS 좌표 24바이트가 바뀔 때마다 849KB를 읽고 파싱하고 다시 썼음. GPS 엔진은 이걸 0.5초마다 호출
Recoil은 2023년 이후 릴리스가 없고 저장소가 아카이브돼 React 19로 올라갈 수 없는 상태
zustand 5로 이행
쓰기 빈도×크기에 따라 저장 칸을 hot·session·bulk·config·volatile 5종으로 나누고, 직렬화를 1초 단위로 몰아서 한 번만 수행
Chrome 측정(849KB, 쓰기 200회)
1회 7.89ms → 0.058ms, 합계 1,578ms → 11.6ms
기존 사용자 데이터는 recoil-persist → zustand 1회 이관 코드로 옮기고, 모든 상태에 저장 정책이 등록돼 있는지 테스트로 전수 검사
배운 것
운영 중인 앱의 상태 관리를 바꾸는 일은 '옮기면 좋다'는 걸 아는 것과 완전히 다른 일이었습니다. 앱이 멈추면 골프장 현장이 멈추기 때문에, 한 번에 갈아엎는 대신 조금씩 바꾸고 테스트가 통과해야 다음으로 넘어가는 순서를 지켰습니다. 느리더라도 이 순서를 지키는 것이 운영 중인 앱을 바꾸는 사람의 책임이라고 생각했습니다.
제대로 옮기려면 두 가지를 깊이 알아야 했습니다. 하나는 라이브러리
Recoil과 zustand가 상태를 어떻게 구독하고 저장하는지를 바닥부터 공부하고 작은 예제로 여러 번 연습한 뒤에야 실제 코드에 손을 댔습니다. 다른 하나는 도메인 — 라운드·홀·스코어·GPS 판정이 어떤 경우에 어떻게 움직이는지를 빼곡하게 적고, 그 경우의 수를 하나씩 테스트로 확인했습니다.
기기 사양 탓이라고 다들 넘기던 느림과 발열이 구조를 고치자 사라졌습니다. 앱이 가벼워지자 실시간 처리까지 매끄러워지는 걸 보면서 짜릿했고, '원래 그런 것'으로 여겨지던 문제를 숫자로 재서 다시 보는 습관이 생겼습니다.
반년 동안 유지보수하며 쌓은 신뢰로 개편을 맡게 됐고, 그만큼 책임도 무거웠습니다. 스트레스가 컸지만 노후한 앱이 빠른 앱으로 바뀌는 과정을 끝까지 맡으면서, 이전보다 한 단계 자랐다고 느꼈습니다. 이 작업 이후로 더 많은 일을 맡게 됐습니다.
쓴 기술
이어서 볼 것
플럭시티에서 한 다른 프로젝트