목록전체 글 (203)
코딩관계론
홈에 소개글을 더했는데, 읽어보니 위에서 했던 말을 아래에서 또 하고 있었다. 나는 그 블록을 지웠다. 추가한 커밋과 지운 커밋 사이는 약 8분이었다. 시작은 SEO 감사였다. 구글이 받는 서버 HTML에서 홈 본문이 488자로 측정됐다. 나는 홈 피드 아래에 소개 블록을 더했다. 서비스 진행 방식과 업종별 설명, 기존 집계값을 담았다. 추가 뒤 본문은 866자가 됐다. 소개글의 재료는 실제 정보였다. 하지만 피드 위에서 보여준 숫자를 다시 말하고, 서비스 목록의 설명을 문장으로 풀고, 첫 화면에 있는 안내를 반복했다. 새 블록을 읽어도 그 페이지에서 처음 알게 되는 내용이 거의 없었다. 색인 상태와 URL별 점검은 구글 검색 클릭이 없어서 확인한 것들에 적어뒀다. 소개글을 내린 뒤에는 이미 공개한 작업..
특수청소 신청에서 주소 검색창이 첫 질문이었다. 세션 리플레이를 보니 이탈한 11명 중 10명이 주소 검색창을 열지도 않았다. 지역만 받기로 했다가 약 6시간 뒤 되돌렸다 나는 처음에 주소를 받는 양을 줄였다. 신청할 때는 시·도와 시·군·구만 고르고, 전체 주소는 업체를 확정할 때 받도록 바꿨다. 업체도 원래 확정 전에는 시·군·구까지만 볼 수 있었다. 그 결정을 오래 유지하지 못했다. 현장 위치는 전체 주소로 받아야 한다고 판단해 주소 검색과 상세주소를 받는 방식으로 돌아갔다. 두 커밋의 시각 차이는 약 6시간이다. 업체를 고른 뒤 주소를 받는다는 랜딩 안내도 함께 고쳤다. 며칠 뒤에는 주소를 골랐는데 다음 버튼이 잠기는 문제가 나왔다. 상세주소를 2자 이상 적어야 한다는 조건 때문이었다. 나는 상세..
‘1평 × 평당 1원’. 견적을 설명하라고 만든 입력칸에 이런 값이 들어왔다. 필수 조건은 통과했고 고객 화면에는 산출 근거로 표시됐다. 내가 처음 확인한 문제는 빈칸이었는데, 입력을 강제하고 나니 채워진 칸을 믿을 수 있는지가 다음 문제가 됐다. 입력칸을 필수로 만들 때는 이유가 있었다 나는 견적 금액 옆에 왜 이 금액인지 보여주고 싶었다. 업체가 적은 포함 항목은 비교 카드에, 산출 내역은 펼쳐보는 영수증에 담았다. 쓰지 않은 항목은 줄 자체를 그리지 않았고, 내역이 없으면 펼치는 버튼도 두지 않았다. 그런데 근거가 비어 있는 견적에는 내역을 볼 방법이 없었다. 같은 날 면적과 평당 단가를 입찰 전송 조건에 넣었다. 화면에서만 막으면 다른 경로로 비어 있는 값이 들어오므로 서버 검증에도 반영했다. 전..
Supabase에서 Kysely로 옮긴 다음 날, 고객이 지도에 그린 경로가 사라졌다. DB에 남은 route_points는 배열 대신 {}였다. 고친 코드는 이것이다. src/server/repositories/ansim-request-repository.ts const CHECKLIST_JSONB_COLS = ["answers", "route_points", "area_center", "extra"] as const;/* * · 객체 `{a:1}` → JSON.stringify 됨 → `answers`·`extra`·`area_center`는 우연히 동작 * · 배열 `[{…}]` → **Postgres 배열 리터럴**로 변환 → jsonb 파싱 실패 (`in..
봉분 두 기 의뢰에 들어온 입찰가 11건의 중앙값은 33만원이었다. 내 화면은 그 가격에도 ‘높음’을 붙였다. 배지를 지울 무렵 실제로 선택된 두 기 견적은 1건뿐이었다. 나는 입찰 중앙값을 낙찰 중앙값으로 잘못 읽었다. 클린브릿지의 견적 비교 화면에는 업체가 적은 금액을 내부 기준과 비교하는 배지가 있었다. 낮음, 적정, 높음으로 나눠 보여줬다. 기준은 봉분 하나에 20만원, 추가 봉분마다 8만원을 더하는 계산이었다. 봉분이 여러 개인 의뢰에 들어온 입찰 대부분을 내 화면이 높은 가격으로 분류하고 있었다. 고객에게 보이지 않는 계산식이 고객 대신 가격을 평가하고 있었다. 업체 쪽에서는 이미 가격을 고르는 방식을 바꾸고 있었다. 기존 슬라이더는 5~200만원 사이에서 손가락으로 값을 맞춰야 했다. 모바일..
견적 신청을 시작한 사람이 등록까지 오지 않았다. 어디에서 멈췄는지 보려고 청소 종류를 고른 뒤 질문을 열었는지, 답을 적고 다음으로 갔는지 세기 시작했다. PostHog에 질문 화면을 볼 때와 다음 단계로 넘어갈 때의 기록을 붙였다. 같은 기준으로 조회하려고 스크립트를 만들고, 내 테스트는 고객 행동에서 제외했다. 숫자만으로 설명되지 않는 구간은 세션 리플레이를 봤다. 화면과 버튼을 거친 순서를 텍스트로 풀어보는 도구도 썼다. 질문에 답하기도 전에 회원가입이 나왔다 여름의 신청 화면은 청소 종류를 고르면 로그인을 요구했다. 현장에 관한 답을 적기도 전에 계정을 만들어야 했고, 입력을 시작하기 전에 빠지는 사람이 있었다. 8월 16일, 나는 회원가입을 등록 직전으로 옮겼다. 질문에 답하고 사진까지 고른 ..
잠을 자는 동안 돈이 벌리지 않으면 부자가 될 수 없다. 벌초로 부업을 시작한 이야기도 이 생각에서 출발했다. 내가 만든 서비스에서 고객과 업체를 연결하고, 거래가 이루어지면 중개 수수료를 받는 일을 해보려 했다. 때마침 추석 시즌에 할 수 있을 것 같다는 생각이 들었다. 산소가 멀면 왕복만으로도 하루가 꼬박 “산소가 멀면 왕복만으로도 하루가 꼬박.” 당시 벌초 페이지에 적었던 문구다. 멀리 사는 사람이 현장까지 다녀오는 수고, 지역 업체를 알아보고 가격을 비교하는 어려움을 페이지에서 다뤘다. 작업 전후 사진을 받아 가족 단톡방에 공유하라는 문구도 넣었다. 벌초를 맡기고도 직접 가서 확인해야 한다면 원거리에서 신청하는 사람에게는 일이 남았다. 7월 18일, 처음 만든 벌초 페이지의 이름은 ‘효도특공대’였..
key=lambda의 의미를 적던 때2023년 3월, 이 블로그에 key=lambda는 무슨 의미일까?라는 글을 썼다. 람다 함수가 무엇인지, 정렬할 때 key에 왜 함수를 넘기는지 설명한 글이다. 그때 적어둔 예시 중 하나가 이 코드다. words = ['Some', 'words', 'sort', 'differently']words.sort(key=lambda word: word.lower())그 뒤에도 개발하면서 정리한 내용을 블로그에 남겼다. 오류를 어떻게 다룰지 적었고, 알고리즘 문제를 이분탐색으로 풀어보기도 했다. 만들던 서비스에서 API Gateway를 직접 구현하기로 한 이유도 썼다. 블로그에는 문법을 설명하는 글과 서비스를 만들며 선택한 것을 설명하는 글이 함께 쌓였다. 내가 만든 화면에 ..
혼자 만든 클린브릿지에 구글 검색으로도 사람이 들어오게 하고 싶었다. 서비스는 열리고 있었고 네이버에서는 유입이 있었다. 전체 유입의 94%가 네이버였다. 그런데 구글 서치콘솔에서 최근 28일 웹 검색 클릭은 0이었다. 2026년 10월 9일 확인한 색인 현황은 색인 82개, 미색인 832개였다. 특히 홈이 걸렸다. 구글이 10월 1일에 크롤링했는데도 색인되지 않은 상태였다. 찾아오기는 했는데 검색에는 넣어주지 않은 것이다. 내가 만든 쪽에서 막아놓은 건 없는지부터 점검했다. 10월 10일에는 구글에 제출하는 sitemap의 URL 107개를 전부 검사했다. 모두 HTTP 200으로 응답했고, 구글 색인을 막는 noindex도, 대표 주소를 지정하는 canonical의 불일치도 없었다. 이 세 항목에서 ..
나는 혼자 클린브릿지를 만들고 운영한다. 고객과 업체를 연결하고 중개 수수료를 받는 서비스를 만들어 보려 했다. 만드는 시간을 줄이는 데는 바이브 코딩이 도움이 됐다. 하지만 서비스를 만든 뒤 고객과 업체를 모으는 일은 내 몫이었다.지금 클린브릿지 첫 화면 (2026년 10월 10일 캡처) 7월 3일에 첫 커밋을 했고, 다음 날에는 의뢰를 올리고 입찰하는 흐름이 실제 DB에 붙어 돌아갔다. Claude Code가 코드를 짜고, 큰 변경은 Codex가 교차 리뷰했다. 나는 만들 것을 정하고, 리뷰에서 지적이 나오면 다시 고치게 했다. 코드를 직접 치는 시간이 거의 없어지니 혼자서도 빠르게 만들 수 있었다. 그렇다고 돌아가는 화면을 그대로 믿을 수는 없었다. 업체 모집 화면에는 AI가 지어낸 실적 숫자가 들..
종소세 스크래핑이 어느 순간부터 느려졌다. 그런데 어떤 순서로, 어느 정도 동시성으로 요청을 쏘아야 가장 빠른지는 직관으로 답이 나오지 않았다. 실험 한 번마다 라이브 스크랩과 정합성 검증이 필요해 비쌌고, 사람이 손으로 조합을 바꿔가며 찾기엔 경우의 수가 너무 많았다. 그러던 중 메타하네스(meta-harness)라는 개념을 접했다. 사람이 코드를 고치는 대신 LLM이 코드를 고쳐 쓰게 하고, 그 결과를 점수로 채점해 반복시키는 방법이다. 이 글은 그 아이디어를 실제 업무 스크래퍼에 적용한 기록이다. 결과부터 말하면 속도 80% 이상, 정확성 3%의 개선을 얻었고, 무엇보다 흥미로웠던 건 LLM이 아무도 알려주지 않은 "일단 완주시키고 → 성공하는 즉시 군더더기를 쳐낸다"는 전략을 스스로 찾아냈다는 점..
최근 프로젝트에서 Kubernetes 환경에서 동시성을 극대화하기 위해 AWS Lambda를 도입했습니다. 하지만 Lambda로 분리된 워크로드 때문에 로그가 누락되면서 서버 전 구간의 추적성이 떨어지는 문제가 발생했습니다. 처음에는 오픈텔레메트리 프로토콜을 사용해서 간단히 traceparent 헤더만 전달하면 될 것으로 생각했지만, 실제로는 Lambda의 serverless-express에서는 AsyncLocalStorage 전파가 끊기게 되면서 전구간으로 이어지지 않는 문제가 발생했습니다.이 글에서는 이러한 문제를 해결하기 위해 제가 시도했던 방법과 그 과정에서 얻은 인사이트를 공유합니다. OpenTelemetry instrumentation을 활용해 trace ID를 로그에 삽입하는 방법과, Lam..