"AI 낙찰률 예측 적중률 90%" 같은 문구를 보신 적 있을 겁니다. 저희도 같은 기능을 만들면서 그 숫자가 어떻게 만들어지는지 들여다봤고, 저희 것도 검증해봤습니다.
결과부터 말씀드리면 저희 적중률은 70.0%입니다. 검증 4,095건 중 2,868건입니다. 이 글은 그 숫자를 자랑하려는 글이 아니라, 이 바닥의 적중률 숫자가 왜 대부분 의미가 없는지, 그리고 저희가 무엇을 숨기지 않기로 했는지에 대한 글입니다.
[2026년 8월 갱신] 이 글은 처음 검증 89건으로 57.3%를 공개하며 썼습니다. 이후 과거 낙찰 데이터를 대량으로 모아 검증 표본을 4,095건으로 늘렸고, 그 과정에서 저희가 이전 글에 적었던 판단 중 두 가지가 틀렸다는 것도 드러났습니다. 아래 4번과 5번에 바뀐 내용을 정리해뒀습니다.
[8월 8일 추가] 표본을 늘린 뒤 서비스가 실제로 멈췄고, 그걸 고치는 과정에서 알게 된 것들을 8번부터 13번에 덧붙였습니다. 발주처마다 낙찰가가 몰리는 폭이 열 배 넘게 차이 난다는 것, 기업정보가 절반만 조회되는 이유, 그리고 저장에 성공한 것처럼 보이지만 실제로는 저장되지 않던 이 게시판의 버그까지 적었습니다.
━━━━━━━━━━━━━━━━━━━━
1. 분모가 없다는 문제
낙찰 예측 서비스들의 홍보 페이지를 보면 대체로 이런 식입니다.
· "AI 예측가 12억 3,400만원 / 실제 낙찰가 12억 3,100만원 — 오차 0.024%"
· 이런 사례가 목록으로 쭉 나열됨
· "누적 100만건 분석"
여기서 빠진 게 하나 있습니다. 몇 건을 예측해서 몇 건이 맞았는지가 없습니다.
분자(맞은 사례)만 있고 분모(전체 시도)가 없습니다. 100번 예측해서 3번 맞았어도 그 3번만 실으면 저 페이지가 똑같이 만들어집니다. 빗나간 97번은 페이지에 존재하지 않으니 아무도 모릅니다.
"누적 100만건"도 마찬가지입니다. 그건 보통 참고한 낙찰 데이터의 양이지, 예측을 100만번 검증했다는 뜻이 아닙니다. 재료의 양과 성적표는 다른 이야기인데 나란히 놓으면 하나처럼 읽힙니다.
이게 얼마나 심각하냐면 — 분모가 없으면 그 숫자는 반증이 불가능합니다. 검증할 수도, 틀렸다고 말할 수도 없습니다. 검증할 수 없는 주장은 정보가 아닙니다.
━━━━━━━━━━━━━━━━━━━━
2. 오차 0.02%가 대단해 보이는 이유
이건 저희가 직접 데이터를 보고 놀랐던 부분입니다.
공공입찰의 낙찰가는 아무 숫자나 나오지 않습니다. 적격심사 대상 공사·용역은 낙찰하한율이 정해져 있고, 실제 낙찰률은 대체로 87~99% 구간에 몰려 있습니다. 즉 아무 예측도 하지 않고 "추정가격의 88% 정도일 겁니다"라고만 말해도 상당수가 근접합니다.
여기서 "오차 0.02%"라는 문구가 인상적으로 보이는 건 착시입니다. 애초에 좁은 범위 안의 숫자를 맞힌 것이라, 예측 모델이 없어도 비슷하게 나옵니다. 비교 대상(아무것도 안 했을 때의 오차)을 같이 보여주지 않으면 그 0.02%가 잘한 건지 알 수 없습니다.
더 중요한 건 이겁니다. 몇만원 차이로 순위가 갈리는 게 입찰인데, 예정가격 자체가 복수예비가격 15개 중 4개를 추첨해 정해집니다. 추첨 결과는 아무도 예측할 수 없습니다. 그러니 "AI가 낙찰가를 맞혔다"는 말에는 구조적인 한계가 있습니다. 맞힐 수 있는 건 대략의 분포지, 정확한 금액이 아닙니다.
━━━━━━━━━━━━━━━━━━━━
3. 백테스트가 왜 중요하고, 왜 위험한가
백테스트는 이미 끝난 공고, 즉 낙찰 결과가 공개된 공고에 모델을 돌려보는 것입니다.
중요한 이유는 이겁니다. 사용자 예측이 쌓이길 기다리면 성능을 알기까지 몇 달이 걸립니다. 백테스트는 하루 만에 수천 건을 검증할 수 있고, 무엇보다 대상 공고를 저희가 고르지 않고 기계적으로 뽑을 수 있습니다. 예산 규모와 업종을 섞어 자동으로 선정하면, 결과가 나빠도 뺄 수 없습니다.
동시에 백테스트에는 함정이 있습니다. 전방 참조 편향(look-ahead bias)이라고 부르는 것입니다.
과거 공고 A를 지금 예측하면, 모델은 유사 사례를 찾을 때 A 이후에 마감된 공고들의 낙찰 결과까지 참고할 수 있습니다. 실전에서는 불가능한 일입니다. 미래를 본 셈이니 성적이 실제보다 후하게 나옵니다.
저희는 이걸 코드로 막았습니다. 각 공고를 검증할 때 그 공고보다 먼저 마감된 사례만 보이도록 시점을 고정했습니다. 그래도 백테스트 적중률은 사전 예측 성능이 아닙니다. 여기를 뭉개고 "우리 AI 적중률 N%"라고 쓰면, 사실상 답을 보고 푼 시험 점수를 실력이라고 내거는 것과 같습니다.
그래서 이 둘을 코드 레벨에서 분리했습니다. 예측 시각이 공고 마감 이후면 자동으로 백테스트로 분류되고, 실측 적중률 계산에서 한 건도 섞이지 않습니다.
━━━━━━━━━━━━━━━━━━━━
4. 그래서 저희 성적은
조달청 개방 데이터에서 과거 낙찰 결과를 모아 7,169건의 낙찰 이력을 확보했고, 그중 검증 조건을 만족하는 4,095건에 모델을 돌렸습니다.
· 구간 적중 2,868건 / 4,095건 = 70.0%
· 빗나감 1,227건
· 평균 절대 오차 3.33%p
· 평균 권장 구간 폭 7.22%p
· 95% 신뢰구간 68.6 ~ 71.4%
표본이 89건일 때는 신뢰구간이 47.0~67.6%로 20%p 넘게 벌어져 있었습니다. 지금은 ±1.4%p입니다. 이제야 숫자를 숫자로 말할 수 있게 됐습니다.
여기서 정정할 것이 하나 있습니다. 이전 글에서 저희는 57.3%를 적으며 "43.3%에서 올랐고 개선일 가능성이 높다"고 썼습니다. 표본을 46배로 늘려 다시 재보니 70.0%였습니다. 57.3%도, 그 전의 43.3%도 표본이 얕아 흔들린 값이었고, 저희가 그 변동을 개선의 근거로 읽었던 것은 성급했습니다. 지금 값이 이전보다 높은 것은 모델이 좋아져서라기보다 표본이 제자리를 찾았기 때문으로 봅니다.
업종별로는 편차가 매우 큽니다. 이것이 이번 검증에서 가장 중요한 발견입니다.
· 공사 2,264건 — 적중 89.1% (평균 오차 1.62%p, 구간 폭 6.79%p)
· 기술용역 106건 — 적중 51.9% (평균 오차 5.24%p, 구간 폭 7.90%p)
· 일반용역 580건 — 적중 47.4% (평균 오차 5.29%p, 구간 폭 7.92%p)
· 물품 1,145건 — 적중 45.5% (평균 오차 5.53%p, 구간 폭 7.67%p)
공사는 89.1%인데 물품은 45.5%입니다. 구간 폭은 6.8~7.9%p로 거의 같은데 적중률이 두 배 가까이 차이 납니다. 구간을 넓게 잡아 얻은 차이가 아니라는 뜻입니다.
공사는 적격심사 규칙이 정형화돼 있어 낙찰률이 96~100%의 좁은 범위에 몰립니다. 물품은 소액수의·수의계약 비중이 높고 품목 성격이 제각각이라 낙찰률이 50~100%로 흩어집니다.
그래서 "저희 적중률 70%"라는 한 줄보다, 본인 업종의 숫자를 보시는 편이 훨씬 실질적입니다. /accuracy 페이지에 업종별로 나눠 실어뒀습니다.
━━━━━━━━━━━━━━━━━━━━
5. 이전 글에서 틀렸던 것
표본이 늘면서 저희가 전에 적은 판단 두 가지가 틀린 것으로 드러났습니다. 지우지 않고 무엇이 어떻게 틀렸는지 적어둡니다.
첫째, 업종별 차이의 원인을 잘못 짚었습니다. 이전 글에서는 물품 적중률이 낮은 이유를 "물품은 원래 낙찰률이 넓게 흩어지기 때문"이라고만 썼습니다. 절반만 맞았습니다.
실제 원인의 상당 부분은 저희 쪽에 있었습니다. 비슷한 공고를 찾을 때 제목의 단어가 겹치는지를 봤는데, 물품은 이 방식이 잘 듣지 않습니다. "칠서정수장 분말활성탄 구입"의 제목 단어와 겹치는 과거 사례를 못 찾으면 곧장 "물품 전체 평균"으로 물러섰고, 그 평균에는 학교 우유급식·종량제봉투·의료장비가 한 통에 섞여 있었습니다.
고친 방법은 이렇습니다. 조달청 공고에는 세부품명번호라는 10자리 품목 코드가 붙습니다. 저희 물품 표본의 95.2%가 이 코드를 갖고 있어서, 제목 대신 이 코드로 먼저 같은 품목군을 찾도록 바꿨습니다.
몇 자리까지 볼지는 짐작하지 않고 전부 돌려서 비교했습니다.
· 업종 전체(기존): 39.5% — 오차 6.04%p, 구간 폭 7.99%p
· 앞 4자리: 45.6% — 오차 5.54%p, 구간 폭 7.67%p
· 앞 6자리: 42.9%
· 앞 8자리: 43.1%
직관과 반대로 세밀할수록 나빴습니다. 앞 8자리는 품목이 정확해지는 대신 같은 코드를 가진 과거 사례가 급감해서, 17%의 공고에만 적용되고 나머지는 결국 업종 전체 평균으로 돌아갑니다. 좁게 맞히는 소수보다 적당히 좁힌 다수가 나았습니다. 8자리에서 6자리, 4자리로 단계적으로 물러서는 방식도 시험했는데 44.5%로 오히려 낮았습니다. 표본이 적은 그룹은 분포 추정 자체가 불안정하기 때문입니다.
결과적으로 가장 단순한 안(앞 4자리)을 골랐고, 물품 적중률이 39.5%에서 45.5%로 올랐습니다. 오차는 6.04%p에서 5.53%p로 줄었고 구간 폭도 7.99%p에서 7.67%p로 좁아졌습니다. 구간을 넓혀서 맞춘 것이 아닙니다. 공사·용역은 이 코드가 없어 변화가 없습니다(±0.0%p).
둘째, 표본 수 자체를 잘못 세고 있었습니다.
낙찰 통계를 낼 때 데이터베이스에서 표본을 2,000건까지 가져오도록 해뒀는데, 사용 중인 API가 응답을 1,000건에서 조용히 자르고 있었습니다. 오류도 경고도 없이 절반이 사라진 채로 통계가 나가고 있었던 겁니다. 기술용역 표본이 20건으로 표시되던 것도 이 때문이었고, 표본이 적으니 신뢰구간이 실제보다 좁게 잡혔습니다.
같은 문제가 /accuracy 페이지에도 있었습니다. 검증 로그가 141건이던 시절에는 드러나지 않다가, 4,000건을 넘기면서 "검증 948건"이라는 실제와 다른 수가 화면에 나왔습니다. 업종별 표를 만들다가 저희 집계값과 화면 숫자가 안 맞아 발견했습니다.
이런 종류의 버그는 결과가 그럴듯해 보여서 위험합니다. 숫자가 나오긴 나오니까요. 표본 수를 화면에 계속 노출해두지 않았다면 못 찾았을 것입니다.
━━━━━━━━━━━━━━━━━━━━
6. 그럼 이 기능은 쓸모가 있나
있다고 생각하지만, 쓸모의 종류가 다릅니다.
이 기능이 못 하는 것
· 낙찰가를 정확히 맞히는 것 — 예정가격 추첨이 있는 한 불가능합니다
· 투찰가를 대신 정해주는 것 — 최종 판단은 사람이 해야 합니다
이 기능이 하는 것
· 비슷한 공고들이 과거 몇 %에 낙찰됐는지 모아서 보여주는 것
· 그 분포에서 벗어난 투찰을 하려 할 때 근거를 확인시켜 주는 것
· 발주처·업종별 낙찰률 경향을 따로 찾아보지 않아도 되게 하는 것
즉 "정답을 알려주는 도구"가 아니라 "판단 재료를 모아주는 도구"입니다. 다만 저희가 구간을 제시하는 이상 그 구간이 얼마나 맞는지는 공개해야 한다고 봤습니다.
한 가지 남은 가설은, 이 이상 올리는 데 구조적인 한계가 있을 가능성입니다. 예정가격이 복수예비가격 15개 중 4개를 추첨해 정해지는 이상, 낙찰률에는 어떤 모델도 맞힐 수 없는 무작위 성분이 섞여 있습니다. 그 폭이 얼마나 되는지는 아직 측정하지 못했습니다. 공사가 89%까지 나오는 걸 보면 업종에 따라 그 한계가 다르다는 것 정도는 말할 수 있습니다.
실측(마감 전 예측) 검증 건수는 아직 3건입니다. 백테스트를 아무리 늘려도 이 수는 늘지 않습니다. 사용자가 마감 전에 실제로 돌린 예측만 여기 들어가기 때문입니다. 통계를 낼 수 있는 수가 아니라서 공개 적중률은 50건이 모일 때까지 숫자를 내지 않습니다.
━━━━━━━━━━━━━━━━━━━━
7. 확인하실 수 있게 해뒀습니다
/accuracy 페이지에 검증 결과를 실어뒀습니다.
· 맞은 건과 빗나간 건을 같은 표에, 최근 순으로
· AI 예상금액과 실제 낙찰가를 나란히
· 권장 구간과 실제 낙찰률, 그리고 오차
· 백테스트인지 사전 예측인지 표시
· 업종별 적중률과 평균 구간 폭
· 비교 표본이 부족해 집계에서 뺀 건은 그 사실까지 표시
빗나간 1,227건도 그대로 있습니다. 지우지 않았습니다.
━━━━━━━━━━━━━━━━━━━━
마지막으로
70.0%도 90%라고 쓰는 곳 옆에서는 낮아 보입니다. 그런데 90%라고 쓰는 곳들은 대부분 분모를 밝히지 않습니다. 같은 방식으로 계산한 숫자가 아니니 애초에 비교가 안 됩니다. 저희 숫자는 검증 가능하고, 저 숫자들은 검증할 수 없습니다.
이번에 숫자가 올랐지만 그게 저희가 잘해서라고는 쓰지 않겠습니다. 표본이 얕을 때 나온 57.3%를 근거로 "개선됐다"고 썼던 것이 성급했던 것처럼, 지금 값도 나중에 다시 흔들릴 수 있습니다. 다만 이제는 4,095건이라 흔들릴 폭이 훨씬 작습니다.
앞으로 개선되면 개선된 대로, 안 되면 안 되는 대로 같은 자리에 올리겠습니다. 숫자가 나빠졌다고 페이지를 내리지 않겠습니다.
이 기능을 써보시고 실제와 얼마나 달랐는지 알려주시면 큰 도움이 됩니다. 빗나간 사례일수록 더 그렇습니다.
━━━━━━━━━━━━━━━━━━━━
8. [2026년 8월 8일 추가] 데이터를 늘렸더니 서비스가 멈췄습니다
앞서 4번에 적은 과거 낙찰 데이터 수집 이야기의 뒷이야기입니다. 표본을 늘린 것 자체는 잘한 일이었지만, 그 뒤에 저희가 예상하지 못한 문제가 줄줄이 나왔습니다. 같은 실수를 하실 분이 있을 것 같아 적어둡니다.
첫째, 조회가 통째로 멈췄습니다.
아카이브 테이블이 24,000행에서 122,796행이 됐습니다. 그랬더니 발주기관으로 낙찰 이력을 찾는 조회가 전부 타임아웃으로 실패하기 시작했습니다. 서울특별시로 조회하면 18초를 기다린 끝에 오류, 심지어 전체 건수를 세는 것조차 응답하지 않았습니다.
원인은 단순했습니다. 인덱스가 없었습니다. 정확히는 유사 사례 검색용 벡터 인덱스 하나만 있었고, 발주기관 컬럼에는 아무것도 없었습니다. 24,000행일 때는 매번 전체를 훑어도 견뎠는데, 다섯 배가 되자 무너진 것입니다.
인덱스를 세 개 추가했더니 같은 조회가 18초 타임아웃에서 196밀리초로 돌아왔습니다. 실행 계획을 떠보니 전체 훑기가 인덱스 조회로 바뀌어 있었습니다.
여기서 배운 것은 "데이터를 늘릴 때는 조회 계획도 같이 늘려야 한다"는 것입니다. 저희는 수집에만 집중했고, 그 데이터를 어떻게 꺼낼지는 기존 코드가 알아서 하리라 여겼습니다.
둘째, 인덱스를 만드는 것부터 막혔습니다.
관리 콘솔의 SQL 편집기에서 인덱스 생성을 실행하면 "Failed to fetch"만 반복해서 떴습니다. 문장을 하나씩 쪼개도 마찬가지였습니다. 알고 보니 데이터베이스가 거부한 게 아니라, 편집기의 요청 대기 시간이 인덱스 생성 시간보다 짧아 매번 취소되고 되돌려지고 있었습니다.
정작 가장 중요한 인덱스는 만드는 데 7.9초밖에 걸리지 않았습니다. 편집기가 그보다 짧은 시점에 끊고 있었던 것입니다. 오류 메시지가 원인을 가리키지 않을 때는 그 메시지를 누가 뱉었는지부터 확인해야 한다는 걸 다시 배웠습니다.
셋째, 인덱스만으로는 부족했습니다.
인덱스를 걸어 조회가 되살아난 뒤에도 화면은 3~10초씩 걸렸습니다. 이번 원인은 전송량이었습니다. 각 공고에는 원문 응답 전체가 통째로 저장돼 있는데, 이게 행당 수 킬로바이트입니다. 1,000행을 가져오면 그 자체로 2초가 넘게 듭니다.
같은 조회에서 이 원문 컬럼만 빼봤더니 2,265밀리초가 357밀리초로 줄었습니다. 통계를 내는 데 필요한 값은 몇 개뿐인데 전부를 실어 나르고 있었던 셈입니다.
━━━━━━━━━━━━━━━━━━━━
9. 발주처마다 낙찰가가 몰리는 폭이 다릅니다
이번에 인덱스를 고치면서 발주기관별 낙찰 이력을 제대로 집계할 수 있게 됐는데, 그 과정에서 저희가 지금까지 잘못 보고 있던 것을 하나 발견했습니다.
처음에는 발주처를 평균 낙찰률 순으로 세워봤습니다. 그런데 상위권이 이상했습니다. 열 곳 중 일곱 곳이 평균 경쟁 업체 수가 한 곳이었습니다. 수의계약이나 단독입찰이라 낙찰률이 늘 상한에 붙어 있었던 것입니다. 게다가 발주기관 이름 자리에 업체명이 들어가 있는 데이터도 섞여 있었습니다.
이런 목록은 투찰 판단에 아무 쓸모가 없습니다. 그래서 경쟁이 두 곳 미만인 건은 통계에서 뺐습니다.
더 중요한 발견은 그다음입니다. 평균 낙찰률로는 발주처를 구분할 수 없었습니다. 상위 열 곳이 전부 97~101% 사이에 몰려 있어서, 어느 기관이 어떻다고 말할 만한 차이가 없었습니다.
대신 낙찰가가 몰리는 폭을 재봤더니 완전히 달랐습니다.
· 한국철도공사 회계통합센터 — 폭 2.2%p (표본 428건)
· 경상남도 산청군 — 폭 3.6%p
· 경상북도 안동시 — 폭 4.0%p
· 서울특별시 — 폭 8.1%p
· 서울교통공사 — 폭 17.3%p
· 조달청 — 폭 25.3%p (표본 1,428건)
여기서 폭은 낙찰률 하위 10%와 상위 10% 사이의 거리입니다. 가장 좁은 곳과 가장 넓은 곳이 열 배 넘게 차이 납니다.
평균으로는 3.6%p 안에서 다 비슷해 보이던 기관들이, 폭으로 보면 2.2%p부터 25.3%p까지 벌어집니다. 어느 발주처의 낙찰가가 예측 가능하고 어느 쪽이 그렇지 않은지는 평균이 아니라 이 폭이 말해줍니다.
이건 4번에서 말씀드린 업종별 편차와 같은 이야기이기도 합니다. 공사 적중률이 89.1%이고 물품이 45.5%인 것도, 결국 낙찰률이 좁게 몰리느냐 넓게 흩어지느냐의 문제였습니다. 발주처 단위에서도 같은 구조가 보입니다.
그래서 저희 서비스 첫 화면에도 평균 낙찰률 대신 이 폭을 앞세워 보여주기로 했습니다.
━━━━━━━━━━━━━━━━━━━━
10. 낙찰률이 100%를 넘는 이유
위 목록을 보시면 조달청 낙찰률 구간이 110%까지 올라가 있습니다. 저희 화면에서도 개별 낙찰 결과에 108%, 104% 같은 값이 그대로 나옵니다.
추정가격보다 비싸게 낙찰됐다는 뜻이라 데이터가 잘못된 것처럼 보이지만, 정상입니다. 저희가 쓰는 낙찰률의 분모는 추정가격인데, 실제 입찰의 기준이 되는 예정가격은 통상 그보다 높게 잡힙니다. 부가세가 붙는 경우도 있고요. 그래서 낙찰가가 추정가격을 넘는 일이 드물지 않습니다.
이 설명 없이 숫자만 보여드리면 데이터를 의심하시는 게 당연합니다. 화면에 "낙찰률 = 낙찰가 ÷ 추정가격"이라고 계산식을 같이 적어두기로 했습니다.
━━━━━━━━━━━━━━━━━━━━
11. 기업정보 조회는 왜 절반만 나오나
낙찰업체를 누르면 대표자·설립일·매출액 같은 기업 정보를 보여드리는데, 절반 정도는 아무것도 뜨지 않습니다. 이유를 밝혀둡니다.
낙찰 결과에서 사업자등록번호를 뽑는 것 자체는 문제가 없습니다. 표본 300건을 확인해보니 100% 다 있었습니다.
문제는 그다음입니다. 저희가 쓰는 기업정보 API는 원천이 공시 자료라, 공시 의무가 없는 중소기업은 아예 등재돼 있지 않습니다. 표본 20곳을 조회해보니 11곳만 나왔습니다. 나머지 아홉 곳은 데이터가 틀린 게 아니라 애초에 없는 것입니다.
직원 수도 마찬가지입니다. 조회에 성공한 기업조차 대부분 0으로 내려오는데, 이건 직원이 없다는 뜻이 아니라 공시하지 않았다는 뜻입니다.
지금까지는 이런 경우 화면이 그냥 비어 있었습니다. 사용자 입장에서는 기능이 고장 난 것처럼 보였을 겁니다. 이제는 "사업자등록번호는 확인됐지만 공시 대상이 아니어서 재무 정보는 조회되지 않습니다"라고 이유를 밝힙니다.
국세청 사업자등록 상태조회도 붙였습니다. 이쪽은 등록번호만 있으면 공시 여부와 무관하게 응답해서, 실제 낙찰업체 49곳을 조회했더니 전부 나왔습니다. 다만 이 API가 주는 것은 사업자상태와 과세유형, 폐업일자까지입니다. 직원 수나 매출액은 여기에 없습니다. 폐업한 업체를 가려내는 데는 확실히 쓸모가 있습니다.
━━━━━━━━━━━━━━━━━━━━
12. 느린 것을 고치는 순서
기업정보 모달이 느리다는 이야기를 듣고 시간을 재봤습니다. 처음 열 때 평균 2.9초, 최대 3.7초였습니다.
내역을 쪼개보니 데이터베이스 조회는 277밀리초로 문제가 아니었고, 나머지가 전부 공공데이터포털 호출이었습니다. 그런데 그 호출들이 서로를 기다리며 줄을 서 있었습니다.
세 가지를 고쳤습니다. 서로 의존하지 않는 호출을 동시에 던지도록 바꿨고, 재무 정보를 연도별로 하나씩 물어보던 것을 한꺼번에 조회하도록 했습니다. 특히 기업정보 조회는 사업자번호로 먼저 찾고 실패하면 상호명으로 다시 찾는 구조였는데, 공시되지 않은 기업은 첫 번째가 항상 실패하기 때문에 두 번의 왕복이 그대로 쌓이고 있었습니다. 이것도 동시에 던지도록 바꿨습니다.
결과는 평균 2,875밀리초에서 1,424밀리초입니다.
남은 1.4초는 대부분 포털 자체의 응답 시간이라 저희 코드로는 더 줄일 수 없었습니다. 그래서 조회 결과를 저장해두고 재사용하기로 했습니다. 기업 재무는 1년에 한 번 공시되고 사업자 상태는 폐업할 때만 바뀌니, 하루 정도 묵혀도 값이 틀어질 일이 없습니다.
같은 업체를 다시 열면 1,555밀리초가 99밀리초가 됩니다.
여기에 부수 효과가 하나 더 있었습니다. 측정 중에 포털이 응답을 붙잡아 9.4초가 걸린 경우가 있었는데, 이런 건도 한 번만 겪으면 다음부터는 저장된 값으로 즉시 응답합니다. 외부 서비스가 불안정할 때 사용자가 그걸 그대로 맞지 않게 되는 셈입니다.
━━━━━━━━━━━━━━━━━━━━
13. 저장에 성공했다고 나오는데 저장이 안 되던 문제
마지막은 이 게시판 자체의 버그입니다.
글을 수정하고 저장하면 화면은 정상적으로 넘어가는데 내용이 그대로였습니다. 오류 메시지도 없었습니다.
원인이 두 겹이었습니다.
먼저 이 게시판의 초기 운영 글들은 작성자 정보 없이 등록돼 있었습니다. 그런데 수정 권한 규칙이 "로그인한 사람과 글쓴이가 같을 때만 허용"이라, 글쓴이가 비어 있으면 아무도 조건을 만족할 수 없습니다. 관리자도 마찬가지입니다.
여기에 더해 권한 규칙 자체에도 오류가 있었습니다. 기존 공지 여부를 확인하려고 넣어둔 조건문에서 비교 대상을 잘못 지정해, 조건이 항상 참이 되면서 데이터베이스가 오류를 냈습니다.
문제는 이 두 가지가 화면에 드러나지 않았다는 점입니다. 권한에 막혀 아무 행도 바뀌지 않아도, 저희가 쓰는 방식에서는 이것이 오류가 아니라 "0건 수정 완료"로 돌아옵니다. 코드는 오류가 없으니 성공으로 판단하고 글 화면으로 넘어갔습니다. 사용자에게는 저장된 것처럼 보였습니다.
세 가지를 고쳤습니다. 빠져 있던 작성자 정보를 채웠고, 권한 규칙의 조건문을 바로잡았고, 저장 후 실제로 몇 건이 바뀌었는지 확인해서 0건이면 사용자에게 알리도록 했습니다.
여기에 하나가 더 있었습니다. 글 상세 페이지가 내용을 저장해두고 재사용하도록 돼 있어서, 수정이 성공해도 예전 내용이 보일 수 있는 상태였습니다. 이 페이지는 매번 새로 읽도록 바꿨습니다.
앞의 5번에서 적었던 "결과가 그럴듯해 보이는 버그가 위험하다"는 이야기와 같은 종류입니다. 실패했는데 성공처럼 보이는 응답을 그대로 믿으면, 무엇이 잘못됐는지 알 방법이 없습니다.