삼태연구소
SAMTAELABS삼태연구소
인사이트2026년 10월 24일·11분 읽기

UUIDv7을 데이터베이스 키와 외부 ID에 함께 써도 될까: 시간 정렬과 비밀성을 분리하는 기준

UUIDv7데이터베이스설계시스템아키텍처
UUIDv7을 데이터베이스 키와 외부 ID에 함께 써도 될까: 시간 정렬과 비밀성을 분리하는 기준
목차(5)

새 서비스의 기본 키를 정할 때 UUIDv7은 매력적으로 보인다. 분산 환경에서도 충돌 가능성이 매우 낮고, UUIDv4보다 삽입 순서가 시간 흐름에 가까워 보이며, ID 안에서 시간 정보를 꺼낼 수도 있기 때문이다. 그래서 데이터베이스 기본 키와 고객에게 보여 주는 주문 번호, 파일 URL의 식별자까지 하나로 통일하려는 설계가 나온다.

하지만 UUIDv7의 시간 필드는 “정확한 생성 시각”, “언제나 증가하는 순번”, “추측하기 어려운 공개 토큰”을 동시에 뜻하지 않는다. 요구사항 단계에서 이 세 가지를 한 요구로 묶으면, 나중에 정렬 오류와 감사 데이터 혼동, 정보 노출 문제를 같은 ID로 해결하려다 설계가 꼬일 수 있다.

핵심은 간단하다. UUIDv7은 고유 식별자로 검토할 수 있다. 다만 시간 정렬의 범위, 업무상 생성 시각, 외부에 드러나도 되는 정보는 각각 따로 결정해야 한다.

오해: UUIDv7이면 생성 순서가 항상 정확히 보장된다

UUIDv7에는 밀리초 단위의 Unix 시간 기반 필드가 들어간다. PostgreSQL의 uuidv7()도 밀리초 시간값에 서브밀리초 정보와 난수를 조합해 시간 순서형 UUID를 생성한다. 같은 방식은 시간대별 조회나 최근 생성 데이터 정렬에 유용할 수 있다.

그러나 “시간 순서형”과 “모든 상황에서 엄격히 증가하는 값”은 다르다.

UUIDv7 표준은 생성기가 단조성을 유지하기 위해 실제 시계에서 읽은 값과 다른 타임스탬프를 사용할 수 있도록 허용한다. 같은 밀리초 안에서 많은 ID를 만들거나 시스템 시계가 뒤로 이동했을 때, 생성기는 이전 UUID보다 작아지지 않도록 타임스탬프나 카운터를 조정할 수 있다. 이때 UUID에 담긴 시간은 벽시계의 현재 시각보다 앞설 수 있다.

특히 여러 애플리케이션 인스턴스, 여러 리전, 클라이언트 생성 ID가 섞이는 서비스라면 “전역 생성 순서”를 UUID 비교만으로 판정하면 안 된다. 각 생성기는 자신의 시계와 구현 방식에 따라 UUID를 만들기 때문이다.

적용 결론: 목록 화면에서 “대체로 최근 생성 순으로 읽히는 키”가 필요하다면 UUIDv7을 후보로 둘 수 있다. 반면 결제 접수 순서, 이벤트 처리 순서, 승인 우선순위처럼 한 건의 앞뒤가 업무 결과를 바꾸는 경우에는 별도 순서 기준을 요구사항에 적어야 한다. 그 기준은 데이터베이스가 기록한 시각, 트랜잭션 순번, 메시지 브로커 오프셋처럼 해당 업무 경계에서 정한 값이어야 한다.

오해: UUID에서 시간을 추출하면 생성일 컬럼을 없앨 수 있다

UUIDv7에서 시간을 꺼낼 수 있다는 점은 검색과 운영에 편리해 보인다. PostgreSQL은 UUIDv7 생성과 타임스탬프 추출 기능을 제공한다. 그러나 UUID에 담긴 시각이 객체가 생성된 정확한 시각과 같다고 보기는 어렵다.

표준은 타임스탬프가 실제 시간에 얼마나 가까워야 하는지 보장하지 않는다. 단조 증가를 우선한 구현은 시계가 뒤로 갔을 때 이전 값보다 큰 시간을 넣을 수 있고, 카운터가 소진되는 상황에서는 시간값을 앞으로 진행시킬 수도 있다. 생성 라이브러리와 버전, 시계 이상 상황에 따라 차이의 양상도 달라질 수 있다.

여기서 구분해야 할 것은 두 시간이다.

  • 식별자에 포함된 시간: ID가 만들어질 때 생성기가 사용한 시간 관련 값이다.
  • 업무상 생성 시각: 주문이 접수됐는지, 사용자가 가입을 완료했는지, 데이터베이스에 커밋됐는지 등 서비스가 정의한 사건의 시각이다.

두 값이 일치해야 하는 서비스라면 UUIDv7만으로 증명할 수 없다. 예를 들어 감사 로그, 정산 기준, 계약상 효력 발생 시점, 장애 분석의 사건 순서는 별도 created_at 또는 업무 이벤트 시각을 저장하는 편이 안전하다. 그 컬럼에는 시간 기준이 애플리케이션 시계인지, 데이터베이스 기록 시각인지도 함께 정해야 한다.

조기 신호: 기획서에 “UUID 시간으로 생성일을 대체한다”, “ID만 있으면 생성 시각을 신뢰할 수 있다”는 문장이 있다면 시간 필드의 용도를 다시 나눠야 한다. 조회 편의를 위한 시간인지, 감사와 정산의 기준인지가 빠져 있을 가능성이 크다.

오해: UUIDv7은 랜덤해 보이므로 외부 공개에도 안전하다

UUIDv7은 고유성에 초점을 둔 식별자다. 권한 검증이나 비밀 토큰의 역할까지 자동으로 맡지는 않는다. 더구나 UUIDv7에는 시간 관련 정보가 포함되므로, 외부에 ID를 노출하면 발급 시점에 관한 단서를 함께 공개하게 된다.

이 정보가 문제가 되는지는 서비스마다 다르다. 공개 게시물처럼 생성 시점이 원래 드러나는 객체라면 영향이 작을 수 있다. 반대로 비공개 사용자 등록, 내부 운영 기록, 파트너 거래 생성 시점처럼 활동량이나 시점을 감추고 싶은 객체라면 공개 ID의 시간 누출을 검토해야 한다.

더 중요한 원칙은 ID의 추측 난이도와 접근 권한을 분리하는 것이다. URL의 식별자가 충분히 무작위처럼 보여도, 서버는 요청자가 해당 객체를 읽거나 수정할 권한이 있는지 확인해야 한다. UUIDv7인지 UUIDv4인지와 무관하게, 식별자 자체를 권한 확인 수단으로 취급하면 안 된다.

적용 결론: 외부 ID가 시간 정보를 드러내면 안 되는 서비스라면 UUIDv7을 그대로 URL, 초대 링크, 다운로드 링크, 고객 공유 코드로 쓰기보다 별도 공개 식별자를 둔다. 공개 식별자는 내부 기본 키와 매핑하고, 권한이 필요한 요청은 인증과 객체별 인가를 통과한 뒤 처리한다.

키 하나로 해결하려 할 때 생기는 요구사항 누락

CTO나 개발 리드가 요구사항을 검토할 때는 “UUIDv7을 쓸까?”보다 아래 질문에 먼저 답하는 편이 낫다.

  1. 정렬은 어디서 필요한가?
    인덱스 삽입과 최근 목록 조회를 위한 대략적 시간 정렬인지, 업무상 엄격한 선후 관계인지 구분한다. 후자라면 UUID 비교 외의 순서 필드를 정한다.

  2. 생성 시각은 무엇을 뜻하는가?
    API 요청 수신, 데이터베이스 저장, 승인 완료, 외부 시스템 응답 중 어느 시점을 기록할지 정한다. UUID 시간은 보조 정보로 쓸 수 있어도 이 정의를 대신하지는 않는다.

  3. ID가 외부로 나가는가?
    브라우저 URL, 이메일, 영수증, API 응답, 로그, 고객 지원 화면에 노출되는지 확인한다. 노출된다면 시간 정보 공개가 허용되는지, 별도 공개 ID가 필요한지 결정한다.

  4. 생성기는 하나인가, 여러 개인가?
    단일 데이터베이스 함수에서만 생성하는지, 여러 서버와 클라이언트가 각각 생성하는지에 따라 정렬 기대치를 낮추거나 검증 방식을 추가해야 한다.

이 질문은 구현 세부사항이 아니라 데이터 계약에 가깝다. ID를 저장한 뒤에는 URL, 로그, 외부 연동, 데이터 웨어하우스까지 퍼지므로 나중에 교체하기 어렵다.

권장안: 내부 식별, 시간 기록, 외부 식별자를 역할별로 둔다

대부분의 서비스는 다음처럼 역할을 나누면 논의가 명확해진다.

  • 내부 기본 키: 분산 생성과 인덱스 정렬 특성이 필요하다면 UUIDv7을 검토한다.
  • 업무 시각: created_at, 승인 시각, 상태 변경 시각처럼 서비스가 정의한 이벤트 시간을 별도 컬럼으로 기록한다.
  • 엄격한 순서: 순번, 커밋 순서, 이벤트 위치 등 업무 경계에서 보장할 수 있는 기준을 사용한다.
  • 외부 노출 ID: 시간 노출 여부와 사용자 경험을 따져 UUIDv7 재사용 또는 별도 공개 식별자를 선택한다.
  • 접근 제어: 어떤 ID를 쓰든 객체 단위 인가를 서버에서 수행한다.

이렇게 나눈다고 모든 테이블에 식별자가 여러 개 필요한 것은 아니다. 공개 노출이 없고, 생성 시각을 감사 기준으로 쓰지 않으며, 엄격한 순서도 요구하지 않는 내부 데이터라면 UUIDv7 하나로 충분할 수 있다. 반대로 주문, 결제, 권한 부여, 민감한 사용자 활동처럼 시간과 공개 범위가 중요한 도메인은 처음부터 분리 비용을 감수할 이유가 있다.

다음 요구사항 검토 회의에서는 UUID 버전부터 고르지 말고, 먼저 한 객체를 골라 보자. “이 레코드는 언제 생성됐다고 말할 것인가”, “두 레코드의 앞뒤가 바뀌면 어떤 업무가 틀어지는가”, “이 값이 URL에 보여도 되는가”를 답한 뒤에야 UUIDv7이 키로 적합한지 판단할 수 있다.

자주 묻는 질문

Q.PostgreSQL의 `uuidv7()`을 쓰면 생성 시각 컬럼을 생략해도 되나요?

최근 데이터 정렬을 보조하는 용도라면 활용할 수 있습니다. 다만 UUIDv7의 시간값은 업무상 생성 시각과 정확히 같아야 한다는 보장이 없으므로, 감사·정산·상태 변경 기준에는 별도 시각 컬럼을 두는 편이 적절합니다.

Q.UUIDv7을 외부 API의 리소스 ID로 반환해도 되나요?

생성 시점 정보가 드러나도 되는지와 객체별 인가가 있는지를 먼저 확인해야 합니다. 시간 노출이 부담스럽거나 공유 링크 성격의 값이라면 내부 키와 별도 공개 식별자를 분리하는 선택지를 검토할 수 있습니다.

Q.UUIDv7은 UUIDv4보다 항상 더 좋은 기본 키인가요?

아닙니다. UUIDv7은 시간에 가까운 정렬이 필요한 경우 장점이 있을 수 있지만, 정확한 시간 기록이나 전역 순서, 비밀성까지 요구하면 추가 설계가 필요합니다. 요구사항에 없는 특성까지 얻기 위해 선택할 이유는 없습니다.

이 글이 도움됐다면, 비슷한 외주 프로젝트 무료 상담을 받아보세요

누적 매출 20억 / 1인 에이전시. 중간 과정 없이 의도 그대로.

관련 아티클

관련 사례

이 글의 키워드와 맞닿은 실제 개발 사례를 함께 보세요.