캐시는 보통 읽기 성능을 올리기 위해 사용한다고 알고 있습니다.하지만 그렇다고 해서 모든 곳에 캐싱을 적용할 수는 없습니다. 캐시는 성능을 얻는 대신, 정합성을 어느 정도 포기해야 하는 구조이기 때문에 무작정 사용하는 건 오히려 리스크가 될 수 있습니다.또 하나 짚고 넘어가야 할 점은, 캐싱만으로 모든 읽기 문제를 해결하려는 접근은 좋은 전략이 아니라는 점입니다. 캐시는 어디까지나 보조 수단이지, 데이터의 진짜 기준(Source of Truth)을 대체하지는 않습니다.그렇다면 캐시가 없거나, 캐시 미스가 발생한 경우 데이터는 어디에서 가져와야 할까요? 흔히 RDB에서 가져온다고 생각하기 쉽지만, 이것도 여러 선택지 중 하나일 뿐입니다. 실제로는 데이터의 성격에 따라 검색 엔진, 별도의 조회용 스토어, 외..
개발을 하다 보면 문법적으로는 성공하는데, 실제로는 정상적으로 동작하지 않는 코드들이 분명히 생깁니다. 이런 문제들은 보통 테스트를 통해 잡게 되는데, 테스트는 결국 코드를 직접 실행시키는 과정이다 보니 비용이 들고, 자주 돌리기에는 부담이 되는 것도 사실입니다.그래서 모든 문제를 항상 테스트까지 가져가기보다는, 코드만 보고도 "이건 위험하다"라고 판단할 수 있는 것들은 최대한 앞단에서 걸러내는 게 더 효율적입니다. 이 역할을 해주는 게 Lint 같은 정적 분석 도구입니다.Lint는 코드를 실행하지 않고, 코드 자체를 분석해서 잠재적인 버그 가능성이나 실수하기 쉬운 코드 패턴들을 찾아내고, 개발자에게 미리 수정하라는 신호를 줍니다. 다만 정적 분석이다 보니, 실제로 값이 어떻게 들어오는지에 따라 달라지는..
MDC는 ThreadLocal에 로그 컨텍스트를 저장해두고, logframwork가 자동으로 꺼내 쓰게 만드는 장치라고 합니다. 위의 메시지로 그려보면 위와 같은 그림이 그려집니다. 그렇다면 우리는 어떤것을 학습해야 할까요?1. Thread Local이란 무엇인가?2. 무엇을 보고 logFramwork는 ThreadLocal에서 꺼내게 만드는가?요런것들이 학습되어야 하지 않나 생각이 듭니다. MDC가 비동기에서 어떻게 동작하는지 실제로 설정은 어떻게 하는지 등등을 학습하면 될거 같습니다. Thread Local이란 무엇이고 어떻게 저장하는가?Thread Local은 쓰레드마다 가지고있는 저장소라고 합니다. 그렇다면, 각각 쓰레드는 어떻게 ThreadLocal에 저장할까요?ThreadLocal t = ne..
지금까지 두 가지 생성 패턴을 학습했습니다. 팩토리 패턴과 빌더 패턴입니다. 팩토리 패턴은 객체 생성 과정에서어떤 구현체를 생성할 것인가에 대한 결정을 호출부에서 분리하는 데 초점이 맞춰진 패턴입니다.즉, 생성의 선택 책임을 한 곳으로 모아 코드의 결합도를 낮추는 것이 핵심입니다. 반면 빌더 패턴은 객체를 어떻게 구성해서 생성할 것인가에 집중합니다.생성자 파라미터 폭발 문제를 해결하고, 복잡한 객체를 단계적으로 조립함으로써 가독성과 안정성을 확보하는 것이 목적입니다. 그렇다면, 퍼사드 패턴은 어떤 패턴일까요? 뭐랄까 이전의 패턴들과 다르게 용어부터 심상치 않습니다. 퍼사드는 외관이라는 뜻을 가지고 있다고 합니다. 그렇다면, 퍼사드 패턴은 겉보기에 괜찮아 보이게 만드는 패턴일까요?왜 탄생하게 되었을까?가장..
서비스 규모가 커지면서 쓰기(write) 작업은 점점 많아졌고, 자연스럽게 각 서비스는 자신에게 맞는 방식으로 데이터를 저장하기 시작했습니다. 어떤 서비스는 데이터베이스에 직접 쓰고, 어떤 서비스는 메시지 큐를 거쳐 비동기로 처리했습니다.문제는 장애가 발생했을 때 드러났습니다. 쓰기 경로가 서비스마다 달라지면서 "이 쓰기가 실제로 처리되었는지","어디까지 반영된 상태인지"를 판단할 수 없게 된 것입니다. 즉, 시스템 전체의 쓰기 상태를 하나의 기준으로 확인할 수 없게 되었습니다.넷플릭스는 이 문제를 개별 서비스의 로직을 고치는 방식이 아니라, 쓰기 자체의 흐름을 통제하는 방식으로 해결했습니다.그 선택이 바로 WAL(Write-Ahead Logging)이었습니다. 이 글에서는 넷플릭스가 WAL을 통해 분산..
개발을 하다 보면 멀티모듈이라는 말을 들어보았었다. 단순히 모듈을 여러 개 사용하는 거라고 생각했다. 하지만 사용할 때마다 빌드가 되지 않는 상황이 발생하였다. 결과적으로는 "되긴 되게" 만들어 놓고 넘어갔다. 즉, 해결은 했지만 이해하지는 못한 상태였다. 멀티모듈을 사용하게 되면 중복제거를 하면 좋다는 정도만 알았고, 반대로 멀티 모듈을 도입하면서 발생하는 빌드 복잡도 증가, 의존성 관리 비용, 실행 구조의 제약과 같은 손해(트레이드오프)에 대해서는 전혀 고려하지 않았던거 같다. 지금 돌이켜보면, 문제의 원인은 멀티 모듈 자체가 아니라구조를 이해하지 않은 상태에서 구조를 도입하려 했다는 점에 있었다.멀티모듈을 왜 사용하는가?멀티모듈을 왜 사용할까요? 사용하지 않으면 다음과 같이 처리 할 수 있습니다. ..
머지 방법에는 크게 세 가지가 존재합니다. 스퀴시 머지(Squash Merge), 머지 커밋(Merge Commit), 그리고 리베이스 앤 머지(Rebase and Merge)입니다.Git을 사용하는 대부분의 팀은 이 세 가지 중 하나, 혹은 조합된 전략을 선택해 사용하고 있습니다.흥미로운 점은, 어느 전략이 "정답"이라고 합의된 경우는 거의 없다는 것입니다.같은 Git을 사용하고, 같은 기능을 개발하더라도 회사나 팀에 따라 머지 전략은 확연히 달라집니다.그렇다면 왜 이런 차이가 발생하는 걸까요?이는 단순히 Git 사용 취향의 문제가 아니라,팀의 협업 방식, 배포 주기, 장애 대응 전략, 그리고 변경 이력을 바라보는 관점이 다르기 때문입니다.이 글에서는 세 가지 머지 방식이 각각 어떤 특징을 가지는지 살..
카드에는 크게 두 가지 결제 방식이 있습니다. 체크 카드와 신용 카드입니다. 결제 과정은 서로 다르지만, 사용자가 기대하는 결과는 같습니다. 같은 10,000원짜리 상품을 구매했다면, 체크 카드로 결제했든 신용 카드로 결제했든 결제가 '성공했다'는 최종 상태는 동일해야 합니다.결제 방식이 다르다는 사실은 사용자에게 중요한 정보가 아닙니다. 중요한 것은 성공 여부와 그 결과가 일관되게 유지되는가입니다. 이처럼 서로 다른 처리 과정을 거치더라도 하나의 동일한 결과로 수렴해야 하는 문제를 시스템에서는 정합성(consistency) 이라고 부릅니다. 이 정합성 문제를 다루는 가장 단순한 접근은 클라이언트가 서버에 상태를 반복적으로 요청하는 HTTP 요청 기반의 폴링 방식입니다. 현재 상태를 계속 확인함으로써 결..
Lombok을 활용하면 빌더 패턴을 보일러플레이트 코드 없이 간단하게 구현할 수 있습니다.그러다 보니 문득 이런 생각이 들었습니다."이 패턴은 왜 사용하는 걸까?"파라미터를 유연하게 조절할 수 있다는 점은 알고 있었지만,빌더 패턴의 본질이 단순히 편의성에만 있는 것은 아닐 것 같았습니다.과연 이렇게 이해하는 것만으로 충분한 걸까요?왜 탄생하게 되었을까?빌더 패턴은 왜 탄생했을까요? 초창기 객체는 파라미터가 간단했다고 합니다. 필드 몇개 생성자 하나...class A { int a; int b; public A(int a, int b) { this.a = a; this.b = b; }}하지만 필드는 증가하고 필수 값과 선택 값들이 혼재되기 시작했습니다. 어떨때는 생성 조건이 도메인마다..
HTTPS로 서비스를 띄우기 위해서는 웹 서버가 필요하다. 내가 사용하고 있는 개발 도구는 Spring Boot이고, 내장 웹 서버로 Tomcat을 사용하고 있다. Tomcat과 Spring Boot는 자체적으로 HTTPS를 지원하지만, 실무 환경에서는 애플리케이션이 직접 HTTPS를 처리하는 방식보다는 프록시 웹 서버를 앞단에 두는 구조가 더 일반적으로 사용된다고 한다. 이는 Spring Boot가 HTTPS를 지원하지 못해서가 아니라, TLS(암호화) 처리를 애플리케이션에서 분리하여 인증서 관리, 성능 부담, 운영 복잡도를 줄이기 위함이다. 그렇다면 프록시 웹 서버는 어떻게 HTTPS 요청을 받아서 내부의 Spring Boot 애플리케이션과 통신하게 될까? 이 글에서는 프록시 웹 서버가 HTTPS 연..
종종 이런 생각을 합니다.디자인 패턴은 왜 학습해야 할까? 그리고 왜 학습했음에도 프로젝트에 바로 적용하지 못할까?디자인 패턴은 문제를 단순화하여 해결을 돕는 도구처럼 보이지만, 본질적으로는 새로운 해결책을 만들어내기 위한 수단이 아닙니다. 디자인 패턴은 소프트웨어 개발 과정에서 반복적으로 발생했던 설계 문제를, 검증된 구조로 다시 풀기 위한 가이드라인에 가깝습니다. 그렇기 때문에 패턴을 학습했음에도 실제 프로젝트에서 곧바로 적용하지 못하는 상황은 자연스럽습니다. 이는 패턴이 아직 필요할 만큼 구조적 문제가 명확하게 드러나지 않았기 때문이며, 비정상적인 현상이 아닙니다. 디자인 패턴의 목적은 코드를 단순하게 만드는 데 있지 않습니다. 오히려 결합도를 낮추고 책임을 분리함으로써, 변경과 확장이 발생하더라도..
분산 환경에서 요청은 한 번만 도착한다는 보장은 없습니다.네트워크 지연, 타임아웃, 클라이언트 재시도 등으로 동일한 요청은 언제든지 여러 번 전달될 수 있습니다. 이때 자주 등장하는 개념이 바로 멱등성입니다. 멱등성은 흔히 "여러 번 동작해도 같은 결과를 반환하는 성질"이라고 설명됩니다. 하지만 이 정의만으로는 멱등성과 재시도의 차이를 제대로 이해하기 어렵습니다. 재시도 역시 요청을 여러 번 수행한다는 점에서는 멱등성과 매우 유사해 보이기 때문입니다. 그럼에도 불구하고 두 개념은 결과와 책임의 관점에서 명확히 다릅니다. 재시도는 실패 가능성을 전제로 다시 요청을 보내는 행위인 반면, 멱등성은 중복 요청이 발생하더라도 시스템의 상태를 안전하게 유지하는 설계적 성질에 가깝습니다. 이 차이를 구분하지 못하면,..