LDBD
/
전체 글

LDBD 개발 일지 #6 — 만드는 동안엔 아무 일도 안 생겼다

런칭은 만드는 동안엔 없던 사고들을 데려왔다. 방문자 600명 뒤에서 서버는 하루 7만 번 일했고, 두 달을 AI로 만든 서비스를 알리려던 글은 "AI가 쓴 텍스트"로 분류돼 몇 분 만에 죽었다. 흔들린 서버를 고친 이야기와, 손으로 서툴게 다시 써야만 살아남은 글 이야기.

지난 런칭 회고의 끝에, 런칭 당일 저녁 호스팅 서비스에서 무료 사용량을 다 썼다는 경고 메일이 왔고 그날 밤 원인을 찾아 고쳤다는 이야기를 한 문단으로 지나갔다. 그때는 “방문자가 있다는 건 이런 문제가 생긴다는 뜻”이라고만 적고 넘어갔는데, 지나고 보니 그 한 문단이 이번 글의 한 축이다. 다른 축은 그로부터 열흘 뒤, 전혀 다른 종류의 사고였다.

두 달 동안 이 서비스를 만드는 내내 아무 일도 안 생겼다. 아무것도 터지지 않았고, 그럴 수밖에 없었다. 방문자가 0명이었기 때문이다. 그러다 런칭을 하자, 서로 아무 관계도 없어 보이는 사고 두 개가 2주 안에 연달아 찾아왔다. 하나는 서버가 트래픽에 흔들린 일이다. 방문자 600명 뒤에서 서버가 하루에 7만 번 넘게 일하다 한 달치 무료 한도를 반나절 만에 태워버렸다. 다른 하나는 그 서비스를 사람들에게 알리려다 막힌 일이다. 두 달을 준비한 해외 커뮤니티 소개 글이 하필 “AI가 쓴 글”로 분류돼 올리자마자 죽었다. 둘 다 만드는 동안엔 한 번도 겪을 수 없는 종류였다. 이건 만드는 일과 알리는 일이 완전히 다른 일이라는 걸, 두 가지 방식으로 배운 기록이다.

1부 — 서버가 먼저 흔들리다: 방문자 600명, 함수 호출 7만 번

경고 메일이 온 저녁

7월 13일 아침에 GeekNews(한국의 개발자·IT 뉴스 커뮤니티)에 LDBD를 올렸고, 방문자가 처음으로 생겼다. 방문자가 한창 밀려들던 그날 저녁, Vercel에서 메일이 왔다. LDBD는 Vercel이라는 호스팅 서비스, 그러니까 코드를 올리면 사이트로 만들어 인터넷에 내보내 주는 서비스 위에서 돌아가는데, 당시 무료 요금제 기준으로 서버가 실제 계산에 쓴 시간을 재는 Active CPU라는 한도가 한 달에 4시간 주어졌다. 여기서 4시간은 사이트가 켜져 있는 시간이 아니라, 서버가 실제로 계산한 시간의 합이다. 메일은 그 4시간을 100% 다 썼고, 더 초과하면 프로젝트가 자동으로 정지된다는 내용이었다.

솔직히 첫 반응은 억울함이었다. 하루 600명은 웹 기준으로 큰 트래픽이 아니다. 정적인 블로그라면 무료 요금제로 수만 명도 받아낸다. 그런데 한 달치 한도를 런칭 반나절 만에 다 썼다니, 뭔가 비정상적으로 돌고 있다는 뜻이었다. 일단 사이트가 멈추는 것부터 막아야 해서 그날로 유료 플랜(Pro, 월 $20)으로 올렸고, 원인 분석은 그 밤부터 시작했다.

범인은 방문자가 아니었다

Vercel의 Observability, 그러니까 어떤 주소가 몇 번 호출됐고 CPU를 얼마나 썼는지 보여주는 관제 화면을 열었다. Vercel에서는 페이지 요청이 올 때마다 서버리스 함수라고 부르는 작은 서버 조각이 깨어나 페이지를 만들어 돌려주는데, 이 함수 호출이 하루에 7만 2천 4백 건이었다. 그날 방문자 600명의 120배다. 한 사람이 백 페이지씩 클릭해도 나올 수 없는 숫자였다.

나머지는 사람이 아니었다. 검색엔진과 AI 회사들이 웹페이지를 자동으로 읽어 가는 프로그램, 크롤러였다. 생각해 보면 당연한 일이었다. LDBD에는 자산 페이지만 한·영 언어판을 합쳐 1,200개가 넘고 참가자 프로필 페이지도 따로 있는데, 검색에 잘 걸리라고 사이트맵 — “우리 사이트에는 이런 페이지들이 있습니다”라고 검색엔진에 알려주는 주소 목록 — 까지 제출해 뒀다. 크롤러들은 그 목록을 받아 들고 방문자가 있든 없든 1,200개의 페이지를 상시 순회하고 있었다. 내가 초대장을 돌려놓고는 많이들 온다고 놀란 셈이다.

CPU 내역은 70%가 함수, 30%가 미들웨어였다. 미들웨어는 모든 요청이 페이지 코드에 닿기 전에 거치는 공통 관문으로, 언어 처리나 로그인 확인 같은 걸 한다. 함수 쪽 상위 경로는 이랬다.

순위 (CPU 기준)경로하루 호출CPU 점유
1위예측 공유 카드 이미지 (/p/[id]/opengraph-image)2.4천 건5분
2위예측 상세 페이지 (/p/[id])6.7천 건3분
3위프로필 페이지 (/@핸들)8.6천 건2분

호출 수로 보면 프로필이 가장 많은데, CPU로 보면 공유 카드 이미지가 1위다. 호출당 비용이 일반 페이지의 열 배가 넘었다는 뜻이다. 그래서 이 카드부터 손댔다.

캐시를 걸어둔 줄 알았던 카드가, 매번 새로 그려지고 있었다

카카오톡이나 X에 링크를 붙이면 뜨는 작은 미리보기 이미지가 OG 카드다. Open Graph라는 규약 이름에서 온 말이다. LDBD는 예측마다 자산·방향·수익률이 담긴 카드를 서버에서 그때그때 그림 파일로 그려 준다. 한글이 깨지지 않게 하려면 폰트도 필요한데, 한글 폰트 전체는 5MB나 돼서 통째로 실을 수 없다. 그래서 카드에 실제로 쓰인 글자만 뽑아낸 몇 KB짜리 조각 폰트를 구글 폰트에서 받아오고, 굵기 두 종을 받느라 카드 한 장에 외부 요청이 최대 네 번 나간다. 카드 한 장을 그리는 데 평균 0.34초쯤 걸리는, 서버 입장에서는 꽤 비싼 작업이다.

비싼 건 알고 있었고, 그래서 캐시를 걸어뒀다고 믿고 있었다. 캐시는 한 번 만든 결과를 저장해 뒀다가 같은 요청이 또 오면 새로 만들지 않고 꺼내 주는 장치다. Next.js(LDBD가 쓰는 웹 프레임워크)에는 revalidate라는 값이 있어서 “이 결과는 N초 동안 재사용해라”라고 선언할 수 있고, 카드 코드에도 그렇게 적어 뒀었다.

그런데 실제 서비스의 응답을 확인해 보니 전부 매번 새로 그려지고 있었다. 응답에는 캐시에서 나왔는지(HIT) 새로 만들었는지(MISS)를 알려주는 표식이 붙는데, 예측 카드뿐 아니라 같은 방식으로 만든 카드 일곱 종이 전부 MISS였다. 적어도 우리 구성에서는, 파일 이름과 위치만으로 만들어지는 카드 이미지 라우트에서 revalidate 선언이 프로덕션에서 먹히지 않고 있었다. 더 무서운 건 에러도 경고도 없었다는 점이다. 나는 두 달 동안 캐시가 켜져 있다고 믿었는데, 크롤러가 카드 주소를 두드릴 때마다 폰트 다운로드 네 번을 포함한 렌더가 통째로 다시 돌고 있었다.

수정은 선언을 믿는 대신 응답에 직접 지시를 실어 보내는 것이었다. 응답 헤더에 캐시 정책을 명시해서 Vercel의 edge 캐시 — 전 세계에 퍼져 있는 중계 서버(CDN)에 결과물을 주소별로 저장해 두는 층 — 가 카드를 들고 있게 했다. 보관 기간은 카드의 성격에 따라 나눴다. 판정이 끝난 예측의 카드는 내용이 다시는 변하지 않으니 7일을 주고, 아직 진행 중이거나 수치가 변하는 카드는 5분만 줬다. “판정 완료는 불변”이라는 서비스의 규칙이 그대로 캐시 정책이 된 셈인데, 이번 사건에서 제일 마음에 드는 수정이기도 하다.

쿠키를 읽는 코드 한 줄이 캐시를 통째로 껐다

카드 다음은 페이지들이었다. Next.js에는 ISR(Incremental Static Regeneration)이라는 캐시 방식이 있다. 페이지를 요청이 올 때마다 새로 만드는 대신 완성본을 미리 만들어 뒀다가 나눠 주고, 정해진 주기(예: 60초)마다 한 번씩만 다시 만드는 방식이다. 방문자가 100명이든 1만 명이든 서버는 60초에 한 번만 일하면 되니, 크롤러 부하에는 이만한 방어가 없다.

자산 페이지에는 이미 60초짜리 ISR을 선언해 뒀었다. 그런데 응답을 확인해 보니 이 페이지도 매 요청마다 새로 만들어지고 있었다. 원인은 페이지가 데이터를 읽을 때 cookies(), 그러니까 방문자의 브라우저에 저장된 로그인 정보 같은 걸 읽는 함수를 거치고 있었기 때문이다. Next.js는 쿠키를 읽는 순간 “이 페이지는 보는 사람마다 내용이 다르겠구나”라고 판단해서, 페이지 전체를 요청마다 새로 만드는 동적 렌더로 강등시킨다. 내가 선언해 둔 60초 캐시는 또 조용히 무효가 돼 있었다. 캐시가 꺼진 것보다 꺼졌는데 켜져 있다고 믿는 상태가 더 위험하다는 걸 하루에 두 번 배웠다.

자산 페이지는 로그인과 무관한 공개 데이터만 보여주므로, 쿠키를 아예 읽지 않는 데이터 조회용 클라이언트를 따로 만들어 갈아 끼우는 것으로 ISR이 복구됐다.

진짜 함정은 그다음이었다. 예측 상세와 프로필 페이지도 같은 방식으로 ISR로 바꾸는 과정에서, Next.js 16의 고약한 동작을 하나 만났다. ISR로 등록된 페이지가 만들어지는 도중에 cookies()를 읽으면, 적어도 내가 쓰는 버전에서는 동적 렌더로 곱게 강등되는 게 아니라 DYNAMIC_SERVER_USAGE라는 이름의 500 에러 — 페이지가 아예 안 뜨는 서버 오류 — 를 낸다. 캐시를 켜려고 한 페이지에 쿠키 읽는 코드가 한 줄이라도 남아 있으면 페이지가 통째로 죽는 것이다.

그래서 구조를 바꿨다. 페이지의 몸통은 모두에게 똑같은 캐시본을 주고, 보는 사람에 따라 달라져야 하는 부분 — 팔로우 버튼의 상태나 “이건 내 예측” 표시 같은 것 — 만 브라우저 쪽에서 별도의 API를 불러 채워 넣게 했다. 개발 쪽에서는 이런 걸 클라이언트 아일랜드라고 부른다. 페이지라는 바다 위에 개인화된 부분만 섬처럼 떠 있다는 뜻이다. 신경 쓴 부분이 하나 있는데, LDBD는 베끼기 방지를 위해 진행 중인 예측의 작성자를 가려 두는 정책이 있어서, 캐시본 자체를 가린 상태로 만들고 누구에게 보여줄지 판정은 브라우저 쪽 API에서 하게 했다. 배포 후에 진행 중 예측 페이지의 캐시본에서 작성자 정보가 새지 않는 것, 그리고 새 API가 로그인 없이 부르면 거부되는 것까지 확인했다.

첫 화면이 “검증된 예측 0건”을 보여줬다

이 무렵 부수적인 사건이 하나 더 있었다. LDBD 첫 화면 상단에는 지금까지 검증된 예측이 몇 건인지 보여주는 숫자가 있는데, 어느 순간 이게 0으로 렌더된 걸 봤다. 12만 8천 건 넘게 쌓아 둔 숫자가 첫 화면에서 “검증된 예측 0건”이 돼 있었던 것이다.

원인은 폴백 코드였다. 통계를 읽는 쿼리가 실패하면 0을 대신 넣도록 짜여 있었는데, 하필 첫 화면은 ISR과 CDN으로 캐시되는 페이지라, 한 번 실패한 순간의 0이 캐시에 박제돼서 다음 재생성 때까지 모든 방문자에게 그대로 나간다. 처음 온 사람에게 “검증된 예측 0건”짜리 사이트로 보이는 것이다.

개발 일지 #2에서 얻었던 교훈이 그대로 다시 왔다. 그때는 점수 판정 코드가 “정확한 날짜의 가격이 없으면 아무 가격으로나 확정”하는 문제였고, 결론은 틀린 값으로 조용히 성공하는 것이 그냥 실패하는 것보다 위험하다는 것이었다. 이번엔 같은 패턴이 화면 쪽에서 나왔다. 그래서 실패하면 0을 보여주는 대신, 페이지 재생성 자체를 실패하게 바꿨다. Next.js는 재생성이 실패하면 직전에 성공한 페이지를 계속 내보내므로, 방문자는 낡았지만 올바른 숫자를 보게 된다.

그런데 이 안전장치가 이번에는 배포 쪽에서 문제를 일으켰다. 배포할 때 첫 화면을 미리 만들어 두는 과정이 있는데, 그 시점에 통계 쿼리가 한 번 삐끗하자 방금 넣은 에러가 배포 전체를 실패시킨 것이다. 재시도를 넣어도 같은 쿼리가 계속 실패해서 파고들어 보니, 근본 원인은 일시적인 문제가 아니었다. 검증된 예측 수를 세느라 12만 8천 행짜리 테이블을 매번 처음부터 끝까지 정확히 세고 있었는데, 테이블이 그만큼 불어나자 그 셈이 점점 자주 시간 초과로 끊겼다. 첫 화면이 “어느 순간” 0을 보여준 것도 이 때문이었다. 세는 방법을 바꿔서, 참가자별로 이미 집계해 둔 테이블 36행의 판정 수를 합산하는 것으로 교체했다. 바꾸기 전에 실제 서비스에서 전수 count와 합산값이 정확히 일치하는지(둘 다 128,604건) 확인했다. 결국 답은 단순했다. 큰 테이블을 매번 처음부터 세지 말고, 이미 세어 둔 값을 더하면 됐다.

고치고 나서

이 사건 이후로 생긴 습관이 하나 있다. 캐시를 걸었으면 선언을 믿지 않고 응답 헤더를 직접 확인하는 것이다. 배포 후에 카드·자산·프로필·예측 상세 주소를 하나씩 두드려서 x-vercel-cache 표식이 MISS에서 HIT로 바뀌는 걸 확인했다. 가끔 STALE이 나오는데, 이건 보관 기한이 막 지난 저장본을 일단 내주고 뒤에서 새로 만들어 두는 상태라는 뜻이라 방문자는 기다리지 않는다. 두 달 동안 “선언해 뒀으니 되겠지”로 지나쳤던 걸, 이제는 눈으로 본 것만 믿는다.

고친 효과는 숫자로도 드러났다. 사건 당일인 7월 13일 하루에 서버 함수는 7만 2천 번 깨어나 CPU를 52분어치 썼고, 그 1위가 예측 공유 카드였다. 카드 한 종류에만 하루 2,400번의 요청과 5분의 CPU가 몰렸다. 캐시를 응답 헤더로 옮긴 뒤, 7월 18일부터 25일까지 일주일을 다시 봤다. 같은 카드가 일주일을 통틀어 1천 번 호출에 CPU 2분이었다. 하루로 환산하면 17초 남짓이라, 하루 5분에서 하루 17초로 대략 95%가 사라진 셈이다. 사건 당일 52분까지 치솟았던 하루 함수 CPU도, 잠잠해진 그 주에는 하루 평균 12분 근처였다. 참고로 이 52분은 함수 몫만 따로 본 값이다. 한도에 잡히는 Active CPU에는 미들웨어 몫이 더해지고, 7월 1일부터 12일까지 쓴 누적분도 이미 쌓여 있었다. 그래서 그날 저녁 한 달치 한도가 넘친 것이다.

그렇다고 CPU 1위 자리가 빈 건 아니다. 카드가 비운 자리를 이번엔 예측 상세 페이지가 물려받았는데, 이건 고장이 아니라 구조가 그렇다. LDBD에는 예측마다 고유한 주소가 붙어 그 수가 13만 개에 이르고, 캐시는 주소마다 따로 잡히기 때문에 크롤러가 처음 밟는 주소는 그 순간 한 번은 새로 그려 줘야 한다. 13만 개를 처음 훑는 동안에는 그 첫 렌더가 계속 쌓여서, 일주일에 3만 1천 번 호출에 CPU 33분이 나왔다. 캐시가 안 걸려서가 아니라 캐시가 걸릴 주소가 그만큼 많아서 드는 비용이다. 그리고 호출 한 건당 가장 비싼 작업은 이제 참가자 프로필 페이지를 그리는 일이다. 요청 넷 중 셋은 1초 안에 끝나지만 나머지 하나는 그보다 오래 걸린다. 그래서 서버가 일한 총량만 그래프로 보면 방문자·크롤러 수와 나란히 오르내려서 고친 보람이 없어 보이는데, 경로별로 쪼개 보면 이야기가 다르다. 내가 직접 손댄 곳들은 확실히 조용해졌고, 전체 숫자가 여전히 움직이는 건 아직 손대지 않은 곳과 새로 들어온 트래픽이 끌어올리기 때문이다.

재발 대비도 걸어 뒀다. Vercel에 사용량이 일정 금액에 닿으면 알림이 오도록 설정해서, 다음에 무언가 비정상적으로 돌기 시작하면 경고 메일이 아니라 알림 단계에서 잡을 수 있게 했다. 다만 솔직히 적어 두면, CPU의 30%를 차지하던 미들웨어 쪽은 아직 손대지 않았고, 리더보드와 피드의 캐시도 후보로만 적어 둔 상태다. 이번에 고친 건 가장 비싸던 것들이지 전부가 아니다.

2부 — 알리려다 막히다: AI로 만든 걸 AI 감지기가 죽였다

서버 쪽 사고가 잠잠해질 무렵, 두 번째 사고가 시작됐다. 이건 서버가 아니라 사람들의 커뮤니티에 닿으려는 자리에서 벌어졌고, 원인을 알아내는 데 며칠이 걸렸다.

그전에 — 계정이 조용히 죽어 있었다

먼저 배경 하나. 런칭 회고에도 한 줄 적었는데, 해외 개발자 커뮤니티인 Hacker News(줄여서 HN)에 글을 올리려고 계정 상태를 점검하다가, 그동안 달아 둔 댓글들이 전부 죽어 있는 걸 발견했다. 나한테는 멀쩡히 보이는데 로그아웃하고 보면 하나도 안 보였다. 이렇게 본인에게만 보이고 남에게는 안 보이도록 계정을 조용히 가려 두는 걸 셰도우밴(shadowban)이라고 부른다. 이유를 알 수 없어서 운영진에게 복구를 요청하는 메일을 보냈고, 답이 없어 한 번 더 보냈다. 답장은 끝내 오지 않았는데, 어느 순간 새로 단 댓글이 정상적으로 보이기 시작했다. 왜 죽었고 왜 살아났는지 모른 채로, 일단 됐구나 하고 넘어갔다.

Show HN, 몇 분 만에 죽다

7월 22일 밤, 두 달을 준비한 글을 올렸다. HN에는 자기가 만든 걸 소개하는 Show HN이라는 형식이 있는데, 규칙이 꽤 깐깐하다. 낚시성 제목 금지, 과장 금지, 본문에 정직한 한계까지 적을 것. 그 규칙을 전부 지켜서 올렸다. 그리고 몇 분 만에 글이 죽었다. HN에는 부적절하다고 판단된 글이 목록에서 조용히 내려가는 장치가 있는데, 내 글은 올라오자마자 그렇게 사라졌다. 무엇이 그 판단을 내렸는지, 무엇을 잘못했는지 그때는 알 수 없었다. 이번에도 이유를 모른 채, 운영진에게 또 메일을 보냈다.

모더레이터의 답장 — 범인은 “AI가 쓴 글”이었다

이튿날인 7월 23일, HN 모더레이터(커뮤니티를 관리하는 운영자)에게서 답장이 왔고, 그동안의 미스터리가 한 번에 풀렸다. HN의 소프트웨어가 내 글을 genai, 그러니까 “AI가 생성한 텍스트”로 자동 분류해서 죽이고 있었던 것이다. 열흘쯤 전 계정이 셰도우밴됐던 것도 같은 이유였다. 그동안 내가 단 댓글들은 AI가 다듬어 준 문장이었고, 그게 전부 AI 텍스트로 걸렸던 것이다. 모더레이터의 지침은 분명했다. 이 커뮤니티에 올리는 글에는 LLM을 한 조각도 쓰지 말 것, 문장을 다듬는 데 쓰는 것조차 안 된다는 것이었다.

아이러니

여기서 잠깐 멈추게 됐다. 나는 개발 경험이 없는 사람이고, 이 서비스 전체 — 사이트도, 봇도, 채점 로직도 — 를 두 달간 Claude Code라는 AI 도구로 만들었다. 그 사실을 정직하게 알리려고 쓴 글이 이번 글의 요지였다. 그런데 그 글을 AI에게 한 번 다듬게 했더니, 다른 AI 감지기가 “이건 AI가 쓴 글”이라며 죽였다. 사람들에게 닿으려던 길의 적어도 첫 번째 문턱은, 알고 보니 사람이 아니라 기계였던 셈이다.

손으로 다시 쓰니 살아남았다

그래서 본문을 처음부터 내 손으로 다시 썼다. 매끄럽게 다듬으려 하지 않고, 서툰 영어 그대로 뒀다. 다시 쓴 글은 더 짧고, 덜 매끄럽고, 문장도 투박했다. 대신 내가 실제로 말하려던 순서에 가까웠다. AI가 다듬은 글은 틀린 말이 없었지만, 너무 안전하고 너무 정리돼 있었다. 그걸 모더레이터에게 보냈더니, 그가 직접 새 사본으로 글을 다시 올려 줬다. 다만 하필 미국의 저녁 시간대라, 사람들이 깨어 있는 낮을 놓친 글은 조용히 지나갔다. 점수는 3점, 댓글은 세 개, 유입된 방문자는 100명 남짓이었다. 폭발을 기대했다면 실망스러운 숫자지만, 0도 아니었다. 그래도 그 세 개 중 하나는 값졌다. 채점을 어떻게 설계했느냐고 진지하게 물어 온 사람이 있었다. 봇이 그냥 운이 좋았던 건지 실력인지를 숫자로 어떻게 갈라내느냐는, 내가 이 서비스를 만들며 제일 오래 붙들고 있던 질문이다. 그 질문에 답하며 오간 짧은 대화가, 두 달 준비한 값을 조금은 했다.

이번에 배운 것

문제가 터졌다는 건, 드디어 사람이 왔다는 뜻이다. 두 달 동안 방문자가 0명일 때는 캐시가 꺼져 있어도, 카운트 쿼리가 느려도, 계정이 죽어 있어도 아무 일도 일어나지 않았다. 서버가 버거워한 것도, 글이 감지기에 걸린 것도, 전부 드디어 누군가 이 서비스에 닿으려 했기 때문에 생긴 일이다. 만드는 동안엔 이런 사고가 없다. 그게 성격이 전혀 다른 두 사건의 가장 큰 공통점이다.

웹에 문을 열면 방문자보다 크롤러가 먼저 온다. 그리고 더 많이 온다. 방문자 600명 뒤에는 7만 번의 함수 호출이 있었고 대부분은 검색엔진과 AI의 크롤러였다. 그런데 이건 쫓아낼 방문자가 아니다. 검색에 걸리라고 사이트맵까지 내며 내가 초대한 쪽이고, 요즘은 AI가 읽어 가는 것도 유입 경로다. 문을 열어둔 대가는 크롤러를 막는 게 아니라 캐시로 낸다. 같은 페이지를 만 번 요청받아도 만들기는 한 번만 하면 되게.

캐시는 선언이 아니라 관측으로 확인한다. revalidate를 적어 뒀다고 캐시가 도는 게 아니었다. 한 곳은 선언이 통째로 무시됐고, 한 곳은 쿠키를 읽는 코드 한 줄이 그 선언을 무효로 만들었고, 어느 쪽도 에러를 내지 않았다. 응답 헤더를 눈으로 확인하는 데는 10초가 걸리고, 안 한 대가는 한 달치 CPU였다.

실패를 0으로 가리지 않는다. 쿼리 실패를 0으로 덮으면 화면은 멀쩡해 보이지만 첫 화면이 거짓말을 하게 된다. 조용히 틀리는 것보다 시끄럽게 실패하는 쪽이 낫다는 걸, 점수 파이프라인에 이어 화면에서 한 번 더 확인했다.

이제 커뮤니티에 올리는 글은 손으로 쓴다. 매끈한 문장이 오히려 의심을 사고, 서툰 문장이 통행증이 되는 자리가 적어도 지금 그 커뮤니티에는 생겼다. 무엇을 만들었는지가 아니라 어떻게 썼는지로 사람을 거른다는 게 조금 씁쓸하지만, 덕분에 배운 것도 있다. 내 손으로 처음부터 쓰면 문장은 서툴러도 적어도 내 생각이 그대로 담긴다. 여기서부터 해외 커뮤니티에 올리는 글은 전부 그렇게 쓴다.

만드는 동안엔 모르는 문제가 있고, 알리기 시작해야만 생기는 문제가 있다. 이번 2주는 그 두 번째 종류를 처음 배운 시간이었다.

dev-loglaunchperformancecachinghacker-news