안 보이면 못 고친다
작게 시작한 앱이라도, 실제 사용자가 붙는 순간 "지금 서버가 괜찮은가?"라는 질문에 답할 수 있어야 합니다. 문제가 생겼을 때 로그를 뒤늦게 뒤지는 대신, 평소에 보이게 만들어 두는 것이 옵저버빌리티입니다.
지표·로그·트레이스 한 곳으로
백엔드에 관측 에이전트를 붙여 지표(metrics)·로그(logs)·트레이스(traces) 를 수집하고, 이를 대시보드로 모읍니다(Grafana 계열 + OpenTelemetry). 대시보드에서는 API p95 지연, 5xx 비율, 요청량, DB 연결 상태, 메모리를 봅니다. 트레이스는 점수 제출처럼 DB 쓰기와 외부 저장소 접근이 섞인 요청에서 병목 구간을 찾는 데 씁니다. 앱에서 일어나는 사용자 행동 이벤트도 배치로 백엔드에 보내, 제품 판단의 근거로 씁니다.
헬스 체크 엔드포인트도 공개해 두어, 배포 자동화와 외부 모니터링이 "지금 살아 있나"를 언제든 물어볼 수 있게 했습니다.
배포는 무섭다 — 그래서 4겹 안전망
혼자 운영하는 서버에 배포하는 건 늘 조마조마합니다. 그래서 배포를 한 줄 명령이 아니라 여러 겹의 게이트를 지나는 파이프라인으로 만들었습니다.
- Preflight — 컴파일·테스트·커버리지·이미지 빌드·필수 설정 존재 여부를 배포 전에 검증합니다. 하나라도 실패하면 프로덕션을 건드리기 전에 멈춥니다.
- 현재 버전 기억 — 문제 시 되돌아갈 지점을 저장합니다.
- 롤링 배포 — 새 인스턴스가 헬스 체크를 통과한 뒤에야 기존 인스턴스를 내려, 무중단으로 교체합니다.
- Smoke 테스트 — 배포 직후 핵심 흐름을 실제로 호출합니다: 헬스 체크, 인증(토큰 갱신), 카탈로그 조회, 점수 제출, 파일 업로드 presign 발급.
- 실패 시 자동 롤백 — smoke가 실패하거나 헬스 체크가 타임아웃되면, 2번에서 저장한 이전 릴리스로 자동 복귀합니다.
핵심은 "실패를 사람이 발견하기 전에, 자동으로 되돌린다"입니다.
1인 운영의 안전망은 영웅적 대응이 아니라 자동화다 — 실패를 사람이 발견하기 전에 파이프라인이 스스로 되돌린다.
예컨대 컴파일은 되지만 점수 제출 경로를 깨뜨리는 변경이 나갔다고 해봅시다. 사용자가 항의하기 전에 smoke의 점수 제출 호출이 실패하고, 파이프라인이 곧바로 이전 버전으로 되돌립니다 — 이게 이 안전망이 막도록 설계된 종류의 사고입니다.
DB 스키마도 안전하게
데이터베이스 변경은 특히 되돌리기 어렵습니다. 그래서 마이그레이션 도구로 스키마 변경을 버전 관리하고, 앱이 뜰 때 자동 적용되도록 했습니다. 컬럼 추가 같은 변경은 기존 코드와 호환되게(덧붙이는 방향으로) 설계해, 배포 순서에 따른 사고를 줄였습니다.
리소스는 넉넉하되 낭비 없이
관측 스택은 공짜가 아닙니다. 에이전트도 메모리를 먹습니다. 그래서 앱과 관측 도구가 함께 도는 환경의 리소스를 실제 사용량에 맞춰 잡되, 여유(헤드룸)를 두어 급증에도 견디게 했습니다. "보이게 만드는 도구가 서비스를 방해하지 않게" 하는 균형입니다.
마치며
옵저버빌리티와 안전한 배포는 화려한 기능은 아닙니다. 사용자는 이걸 직접 보지 못합니다. 하지만 밤에 두 다리 뻗고 자게 해주는 건 이 부분입니다. 보이게 만들고, 자동으로 검증하고, 실패하면 스스로 되돌리게 — 1인 개발일수록 이 자동화가 곧 안심이자 지속 가능성이었습니다.
If you can't see it, you can't fix it
Even an app that started small has to answer "is the server OK right now?" the moment real users arrive. Instead of digging through logs after the fact, observability is about making things visible in the normal course of things.
Metrics, logs, and traces in one place
An observability agent on the backend collects metrics, logs, and traces, gathered into dashboards (Grafana-family + OpenTelemetry). The dashboard shows API p95 latency, the 5xx rate, request volume, DB connection state, and memory. Traces are used to find bottlenecks in requests that mix a DB write with external storage — like score submission. User behavior events from the app are batched to the backend too, as a basis for product decisions.
A health-check endpoint is exposed so deploy automation and external monitoring can ask "are you alive?" at any time.
Deploys are scary — so, a four-layer safety net
Deploying to a server you run alone is always nerve-racking. So a deploy isn't a one-line command but a pipeline that passes through several gates.
- Preflight — verify compilation, tests, coverage, the image build, and the presence of required config before deploying. If any of these fail, it stops before touching production.
- Remember the current version — save a point to roll back to if things go wrong.
- Rolling deploy — the new instance is only cut in after it passes a health check, then the old one drains — a zero-downtime swap.
- Smoke test — actually call the core flows right after deploy: health check, auth (token refresh), catalog fetch, score submit, and file-upload presign.
- Auto-rollback on failure — if the smoke test fails or the health check times out, automatically revert to the previous release saved in step 2.
The point is: "revert automatically, before a human even notices the failure."
A solo operator's safety net isn't heroics — it's automation. The pipeline reverts itself before a human even notices the failure.
Say a change ships that compiles but breaks the score-submit path. Before a user can complain, the smoke test's score-submit call fails and the pipeline reverts to the previous version right away — exactly the kind of incident this safety net is built to catch.
Keep DB schema changes safe too
Database changes are especially hard to undo. So schema changes are version-controlled with a migration tool and applied automatically at app startup. Changes like adding a column are designed to stay compatible with existing code (additive), reducing incidents tied to deploy ordering.
Generous on resources, but no waste
An observability stack isn't free; agents eat memory too. So the resources for the environment where the app and observability tools run together are sized to actual usage, with headroom to withstand spikes. It's the balance of "the tools that make things visible must not get in the service's way."
Closing
Observability and safe deploys aren't flashy features — users never see them directly. But they're what lets you sleep soundly at night. Make it visible, verify automatically, and let it revert itself on failure — the more solo the project, the more that automation is the peace of mind and the sustainability.