
업무 프로그램 개발 견적, 몇 배씩 차이 나는 이유는 화면 아래 층에 있습니다
책상 위에 견적서 두 장이 나란히 놓여 있습니다. 같은 요구사항 문서를 보내고 받은 것입니다.
화면 목록은 거의 똑같습니다. 로그인·계약 목록·고객 상세·엑셀 내려받기·관리자 화면까지 줄마다 대응됩니다.
그런데 합계는 몇 배가 차이 납니다. 구매 담당자는 그 차이를 대표에게 설명해야 하는데, 견적서에는 설명할 근거가 적혀 있지 않습니다.
저는 보험대리점(GA) 지사의 계약관리 시스템을 만들고 운영하고 있습니다. 화면 목록에는 한 줄도 나오지 않는 일을 이 시스템에 꽤 많이 깔았습니다.
그 층들을 실제로 깐 순서대로 하나씩 꺼내 보겠습니다. 견적서를 비교할 때 어느 줄을 물어야 하는지가 거기서 보입니다.
화면 목록이 같은 두 견적서
화면 목록은 사용자가 보는 것만 셉니다. 계약 목록 화면이 하나라는 사실은 두 견적서에서 같습니다.
달라지는 건 그 화면이 뒤에서 무엇을 지키느냐입니다. 설계사 한 명이 남의 고객을 볼 수 있는지, DB 파일이 새어 나갔을 때 전화번호가 그대로 읽히는지 같은 것들입니다.
이런 층은 시연에서도 안 보입니다. 담당자가 관리자 계정으로 눌러 보면 두 프로그램은 똑같이 동작합니다.
지난 8월 저는 이 시스템의 전수 보안 감사를 외부 AI 코딩 에이전트에게 맡겼습니다. 리포지토리를 처음부터 끝까지 읽고 파일과 줄 번호를 근거로 문제를 적게 했습니다.
그 보고서 항목 중 DB 구조를 바꾸지 않아도 되는 11개를 먼저 고쳤습니다. 운영 서버에는 끊김 없는 교대 배포(blue-green)로 두 번 나눠 올렸습니다.
최상위 구멍 하나는 DB 구조 변경이 필요해서 이 묶음에서 뺐습니다. 그 구멍 이야기는 바로 아래에 이어집니다.
에이전트는 DB와 네트워크에 손이 닿지 않는 환경이라 실제 데이터 검증과 배포는 제 몫이었습니다.
남의 고객을 막는 자리
설계사가 자기 담당 계약만 보게 하는 방법은 크게 둘입니다. 화면에서 남의 계약을 숨기는 방법, 그리고 DB가 행마다 주인을 보고 걸러 주는 방법입니다.
화면 숨김은 싸고 빠릅니다. 대신 화면을 거치지 않는 요청 하나면 전부 보입니다.
이 시스템은 DB가 거르는 방식(행 단위 보안)을 쓰고 있었습니다. 그런데 감사가 찾은 최상위(P0) 구멍이 바로 이 벽에 있었습니다.
앱이 쓰는 DB 계정이 설정값 하나만 바꾸면 스스로 '전체 열람'으로 올라갈 수 있었습니다. 비밀번호가 새거나 입력창으로 SQL이 끼어드는 순간, 벽이 있으나 마나 해지는 구조였습니다.
고친 방법은 전체 열람을 아예 다른 DB 로그인 역할로 떼어 내는 것이었습니다. 앱 계정이 무슨 설정을 걸어도 그 역할이 될 수는 없습니다.
이 수정은 다음 날 따로 두 번에 나눠 배포했습니다. 교대 배포 중에는 옛 버전과 새 버전이 잠깐 같이 돌기 때문입니다.
첫 배포에서는 옛 경로와 새 역할을 둘 다 열어 두고 검증했습니다. 옛 경로는 두 번째 배포에서 지웠습니다.
검증은 공격자처럼 했습니다. 관리용 접속에서 앱 계정 권한으로 바꾼 뒤 그 설정을 직접 걸고 계약과 인물 테이블을 조회했습니다.
둘 다 0행이 나왔습니다.
연락처를 잠그는 층
고객 연락처와 주소는 처음에 DB에 글자 그대로 들어 있었습니다. 백업 파일 하나가 새면 그대로 읽히는 상태였습니다.
이 층은 두 번에 나눠 깔았습니다. 먼저 암호문 칸을 새로 만들고, 저장할 때 평문과 암호문을 같이 쓰고, 읽을 때는 암호문을 먼저 보게 했습니다.
그다음 기존 5,013행을 암호문으로 채웠습니다. 행마다 암호화한 값을 다시 풀어 원래 값과 맞는지 왕복 15,686건을 검증했습니다.
평문 칸을 지우는 일은 되돌릴 수 없습니다. 그래서 지우기 직전에 평문은 있는데 암호문이 없는 행을 세어 0건인 것을 보고 나서 지웠습니다.
여기에는 갈림길이 하나 있었습니다. 계획서는 관리자만 원문을 보고 나머지는 가리는 방식을 권했습니다.
지사 쪽은 설계사 화면에 전화번호를 그대로 두기로 했습니다. 고객에게 전화하는 일이 설계사의 하루 업무라 가리면 일이 멈추기 때문입니다.
저장 암호화와 화면 표시는 서로 다른 문제입니다. 이 결정 덕에 가림 처리와 열람 버튼, 건별 열람 기록이 빠졌고 구현이 크게 줄었습니다.
누가 봤는지 남기는 층
주민등록번호는 관리자만 전체를 볼 수 있게 했습니다. 감사 이후 이 열람은 기록이 먼저 남아야 번호가 나가도록 바꿨습니다.
기록 저장이 실패하면 번호를 내보내지 않습니다. 접속한 IP도 같이 남깁니다.
그 열람 기록 테이블에는 덧붙이기만 되게 막는 장치를 달았습니다. 기록을 고치거나 지우는 요청은 DB가 거절합니다.
견적서에서 이 층은 보통 "로그 관리" 한 줄로 끝납니다. 기록이 실패할 때 원문이 나가는지, 기록을 지울 수 있는지까지 물어보세요.
백업이 정말 돌아오는지
백업은 대부분의 견적서에 들어 있습니다. 그 백업을 실제로 다른 곳에 풀어 본 적이 있는지는 거의 적혀 있지 않습니다.
저는 복원 리허설을 배포 절차에 넣었습니다. 그런데 나중에 보니 리허설 컨테이너가 운영보다 한 단계 낮은 Postgres 버전에 복원하고 있었습니다.
운영은 16, 리허설은 15였습니다. 리허설 컨테이너를 16으로 맞추고 다시 풀어 봤습니다.
처음 리허설을 돌린 날은 아예 중간에 멈췄습니다. DB 덤프에는 로그인 역할이 담기지 않아서 권한을 주는 줄에서 "그런 역할이 없다"는 오류가 났습니다.
백업은 매일 성공하고 있었고 크기도 정상이었습니다. 야간 백업에 역할 정의만 따로 떠 두는 파일을 더한 뒤에야 복원이 끝까지 갔습니다.
DB 통합 테스트 14개도 붙였습니다. 운영과 같은 세 역할로 임시 DB에 붙어 권한 경계를 확인합니다.
그중 "옛 전체 열람 조건이 어느 정책에도 남아 있지 않다"는 전수 검사 한 줄이 실제로 하나를 잡았습니다. 나중에 만든 테이블 정책이 옛 조건을 복사해 되살려 놓았던 것입니다.
되돌리는 길과 그 끝
배포가 실패했을 때 이전 버전으로 돌아가는 롤백 명령도 배포 도구에 넣었습니다. 장애가 난 자리에서 손으로 고치는 일을 줄이려는 장치입니다.
롤백에도 끝이 있습니다. 연락처 평문 칸을 지운 뒤로는 평문 칸을 읽는 옛 버전 앱으로 되돌리지 않기로 정했습니다.
옛 앱은 이미 지워진 칸을 읽으려 하기 때문입니다. 되돌릴 수 없는 변경이 들어간 배포부터는 롤백 대상이 새 버전들로 좁아집니다.
견적서에 "롤백 지원"이 적혀 있다면 되돌릴 수 없는 변경 뒤에는 어떻게 하는지도 물어보세요. 배포 도구가 성공이라고 보고했는데 실제로는 적용되지 않은 경우를 잡는 법은 외주 검수 글에 43과 42라는 숫자로 남겨 두었습니다.
빼도 되는 층
반대로 깔지 않아도 되는 층도 있습니다. 사내 그룹웨어를 만들 때 국내 협업툴을 조사하고 내린 결론이 그랬습니다.
수십 명 규모 조직에는 업무를 보드·플래닝·타임라인·캘린더로 바꿔 보는 기능이나 자동화 규칙은 과잉이었습니다. 보드와 담당자, 만기, 상태 정도면 충분했습니다.
비싼 견적이 이런 층을 잔뜩 얹은 결과일 수도 있습니다. 화면 목록 옆에 "우리 규모에 필요 없는 기능"을 적어 빼 달라고 하면 금액이 내려갑니다.
칸이 빈 견적서 읽는 법
비교표에 X가 많다고 그 견적을 탈락시킬 필요는 없습니다. 무엇을 빼서 싼지 알고 고르면 됩니다.
고객 개인정보가 들어가고 여러 사람이 서로 다른 범위를 봐야 한다면 위쪽 네 줄은 빼기 어렵습니다. 내부 일정표처럼 민감한 정보가 없다면 암호화와 열람 기록은 나중으로 미뤄도 됩니다.
화면 위로 드러나는 부분은 이음 데모에서 확인됩니다. 대표·관리자·설계사로 역할을 바꾸면 보이는 계약 범위가 달라지고, 주민번호는 뒷자리가 가려져 나옵니다.
데이터는 전부 가짜로 바꿔 두었습니다.
견적을 받기 전에 요구사항을 표로 먼저 세우고 싶다면 업무 시스템 도입 교육이 그 표를 같이 만드는 과정입니다.
받아 둔 견적서 두 장에서 어느 층이 빠졌는지 같이 읽어 보고 싶을 수도 있습니다. 그럴 땐 문의 양식에 견적서 항목을 붙여 보내 주셔도 됩니다.
견적 비교표
받은 견적서마다 아래 줄에 O나 X를 적어 보세요. 견적서에 문장이 없으면 X입니다.
| 확인할 줄 | 견적서에서 찾을 문장 | 빠졌을 때 감수할 것 |
|---|---|---|
| 역할별 열람 제한 | DB가 행 단위로 거르는가, 화면에서 숨기는가 | 화면을 거치지 않는 요청 하나로 전체가 보임 |
| 개인정보 저장 암호화 | 연락처·주소·주민번호를 암호문으로 저장하는가 | 백업 파일 유출 시 원문 노출 |
| 열람·수정 기록 | 기록 실패 시 원문을 막는가, 기록을 못 지우게 했는가 | 사고 뒤 누가 봤는지 답할 수 없음 |
| 백업과 복원 리허설 | 운영과 같은 버전에 실제로 풀어 보는가 | 필요한 날 백업이 안 돌아올 수 있음 |
| 배포 실패 시 롤백 | 되돌리는 절차와 배포 후 확인 항목이 있는가 | 장애 때 손으로 복구 |
| 출시 후 유지보수 범위 | 몇 달, 어떤 요청까지, 비용은 어떻게 | 작은 수정마다 새 견적 |
| 우리 규모에 과잉인 기능 | 빼 달라고 적은 줄이 반영됐는가 | 쓰지 않는 기능 값을 냄 |
마지막 줄은 직접 채우는 칸입니다. 견적을 다시 받을 때 이 줄을 요구사항 문서 맨 아래에 붙여 보내면, 다음 견적서들은 같은 기준으로 금액을 적어 옵니다.
이어서 읽기
정산 프로그램 마진이 실제보다 크게 나올 때, 공식부터 보는 법
정산 앱이 띄운 누적 지사마진이 본사 확정 장부의 약 4.5배였고, 적자인 달 셋을 흑자로 그리고 있었습니다. 장부 항등식은 원 단위까지 맞았는데 화면 공식이 13항목 중 2항목만 보던 사례와 화면을 점검하는 3단계 절차를 적었습니다.

매장 예약 관리, 엑셀 대신 작은 예약 시스템으로 시작하기
담당자별 예약이 메모장과 메신저에 흩어진 매장이라면 업무 전체를 바꿀 필요는 없습니다. 접수와 담당자 일정, 일별 예상 매출만 묶은 예약 화면의 크기를 데모로 짚고 엑셀로 충분한지 가르는 문항과 첫 범위를 적었습니다.

본사 마감 엑셀 양식이 매달 바뀔 때, 업로드가 반만 들어가는 문제
본사가 보내는 월 마감 엑셀은 매달 열이 빠지고 형식이 바뀝니다. 보험대리점 지사의 마감 적재를 다시 만들며 정한 기준, 처음 보는 변화에서 멈추고 넣은 뒤 DB에서 다시 세는 방식을 실제 숫자와 실패까지 함께 적었습니다.