전체 90편
-
이 블로그는 2024년 2월부터 Jekyll(jekyll-theme-yat)로 굴러왔다. 오늘 Astro 5로 옮겼다. 시작은 거창한 기술 부채 청산이 아니라 이거였다 — "Jekyll 이름을 떼고 싶다." 푸터에 박힌 "Powered by Jekyll"과, 테마가 정해준 모양대로만 살아야 하는 상태가 슬슬 답답했다. 옮기면서 가장 신경 쓴 건 하나였다. 글 89편의 URL을 단 하나도 깨뜨리지 않는 것. 2년 반 동안 공유된 링크들이 있고, 검색엔진에 색인된 주소가 있다. 디자인이야 마음에 안 들면 다시 고치면 되지만, 깨진 링크는 조용히 죽는다. 결과부터 쓰면 성…
-
이 서비스의 커밋 855개 중 806개(94%)에는 Co-Authored-By: Claude 가 붙어 있다. 석 달 반 동안 사실상 모든 코드를 AI와 같이 썼다는 뜻이다. 이렇게 말하면 보통 "프롬프트를 어떻게 쓰세요?"라는 질문이 돌아온다. 그런데 실제로 해보니, AI와 함께 개발하는 일의 본체는 프롬프트가 아니었다. 이번 편은 그 얘기다 — 이 시리즈에 기록해온 사고들을 "AI와의 협업"이라는 축으로 다시 꿰어보면 뭐가 보이는지. 1. 동료는 세션마다 기억을 잃는다 AI가 낸 사고들을 돌아보면 패턴이 하나 있다. 몰라서 낸 사고가 아니라, 이 코드베이스의 암묵지…
-
5월 중순, 배팅 포인트 상점에 스크래치 복권을 넣었다. 50P를 내면 황금 카드가 나오고, 손가락으로 긁으면 당첨금이 드러나는 단순한 장치다. 그런데 커밋 로그를 다시 보니 재미있는 기록이 남아 있었다 — 출시하고 40여 분 동안 확률표를 네 번 고쳤다. 처음엔 당첨 확률을 두 번이나 올렸고, 30분 뒤엔 정반대로 꽝을 82%까지 끌어올렸다. 이번 편은 그 짧고 굵었던 밸런싱 시행착오의 기록이다. 시작 — 50P짜리 스크래치 카드 구현 자체는 소박하다. 서버는 누적 확률 테이블에서 가중 랜덤으로 당첨금을 뽑고, 프론트는 canvas의 destination-out 합…
-
오늘 "나만 하는 게 없는 것 같아서, 깃을 뒤져봤다"는 글을 쓰고 났는데, 문득 깃 로그보다 더 오래된 로그가 하나 더 있다는 생각이 들었다. 이 블로그다. 2024년 2월부터 2년 반, 80편 남짓. 이 블로그는 성장 곡선이 통째로 남은 기록이었다 시기별로 펼치면 이렇다. - 2024-02~03 — 첫 직장 입사 직후. 기술보다 "어떻게 배워야 하는가"가 주제였다. 남의 코드 읽는 법, 커밋 컨벤션 제안. 스스로를 "말하는 감자"라고 불렀다 - 2024-04~08 — 5개월 공백 - 2024-09~2025-03 — 복구. 북스터디 4편, 25일간 16편짜리 데일리…
-
실시간 경매 채팅의 말풍선에 스킨을 입히는 기능을 만들었다. 디자이너(라기보단 그림 그려주는 모임원)가 만든 말풍선 SVG를 등록하면, 그 유저의 채팅 말풍선이 기본 색상 박스 대신 그 그림으로 렌더되는 기능이다. 요구사항을 말로 하면 한 줄이다 — "이미지를 텍스트 길이에 맞춰 늘려주세요." 그 한 줄이 커밋 열네 개가 됐다. 이번 편은 시리즈에서 처음 다루는 순수 프론트엔드/CSS 이야기이고, 하이라이트는 캡(말풍선 양끝 조각)이 화면을 뒤덮을 때까지 스스로 커지던 순환 폭주 버그다. 시작 — 통짜 이미지를 늘리면 되겠지 첫 커밋은 플랫폼 관리자 전용 테스트 페이…
-
7월에 시즌패스 "함께하는 여름방학"을 만들어 배포했다. 30레벨 단일 트랙(무료 5개 / 유료 25개 보상), 6주 진행, 일일/주간 미션으로 XP를 모아 레벨을 올리는 전형적인 배틀패스 구조다. 출발점은 거창한 게 아니라 "배팅 포인트가 쌓이기만 하고 쓸 데가 없다"는 경제 문제였다 — 포인트 소진처를 만들면서 리텐션도 노려보자는 것. 14편에서 "이게 리텐션에 실제로 도움이 됐는지 아직 확정하지 않기로 했다"는 지표 이야기는 이미 했으니, 이번 편은 반대쪽 — 만드는 과정에서 했던 기획 판단들을 기록한다. 돌아보니 코드보다 "무엇을 보여주고 무엇을 가릴까", "…
-
설계 기록. 아직 실행하지 않았다. 코드도 인프라도 건드리지 않은 상태이고, 이 글은 13편에서 "별도 글감"이라고 미뤄뒀던 그 설계를 실측과 함께 정리해두는 것이다. 실행하고 나면 계획과 실제가 어디서 달랐는지 이어서 기록할 예정이다. Supabase 요금 조사에서 나온 두 가지 원인 중 Realtime은 13편에서 정리했다. 남은 하나가 이번 주제다 — RiotMatch.rawInfo , Riot API에서 받아온 전적 원본 JSON을 통째로 담고 있는 컬럼. 이게 DB 용량의 90% 이상을 혼자 먹고 있다. 실측부터 — 한 달 사이 얼마나 자랐나 6월 25일에…
-
02편에서 멀티테넌시를 경로 방식( X-Group-Slug 헤더)으로 설계했고, 10편에서 백엔드의 격리 누락을 한바탕 잡았다. 그런데 그 뒤로도 "다른 모임 화면에 원조 모임(이세계) 멤버가 보인다"는 증상이 또 나왔다. 이번엔 백엔드가 아니었다. 백엔드는 받은 헤더대로 정확히 스코프하고 있었고, 프론트가 틀린 slug를 보내고 있었다. 이번 편은 그 마지막 조각 — 프론트 라우팅을 /g/:slug/ 로 옮긴 마이그레이션 기록이다. 증상 — 백엔드를 다 고쳤는데 왜 또 섞이나 플랫폼 운영진 계정으로 다른 모임에 들어가서 홈을 본 다음, 멤버 목록( /users )…
-
실시간 경매에는 "0점 입찰"이라는 규칙이 있다. 최하위 티어(8~9티어) 매물은 시작가가 0점이라, 첫 입찰에 한해 0점으로 데려갈 수 있다. 문제는 이 0이라는 값이 지난 두 달 동안 서로 다른 자리에서 네 번이나 사라졌다는 것이다 — UI에서 한 번, DB 동기화 타이밍에서 한 번, Redis Lua 스크립트에서 한 번, 그리고 전혀 무관한 기능의 기본값 처리에서 한 번. 03편에서 소개한 그 Lua 스크립트의 뒷이야기이기도 하다. 모아놓고 보면 전부 같은 뿌리다: 0은 유효한 값인데, 코드 어딘가가 0을 "없음"으로 취급했다. 배경 — 같은 규칙이 세 언어로…
-
04편에서 "요청 1개가 쿼리 130개로 터지는" N+1 이야기를 했는데, 이번 편은 그보다 한 단계 더 나쁜 케이스다. 요청 1개가 쿼리 N개가 되는 게 아니라, 요청 자체가 무한 이 되는 버그. 그것도 일주일 사이에 두 번, 서로 다른 훅에서 같은 원리로 터졌다. 서버 입장에서 보면 이건 사실상 DDoS다. 다만 공격자가 외부가 아니라 우리가 배포한 프론트엔드 코드라는 점이 다를 뿐이다. TL;DR - 테넌시 마이그레이션 다음날 아침, Cloud Run 로그에서 동일 클라이언트가 같은 API를 초당 10회 이상 무한 호출 하는 걸 발견했다. 원인은 새로 만든 us…
마지막 글까지 다 봤어요.