개발 팁
프로젝트와 상관없이 다시 쓸 수 있는 요령을 문제·원인·해법·코드 예시로 짧게 적습니다.
- SPA는 새 버전을 스스로 감지해 새로고침을 권한다 →배포 뒤에도 열려 있는 탭은 새로고침 전까지 옛 번들로 돈다. 탭으로 돌아오거나 다시 연결될 때 index.html의 번들 이름을 비교해 새로고침 버튼을 보여 준다.
- compose command는 리스트로 쓰고, 실제 프로세스 인자로 확인한다 →YAML 접힘(>)에서 더 들여 쓴 줄은 줄바꿈이 남아, sh -c가 첫 줄만 실행하고 옵션이 조용히 빠진다.
- 같은 상태를 보는 연결은 한 프로세스로 모으고, 휘발성 이벤트는 묶어 보낸다 →스티키 라우팅으로 같은 문서의 연결을 한 프로세스에 모으고, 프로세스 안에서는 메모리로 나눠 주고, 커서처럼 마지막 값만 중요한 이벤트는 모아 보낸다.
- flex·grid 안의 input과 긴 텍스트에는 min-width: 0을 준다 →flex·grid 항목의 기본 min-width: auto 때문에 내용보다 작게 줄어들지 못하고 가로로 넘친다. 모바일 가로 넘침의 가장 흔한 원인이다.
- 오래 도는 루프의 지역 변수는 GC 루트다 →while True 루프는 다음 메시지를 기다리는 동안에도 마지막으로 다룬 객체를 붙잡는다. 한 번의 처리를 함수로 빼면 참조가 함수 안에서 끝난다.
- 운영 이미지 태그는 패치 버전까지 고정한다 →latest나 2-alpine 같은 움직이는 태그는 배포할 때마다 컨테이너를 예고 없이 다시 만들 수 있다.
- 연결 정리는 finally에서 「어떻게 끝나든 정확히 한 번」 →프레임워크의 disconnect 콜백은 예외로 끝나면 불리지 않을 수 있다. 진입점을 try/finally로 감싸고 두 번 정리하지 않게 표시를 둔다.
- 시험 환경에서는 일부러 느리게 만든 것을 끈다 →비밀번호 해셔는 MD5, Postgres는 tmpfs와 fsync=off, Redis는 저장 끔, 배경 작업 주기는 짧게, YAML은 C 로더. 운영 설정에는 두지 않는다.
- E2E는 폴링·고정 대기 대신 이벤트로만 기다린다 →expect.poll·waitForTimeout·toPass 대신 MutationObserver와 스트림 메시지 리스너로 화면이 바뀔 때마다 한 번씩만 다시 본다. 전체 E2E 6분(실패 10개)이 CI 45초(110개 통과)가 됐다.
- 시험 DB는 마이그레이션해 둔 템플릿에서 복제한다 →워커마다 migrate를 돌리지 않는다. 마이그레이션 해시로 이름 지은 템플릿 DB를 한 번 만들고 CREATE DATABASE … TEMPLATE으로 복제하면 7~9초가 1초 미만이 된다.
- 처음 쓸 때 적재되는 것은 세션 픽스처에서 미리 읽는다 →URLconf나 정의 레지스트리처럼 첫 사용 때 적재되는 비용을 「처음 쓴 시험」 하나가 혼자 지면, 빌드마다 다른 시험이 떨어진다. autouse 세션 픽스처로 앞당긴다.
- Next.js 공개 값은 빌드에 굽지 말고 실행 환경에서 넘긴다 →NEXT_PUBLIC_*은 번들에 문자열로 박혀 환경마다 이미지를 다시 굽게 만든다. 실행 env를 루트 레이아웃이 window로 넘기면 같은 이미지를 어디서나 쓴다. 빌드는 Turbopack으로.
- SSE 동시 접속 한도는 CPU보다 파일 서술자 한도에서 먼저 막힌다 →스트림 하나가 소켓 하나와 Redis 구독 하나, 서술자 2개를 쓴다. 컨테이너 기본 soft 한도 1024면 약 500명에서 Too many open files가 난다.
- 문서가 닫힐 때 malloc_trim으로 빈 메모리를 OS에 돌려준다 →glibc는 free한 메모리를 바로 OS에 돌려주지 않는다. 큰 객체(문서·방)가 닫힐 때 간격을 두고 malloc_trim(0)을 부르면 RSS 355MB가 229MB로 줄었다.
- 지연에 민감한 프로세스는 CPU 몫 제한 대신 코어를 못박는다 →--cpus 몫 제한은 할당량이 차면 프로세스를 0.1초씩 통째로 멈춘다. 틱 루프 같은 프로세스는 --cpuset-cpus로 코어를 고정하고 taskset으로 자리를 나눈다.
- 시험 하나마다 시간 예산을 두고, 러너가 강제한다 →순수 로직 0.5초, 백엔드 1초, E2E 3초. 넘으면 통과한 시험도 실패로 바꾸고, 예산을 늘리는 대신 로직과 준비를 고친다.
- 시간에 기대는 화면은 시험이 시간을 쥔다 →마감·만료·자동 갱신을 실제로 기다리지 않는다. 브라우저는 page.clock, 서버의 「지금」은 주입한 시계, 배경 주기 작업은 「한 번 돌기」로 부른다.
- Django ASGI에서 DB 연결은 풀로 쓰고, 오래 열린 요청은 바로 돌려준다 →ASGI에서는 CONN_MAX_AGE를 올려도 연결이 재사용되지 않는다. psycopg 풀을 켜고, SSE·롱폴링은 DB를 쓴 직후 연결을 풀에 돌려준다.
- E2E 준비는 API로 하고, 화면은 흐름마다 한 번만 지난다 →가입·만들기처럼 앞 단계에 필요한 사정은 화면을 열기 전에 API로 깔고 쿠키를 브라우저 맥락에 옮긴다.
- 백엔드 시험 시간은 벽시계가 아니라 CPU 시간으로 잰다 →CI가 붐비면 1초 근처 시험이 빌드마다 번갈아 떨어진다. 프로세스가 쓴 CPU 시간에 자식 프로세스 몫을 더해 재면 부하에 흔들리지 않는다.