{"title":"해피 패스를 넘어서는 엔지니어링: 시간","url":"https://hugo-blog-static-site.haxlys.workers.dev/posts/beyond-happy-path-engineering-time/","section":"posts","date":"2026-07-23T21:06:49+09:00","lastmod":"2026-07-23T21:06:49+09:00","description":"시계가 항상 정확하고 앞으로만 흐른다는 가정이 무너질 때를 대비해, 시간의 종류를 구분하고 만료·스케줄링·비즈니스 경계를 안전하게 설계하는 방법을 정리한다.","summary":"시간은 프로그램에서 가장 익숙한 의존성처럼 보인다. now()를 호출하고 두 시각을 비교하면 예약이 만료됐는지, 세션이 유효한지, 재시도할 때가 됐는지를 알 수 있다고 생각하기 쉽다. 그러나 운영 환경에서는 머신의 시계가 서로 다르고, 시스템 시각이 보정으로 앞뒤로 움직이며, 스케줄러가 늦거나 중복 실행된다. 시간은 단순한 값이 아니라 설계해야 할 경계다.\n3줄 핵심 정리 시간의 종류를 나눠야 한다: 경과 시간은 monotonic clock, 사건 기록은 wall-clock, 경쟁 순서는 버전·시퀀스, 사용자 업무 규칙은 시간대와 달력으로 다룬다. 만료 시각은 판결문이 아니다: clock skew와 늦은 worker 때문에 timestamp 비교만으로 예약·세션의 상태를 바꾸면 경쟁 조건이 발생한다. 경계 조건을 명시한다: 늦은 작업, 중복 실행, 자정·서머타임·월말 같은 상황을 테스트하고 재조정·관찰 가능성까지 설계한다. 원문: Beyond Happy Path Engineering: Time\n","content":"시간은 프로그램에서 가장 익숙한 의존성처럼 보인다. now()를 호출하고 두 시각을 비교하면 예약이 만료됐는지, 세션이 유효한지, 재시도할 때가 됐는지를 알 수 있다고 생각하기 쉽다. 그러나 운영 환경에서는 머신의 시계가 서로 다르고, 시스템 시각이 보정으로 앞뒤로 움직이며, 스케줄러가 늦거나 중복 실행된다. 시간은 단순한 값이 아니라 설계해야 할 경계다.\n3줄 핵심 정리 시간의 종류를 나눠야 한다: 경과 시간은 monotonic clock, 사건 기록은 wall-clock, 경쟁 순서는 버전·시퀀스, 사용자 업무 규칙은 시간대와 달력으로 다룬다. 만료 시각은 판결문이 아니다: clock skew와 늦은 worker 때문에 timestamp 비교만으로 예약·세션의 상태를 바꾸면 경쟁 조건이 발생한다. 경계 조건을 명시한다: 늦은 작업, 중복 실행, 자정·서머타임·월말 같은 상황을 테스트하고 재조정·관찰 가능성까지 설계한다. 원문: Beyond Happy Path Engineering: Time\n시간은 하나의 도구가 아니다 먼저 코드가 묻는 질문을 분리해야 한다.\n얼마나 오래 걸렸는가? → monotonic time 사용자가 언제 행동했는가? → wall-clock time 어떤 변경이 먼저인가? → logical time 또는 버전·시퀀스 어느 업무일에 속하는가? → business time 경과 시간을 재는 timeout과 사용자에게 표시할 발생 시각은 같은 시간 타입으로 처리하면 안 된다. 전자는 시계가 조정돼도 앞으로만 흐르는 성질이 중요하고, 후자는 사람이 이해할 수 있는 달력과 시간대가 중요하다.\nclock skew는 정상적인 운영 조건이다 예약이 10시 15분에 만료된다고 하자. 결제 요청을 처리하는 서버의 시계는 10시 14분이라고 보지만, 만료 worker의 시계는 2분 빠른 10시 16분일 수 있다. 두 서버가 같은 데이터를 읽어도 서로 다른 결론을 낸다.\n시간 동기화는 오차를 줄이지만 정확성의 증명이 아니다. 따라서 만료 worker는 timestamp를 후보를 찾는 필터로 사용하고, 실제 상태 변경 때는 다음 조건을 원자적으로 확인해야 한다.\n아직 예약 상태인가? 결제나 완료 처리가 이미 반영되지 않았는가? 이 상태 전이가 현재 시점에 허용되는가? 경계에 가까운 경우에는 grace period, pending 상태, 재조정 작업을 두는 편이 안전하다.\n타임스탬프는 순서를 보장하지 않는다 분산 환경에서 두 이벤트의 timestamp가 어느 쪽이 먼저인지 알려주지 못하는 경우가 많다. 네트워크 지연, 서로 다른 시계, 재시도 때문에 실제 인과관계와 기록 시각의 순서가 달라질 수 있기 때문이다.\n경쟁하는 갱신의 승자를 정해야 한다면 버전 번호, 데이터베이스 transaction, stream sequence, 명시적인 상태 머신을 사용해야 한다. timestamp는 감사 로그와 관찰을 위한 유용한 정보지만, 단독으로 “이 이벤트가 반드시 먼저 발생했다”는 증거가 되어서는 안 된다.\n스케줄러는 제때 한 번만 실행되지 않는다 백그라운드 작업은 늦게 실행되거나, 장애 복구 후 밀린 작업을 한꺼번에 실행하거나, 중복 실행될 수 있다. 따라서 “정각에 한 번 실행된다”는 가정 대신 다음 성질을 설계해야 한다.\n작업을 여러 번 실행해도 결과가 안전한가? 늦게 도착한 작업이 최신 상태를 덮어쓰지 않는가? 놓친 구간을 재처리할 수 있는가? 작업이 현재 상태를 다시 확인하는가? 이 관점에서 expiration job은 과거에 만들어진 결정을 무조건 실행하는 명령이 아니라, 현재 상태를 확인한 뒤 가능한 상태 전이를 시도하는 worker가 된다.\n비즈니스 시간은 timestamp보다 풍부하다 “오늘”, “자정”, “영업일”, “청구 월”, “배송 마감”은 UTC timestamp 하나로 충분히 표현되지 않는다. 사용자의 시간대, 매장의 위치, 서머타임, 공휴일, 월말 규칙이 함께 필요하다.\n저장할 때는 명확한 instant와 시간대 정보를 보존하고, 화면이나 업무 판단에 사용할 때는 도메인의 달력 규칙을 적용해야 한다. 특히 서머타임 전환일에는 존재하지 않는 시각이나 두 번 나타나는 시각이 생길 수 있으므로 경계 입력을 별도로 테스트해야 한다.\n실무 설계 원칙 시간 관련 코드는 다음 질문을 명시하면 훨씬 견고해진다.\n이 값은 instant인가, duration인가, calendar rule인가? 이 비교가 clock skew를 견딜 수 있는가? 늦거나 중복 실행돼도 같은 결과를 만드는가? 상태 변경은 버전 또는 조건부 갱신으로 보호되는가? 운영자가 시계 이상, 지연, 재조정을 관찰할 수 있는가? 테스트도 정상적인 시간 흐름만 확인해서는 부족하다. 시계가 뒤로 움직이는 경우, worker가 늦게 실행되는 경우, 경계 시각에 두 작업이 경쟁하는 경우, 시간대와 서머타임이 바뀌는 경우를 가짜 시계와 통제된 스케줄러로 재현해야 한다.\n결론 및 시사점 한 줄 결론: 시간은 단순한 timestamp가 아니라 경과 측정·사건 기록·순서 결정·비즈니스 규칙을 나눠 다뤄야 하는 시스템 경계다.\n","wordCount":556,"tags":["분산 시스템","신뢰성","소프트웨어 아키텍처","시간","엔지니어링"],"categories":["소프트웨어 엔지니어링"],"frameworks":["Beyond Happy Path Engineering"],"mental_models":["경계 설계","명시적 상태"],"philosophy_type":"software-reliability","schema_type":"TechArticle","actionable":true,"priority":"high","key_points":["경과 시간 측정에는 단조 시계를, 사용자에게 보여줄 사건 시각에는 wall-clock을 사용해야 한다","타임스탬프는 인과관계나 승자 판정의 증거가 아니므로 버전·시퀀스·트랜잭션과 구분해야 한다","만료 작업은 시각 비교만으로 상태를 바꾸지 말고 현재 상태·경쟁 갱신·재조정 경로를 함께 확인해야 한다","시간대·서머타임·영업일·청구 주기는 원시 timestamp가 아닌 비즈니스 규칙으로 모델링해야 한다"],"related":[]}