작은 서버를 망가뜨리는 것은 메모리가 아니라 질문의 부재다
Hatched by min dulle
May 08, 2026
6 min read
3 views
84%
서버를 켜는 일은 반쯤만 끝난 일이다
새 서버를 띄웠다고 해서 진짜 시작된 것은 아니다. 오히려 그때부터가 질문의 시간이다. 이 인스턴스는 왜 느려지는가, 어떤 포트가 열려 있는가, 외부 요청은 어디까지 들어올 수 있는가, 배포된 코드는 예상대로 동작하는가. 많은 사람들은 서버를 “살리는” 데 집중하지만, 실제로 서비스 품질을 결정하는 것은 서버를 어떻게 통제 가능한 상태로 유지하느냐다.
흥미로운 점은, 이 질문이 인프라의 세계에서만 중요한 것이 아니라는 사실이다. 서버를 붙이고 스왑을 만들고 Nginx를 설치하는 일과, 코드 리뷰에서 요구사항, 논리, 보안, 관측 가능성을 묻는 일은 얼핏 전혀 달라 보인다. 하지만 둘은 같은 본질을 향한다. 복잡한 시스템은 기본적으로 실패할 수 있으며, 좋은 실무는 그 실패를 미리 상정하고 다루는 기술이라는 점이다.
시스템을 잘 만든다는 것은 더 많은 기능을 추가하는 일이 아니라, 더 적은 우연에 기대는 일이다.
이 관점에서 보면, 서버 설정과 코드 리뷰는 분리된 두 단계가 아니다. 둘 다 “이 시스템은 어떤 조건에서 무너지는가”를 묻는 방어적 사고의 서로 다른 표현이다. 하나는 운영 환경을 단단하게 만들고, 다른 하나는 구현의 허점을 줄인다. 결국 같은 질문에 대한 두 개의 답변이다.
가장 먼저 봐야 할 것은 성능이 아니라 취약점이다
새로 띄운 서버에서 가장 흔한 착각은, 일단 돌아가기만 하면 괜찮다고 믿는 것이다. 하지만 작은 서버일수록 문제는 빨리 드러난다. 메모리가 1GB에 불과한 환경에서 스왑이 없는 상태는, 자동차에 예비타이어 없이 장거리 운전을 하는 것과 비슷하다. 당장 달릴 수는 있어도, 예상보다 조금만 더 부담이 걸리면 시스템은 멈춘다.
그래서 스왑을 생성하는 일은 단순한 최적화가 아니다. 그것은 실패 완충 장치를 넣는 일이다. 4GB 스왑은 느린 메모리를 추가하는 마법이 아니라, 순간적인 메모리 압박이 전체 서비스를 즉시 죽이지 않도록 만드는 안전판이다. 이 한 가지 조치만 봐도, 좋은 시스템 운영은 “최적 상태”가 아니라 최악의 순간에도 버티는 상태를 목표로 한다는 사실이 드러난다.
이 생각은 코드 리뷰의 질문과 정확히 이어진다. 구현이 논리적으로 맞는가, 성능은 충분한가, 동시성 문제는 없는가, 에러 처리는 되어 있는가. 이 질문들은 겉으로는 품질 점검처럼 보이지만, 실제로는 모두 같은 목적을 가진다. 예외 상황에서 시스템이 어떻게 붕괴하는지 미리 보는 것이다.
예를 들어, 웹 서버가 정상 상태에서는 잘 응답해도 동시 요청이 몰리면 파일 디스크립터가 고갈될 수 있다. 데이터베이스 쿼리가 정확해도 인덱스가 없으면 트래픽이 늘 때 즉시 병목이 생긴다. 인증 로직이 작동해도 에러 메시지가 너무 친절하면 공격자에게 힌트를 준다. 즉, 진짜 실력은 평상시의 아름다움이 아니라 압박 상황에서의 질서로 드러난다.
여기서 중요한 전환이 있다. 성능, 보안, 안정성은 서로 다른 체크박스가 아니다. 그것들은 모두 실패의 전파를 얼마나 잘 끊는가라는 하나의 문제를 다른 각도에서 본 것이다. 서버의 스왑은 메모리 부족의 전파를 늦추고, 방화벽 규칙은 외부 침입의 전파를 막고, 코드의 입력 검증은 잘못된 데이터의 전파를 차단한다.
포트를 여는 일과 코드를 검토하는 일은 같은 종류의 판단이다
80번과 443번 포트를 연다는 것은 단지 웹사이트를 외부에 노출하는 행위가 아니다. 그것은 무엇을 허용하고 무엇을 거부할지 명시하는 정책 결정이다. 클라우드 패널에서 퍼블릭 IP를 붙이고 보안 목록에 ingress rule을 추가하는 과정은, 시스템의 경계를 정의하는 일이다. 경계가 없는 시스템은 편리해 보이지만 실제로는 무방비 상태다.
이것은 코드 리뷰에서 “보안적인가”를 묻는 것과 본질적으로 같다. 포트를 열 때는 공격면이 넓어지고, 의존성을 추가할 때는 공급망 리스크가 늘어난다. 마찬가지로 기능을 빨리 만들기 위해 외부 라이브러리를 붙이면, 그 라이브러리의 라이선스, 유지보수 상태, 버전 호환성까지 함께 떠안게 된다. 도구를 가져오는 순간, 그 도구의 리스크도 함께 수입한다.
이 점을 이해하면, 시스템 설계는 훨씬 덜 낭만적이지만 훨씬 더 현실적이 된다. 좋은 운영자는 “열 수 있는가”보다 “열어도 되는가”를 먼저 묻는다. 좋은 리뷰어는 “작동하는가”보다 “어떤 비용을 미래에 남기는가”를 먼저 묻는다. 이 두 태도는 사실상 같다.
비유하자면, 서버는 집이고 포트는 문이다. 집을 지었다고 해서 모든 문을 열어둘 이유는 없다. 정문은 손님을 위해 열고, 창문은 환기를 위해 조금 열 수 있다. 하지만 지하실 문까지 열어둘 필요는 없다. 코드 역시 마찬가지다. 모든 기능을 외부에 노출하는 것은 자유가 아니라 무질서다.
이 관점에서 보면, 단순한 설정 작업조차 철학을 품고 있다. 공개 IP를 붙이고, 보안 그룹을 조정하고, 80과 443만 허용하는 행위는 “이 서비스는 인터넷과 대화하지만, 아무와나 대화하지는 않는다”는 선언이다. 그리고 이 선언은 코드 안에서도 반복되어야 한다. 입력값을 검증하고, 실패를 명시하고, 민감 정보를 숨기고, 로그를 남기는 일은 모두 같은 선언의 다른 문법이다.
경계 관리는 보안의 일부가 아니라, 시스템 설계의 출발점이다.
좋은 시스템은 요구사항을 충족하는 것이 아니라, 질문에 견딘다
코드 리뷰에서 가장 중요한 질문은 의외로 간단하다. 원래 요구사항을 만족하는가. 하지만 이 질문이 중요한 이유는 기능 목록을 맞췄는지 확인하기 위해서가 아니다. 요구사항은 시스템이 존재해야 하는 이유를 보존하기 위한 최소 기준이기 때문이다. 요구사항을 벗어난 구현은 아무리 우아해 보여도 종종 부담만 늘린다.
여기서 두 번째 질문이 따라온다. 논리적으로 맞는가. 많은 코드가 이 질문 앞에서 무너진다. 테스트 환경에서 한번 실행되는 것과 실제 운영에서 지속적으로 버티는 것은 전혀 다르다. 예를 들어, 요청 하나를 처리하는 함수가 단순히 성공만 반환한다면 좋게 보일 수 있다. 하지만 실패했을 때 재시도 전략이 없고, 부분 성공을 정리하지 못하고, 중복 호출에 안전하지 않다면 그 코드는 이미 불안정하다.
이 지점에서 “불필요한 복잡성이 없는가”라는 질문이 중요해진다. 역설적이지만, 시스템을 튼튼하게 만드는 가장 흔한 방법은 기능을 더하는 것이 아니라 구조를 덜 복잡하게 만드는 것이다. 복잡성은 버그의 토양이다. 특히 작은 서버나 제한된 자원 환경에서는 더 그렇다. 스왑을 넣는 이유도 결국 같은 맥락이다. 부족한 자원을 임시로 보완하는 것이지, 설계가 허술한 것을 은폐하는 수단이 아니다.
좋은 아키텍처는 마치 잘 정리된 작업대 같다. 필요한 도구는 손에 닿는 곳에 있고, 불필요한 물건은 치워져 있으며, 무엇이 어디에 있는지 한눈에 보인다. 반대로 복잡한 코드베이스는 도구가 너무 많고, 문이 너무 많고, 어디로 들어가야 할지 알 수 없는 복도 같은 상태다. 그런 시스템은 기능이 많아도 신뢰를 주지 못한다.
이때 관측 가능성이 마지막 퍼즐을 맞춘다. 로그, 메트릭, 트레이싱이 없으면 시스템은 “돌아가는 것처럼 보이는” 블랙박스가 된다. 하지만 문제가 생겼을 때 원인을 찾지 못하면, 복잡성은 단지 코드 안에만 있는 것이 아니라 운영팀의 시간과 정신을 갉아먹는 부채가 된다. 좋은 시스템은 단지 잘 동작하는 시스템이 아니라, 문제가 생겼을 때 이해할 수 있는 시스템이어야 한다.
가장 실용적인 원칙은, 실패를 미리 설계하는 것이다
여기서 하나의 통합된 원칙이 보인다. 서버 설정과 코드 리뷰를 관통하는 원칙은 실패를 상정한 설계다. 서버는 메모리가 부족해질 수 있고, 네트워크는 불안정할 수 있고, 외부 공격은 들어올 수 있고, 배포된 코드는 예상보다 복잡할 수 있다. 그러므로 질문은 늘 같다. 이 시스템은 실패를 어떻게 흡수하는가.
실패를 흡수한다는 것은 단순히 에러를 잡는 것이 아니다. 실패가 어디까지 퍼질지 경계를 정하고, 원인을 추적할 수 있게 하며, 복구를 빠르게 하는 것이다. 스왑은 메모리 압박의 충격을 완화하고, 포트 제한은 외부 노출을 줄이며, 에러 처리는 잘못된 경로를 명확히 하고, 로깅은 복구의 방향을 알려준다. 이 모든 것은 서로 다른 기술처럼 보이지만, 사실상 하나의 시스템적 감각을 공유한다.
이 감각을 익히면, 의사결정 방식도 바뀐다. 새로운 의존성을 넣기 전에 “정말 필요한가”를 묻게 되고, 서버를 열기 전에 “왜 이 포트가 필요한가”를 묻게 되며, 함수를 작성하기 전에 “실패할 때 무엇이 일어나는가”를 묻게 된다. 이런 질문은 속도를 늦추는 것처럼 보이지만, 실제로는 나중의 대가를 줄인다.
가장 강력한 팀은 빠르게 만드는 팀이 아니라 안전하게 빠르게 만드는 팀이다. 그리고 안전은 우연히 생기지 않는다. 스왑 파일 하나, 보안 규칙 하나, 리뷰 질문 하나가 쌓여야 한다. 작은 결정들은 사소해 보여도, 결국 시스템 전체의 체력을 결정한다.
Key Takeaways
-
성능보다 먼저 실패 완충 장치를 만들라. 스왑, 재시도, 타임아웃, 에러 처리처럼 시스템이 흔들릴 때 버티게 해주는 장치를 먼저 점검하라.
-
포트를 여는 일은 정책을 정하는 일로 생각하라. 무엇을 외부에 노출할지, 어디까지 허용할지, 왜 그 범위인지 명확히 해야 한다.
-
코드 리뷰에서는 “작동함”보다 “어떻게 무너지는가”를 보라. 요구사항 충족, 논리성, 보안, 관측 가능성, 복잡성은 모두 붕괴 경로를 줄이기 위한 질문이다.
-
의존성은 기능이 아니라 부채도 함께 가져온다. 라이선스, 유지보수성, 성능 영향, 보안 리스크를 반드시 함께 평가하라.
-
관측 가능성은 사치가 아니라 복구 능력이다. 로그와 메트릭이 없으면 문제를 고칠 수 있어도 이해할 수는 없다.
끝내 중요한 것은, 시스템을 믿는 것이 아니라 질문을 유지하는 일이다
서버를 세팅하는 사람은 종종 자신이 인프라를 다뤄야 한다고 생각한다. 코드 리뷰를 하는 사람은 자신이 구현을 검토해야 한다고 생각한다. 하지만 더 깊은 수준에서는 둘 다 같은 일을 하고 있다. 시스템이 실패할 수 있다는 사실을 잊지 않도록 하는 것이다.
이제 관점을 바꿔보자. 좋은 시스템은 잘 만들어진 시스템이 아니다. 좋은 시스템은 질문을 계속 받더라도 무너지지 않는 시스템이다. 스왑을 추가하는 일은 질문 하나에 대한 답이고, 포트 정책을 세우는 일도 질문 하나에 대한 답이며, 리뷰 체크리스트를 사용하는 일도 질문 하나에 대한 답이다. 중요한 것은 답을 아는 것이 아니라, 질문을 멈추지 않는 것이다.
결국 안정성은 기술 스택의 문제가 아니라 태도의 문제다. 우리는 기능을 더 많이 쌓아야만 신뢰할 수 있다고 착각하기 쉽다. 하지만 실제로는 반대다. 신뢰는 덜 놀라고, 덜 노출되고, 덜 복잡하고, 더 잘 관찰되는 구조에서 나온다. 시스템을 잘 운영한다는 것은 그것을 완벽하게 만드는 일이 아니라, 실패해도 이해 가능한 형태로 유지하는 일이다.
그리고 어쩌면 이게 가장 중요한 통찰일지 모른다. 서버를 띄우는 순간부터 진짜 업무가 시작되는 것이 아니라, 그 서버가 왜 버티는지 계속 설명할 수 있을 때 비로소 업무가 시작된다.
Sources
Hatch New Ideas with Glasp AI 🐣
Glasp AI allows you to hatch new ideas based on your curated content. Let's curate and create with Glasp AI :)
Start Hatching 🐣