시간순 정렬이 가능한 UUID v7: 개념, 장점 및 데이터베이스 ID 전략

개념 UUID v7은 시간순으로 정렬 가능한(time-sortable) UUID임. 전체 128비트 길이와 표준 36자 형식(8-4-4-4-12)은 다른 버전과 동일하지만, 식별자 앞부분에 타임스탬프를 포함하는 것이 핵심적인 차이점임 UUID v7의 구조 UUID의 13번째 문자는 버전을 나타냄 UUID v4: 버전을 제외한 모든 비트가 무작위 값으로 채워짐 UUID v7: 앞 48비트가 Unix epoch 타임스탬프(밀리초 단위)로 구성되며, 그 뒤로 버전(7)과 나머지 랜덤 비트가 이어짐 구조를 분해하면 다음과 같음 0192f0c1-2345-7abc-89de-1234567890ab 0192f0c12345: 48비트 타임스탬프 7: 버전 7 abc: 랜덤 비트 8: Variant (RFC 4122 호환) 9de-1234567890ab: 나머지 랜덤 비트 이 구조 덕분에 UUID v7은 생성 순서와 정렬 순서가 거의 일치함. 나중에 생성된 ID가 사전순(lexicographical)으로 더 크므로, ID로 정렬하면 사실상 생성 시간순으로 정렬하는 효과를 냄 ...

May 31, 2026

1억 건 테이블 쿼리 최적화: 원인 진단과 실용적 해결 전략

배경 ‘테이블에 1억 건 데이터가 있으면 조회가 느리다’는 말은 항상 참이 아님. 성능 저하는 데이터 양 자체보다 ‘어떤 쿼리를 어떤 인덱스로 어떻게 읽는가’의 문제일 때가 많음. 이 글에서는 대용량 테이블에서 조회 성능이 저하되는 주된 원인을 진단하고, 각 상황에 맞는 해결 전략을 정리함. 느린 쿼리의 대표적인 원인 대용량 테이블에서 쿼리가 느리다면 대부분 아래 원인 중 하나에 해당함. Full Table Scan: WHERE나 ORDER BY 절이 인덱스를 효과적으로 사용하지 못해 테이블 전체를 스캔 낮은 인덱스 선택도: 인덱스를 사용하더라도, status='ACTIVE'처럼 대부분의 행이 해당하는 조건이라 읽어야 할 데이터가 너무 많음 메모리 외부 정렬/그룹화: 정렬이나 그룹화할 데이터가 메모리 용량을 초과해 디스크 I/O가 발생하는 경우 (External Sort, Hash Aggregate) 비효율적인 조인: 조인 순서가 잘못되었거나 조인 키에 인덱스가 없어 비효율적으로 동작 OFFSET 기반 페이지네이션: OFFSET 1000000처럼 앞부분의 데이터를 모두 읽고 버리는 비효율적인 방식 기타 운영 이슈: 특정 행에 대한 동시 접근으로 인한 핫스팟(Hotspot), 잠금(Lock) 경합, 디스크 IOPS 부족, 캐시 미스 등 결론적으로 1억 건 테이블이라도 쿼리가 인덱스를 통해 소수의 행만 접근(Index Seek)하고 짧은 범위만 스캔(Range Scan)한다면 응답 속도는 충분히 빠를 수 있음. ...

May 28, 2026

Soft Delete와 Hard Delete: 장단점과 올바른 선택 기준

개념: Soft Delete와 Hard Delete Soft Delete는 데이터를 물리적으로 삭제하지 않고, 삭제된 것처럼 ‘표시’만 하는 전략입니다. DELETE 쿼리로 데이터를 완전히 제거하는 물리 삭제(Hard Delete)와 달리, Soft Delete는 UPDATE 쿼리를 사용해 특정 컬럼 값을 변경합니다. deleted_at - 데이터가 삭제된 시점을 타임스탬프로 기록 is_deleted - 삭제 여부를 나타내는 플래그(flag)를 true로 변경 요약하면 Soft Delete는 ‘삭제’를 데이터 파괴(Destroy)가 아닌 상태 변경(State Change)으로 다루는 접근법이며, Hard Delete는 데이터를 영구적으로 제거하는 것을 목적으로 합니다. Soft Delete의 장점과 목적 Soft Delete를 도입하면 다음과 같은 이점을 얻을 수 있습니다. ...

May 27, 2026

DynamoDB UpdateItem의 원자성은 어떻게 보장되는가

들어가며 DynamoDB의 UpdateItem API는 카운터 증감이나 상태 변경 같은 ‘읽기-수정-쓰기’ 연산을 단 한 번의 호출로 실행합니다. 이때 여러 클라이언트가 동시에 같은 아이템을 수정하려 할 때, 어떻게 데이터의 정합성과 원자성(Atomicity)을 보장하는지 그 내부 동작 원리가 궁금할 수 있습니다. 결론부터 말하면, 특정 아이템이 속한 파티션의 모든 쓰기 요청을 단일 리더(Leader) 노드가 직렬화(Serialize)하여 순차 처리하기 때문입니다. 이 구조 덕분에 동시 쓰기 요청이 서로를 덮어쓰거나 유실되지 않고 안전하게 처리됩니다. 핵심 원리 요약 개념 설명 단일 파티션 리더 특정 파티션 키에 대한 모든 쓰기 요청은 하나의 리더 노드가 전담함 쓰기 요청 직렬화 리더는 수신한 쓰기 요청을 큐처럼 순서대로 하나씩 처리함 동기식 복제 리더는 변경 사항을 다수의 팔로워 노드에 복제하고, 확인 응답을 받아야 최종 커밋함 원자성 보장 이 세 가지 요소가 결합되어 UpdateItem 연산의 원자성을 보장함 원자성 보장 메커니즘 상세 1. 파티션 기반 분산과 리더 선정 DynamoDB 테이블 데이터는 파티션 키(Partition Key)를 기준으로 여러 파티션에 분산 저장됨 각 파티션은 3개의 복제본(Replica)으로 구성되며, 서로 다른 가용 영역(AZ)에 배치됨 복제본 중 하나는 리더(Leader)가 되고 나머지는 팔로워(Follower) 역할을 수행함 2. 단일 리더로의 쓰기 요청 라우팅 클라이언트의 UpdateItem 요청은 먼저 DynamoDB 요청 라우터(Request Router)에 도달함 요청 라우터는 아이템의 파티션 키를 해싱하여 데이터가 속한 파티션을 찾고, 해당 파티션의 단일 리더 노드로 요청을 전달함 따라서 같은 아이템에 대한 모든 쓰기 요청은 항상 동일한 리더 노드가 처리하게 됨 3. 리더 노드의 쓰기 요청 직렬화 원자성 보장의 핵심으로, 리더 노드는 동시에 도착한 여러 쓰기 요청을 병렬로 처리하지 않음 대신, 요청을 내부적으로 큐에 넣고 하나씩 순서대로(Serially) 처리함. 이는 아이템에 대한 비관적 락(Pessimistic Lock)과 유사한 효과를 가져옴 예를 들어, 두 요청이 동시에 SET counter = counter + 1을 호출하면, 리더는 첫 번째 요청의 ‘읽기-수정-쓰기’ 사이클을 완전히 마친 후에야 두 번째 요청을 시작함 이로 인해 두 번째 요청은 첫 번째 요청이 변경한 최신 값을 읽게 되어 데이터 정합성이 깨지지 않음 4. 동기 복제를 통한 내구성 확보 리더는 쓰기 연산을 완료한 후, 변경 사항을 팔로워 노드들에게 **동기적으로 복제(Synchronous Replication)**함 리더는 쿼럼(Quorum), 즉 과반수 이상의 복제본에 데이터가 성공적으로 기록되었음을 확인한 뒤에 클라이언트에게 성공(200 OK) 응답을 보냄 이 과정 덕분에 UpdateItem 연산은 ‘전부 성공’하거나 ‘전부 실패’하는 원자적 특성을 가지며, 리더 장애 시에도 데이터 유실을 방지함 동시 업데이트 처리 과정 예시 두 클라이언트(A, B)가 counter 값이 0인 아이템에 SET counter = counter + 1 요청을 동시에 보냈다고 가정해 보겠습니다. ...

May 26, 2026

Foreign Key 컬럼은 NULL을 가질 수 있는가

개념/배경 결론부터 말하면 가능함 Foreign Key 컬럼의 NULL 허용 여부는 해당 컬럼의 NULL 혹은 NOT NULL 제약으로 결정됨 FK 제약은 NULL이 아닌 값만 검증하므로 값이 NULL인 경우 참조 무결성 검사를 생략함 컬럼이 NOT NULL이면 NULL 저장 불가 컬럼이 NULL 허용이면 NULL 저장 가능 핵심 개념 FK 제약은 입력값이 존재할 때만 참조 대상 테이블에 키가 있는지 검사 NULL은 미지정 상태로 간주되어 검증 대상 아님 대부분의 RDBMS에서 기본은 NULL 허용 컬럼이며, 명시적으로 NOT NULL을 지정해야 함 동작 원리 INSERT 또는 UPDATE 시 FK 컬럼이 NULL이면 무결성 검사 스킵 값이 존재하면 참조 테이블의 대상 키 존재 여부 검사 복합 FK의 경우 참조 컬럼 중 하나라도 NULL이면 전체 FK 검사를 생략하는 동작이 일반적임 간단 예시 CREATE TABLE parent ( id INT PRIMARY KEY ); CREATE TABLE child ( parent_id INT, FOREIGN KEY (parent_id) REFERENCES parent(id) ); child.parent_id는 기본적으로 NULL 허용 상태 parent_id에 NULL을 넣어도 FK 위반 아님 parent_id에 NOT NULL을 추가하면 parent에 존재하는 id만 허용됨 주의 사항 ON DELETE SET NULL을 사용할 경우 FK 컬럼이 NULL 허용이어야 함 FK 컬럼 인덱스는 필수는 아니나 삭제·업데이트 시 성능과 잠금 범위 측면에서 권장 관계가 필수라면 애플리케이션 규칙이 아닌 스키마에서 NOT NULL로 모델링하는 편이 안전함 마무리 FK 컬럼은 NULL 허용 설정이면 NULL을 저장할 수 있으며, 이 경우 FK 검사는 수행되지 않음 NULL을 금지하고 참조 무결성을 강제하려면 NOT NULL을 함께 적용하면 됨 ...

February 19, 2026

CTE(Common Table Expression) 개념과 WITH 사용법 요약

개념/배경 CTE는 WITH로 정의하는 이름 있는 임시 결과셋 바로 다음 한 개 DML에서만 참조 가능하며 실행이 끝나면 범위 소멸 가독성 향상과 쿼리 구조 분리에 유용함 사용법 WITH cte_name (col1, col2) AS ( SELECT ... ) DML필요 시 컬럼 목록 생략 가능하나 명시를 권장 주의사항 일부 DBMS에서는 배치 내에서 CTE 앞에 오는 쿼리를 세미콜론으로 종료 필요 CTE 범위는 바로 뒤 한 개 DML로 한정됨 복잡한 쿼리에서 과도한 중첩 사용은 계획 복잡도 증가 가능 참고자료 https://s2choco.tistory.com/34 https://learn.microsoft.com/sql/t-sql/queries/with-common-table-expression-transact-sql

February 16, 2026

TypeORM JOIN vs 코드 레벨 매핑 선택 기준과 실무 트레이드오프

개요 동일한 문제를 TypeORM의 JOIN으로 해결할 수도 있고, 각 테이블을 개별 조회한 뒤 코드에서 매핑할 수도 있음 어떤 접근이 더 효율적인지는 데이터 크기, 관계 복잡도, 인덱스 상태, 네트워크 제약, 성능 요구사항에 따라 달라짐 핵심 장단점과 선택 기준을 정리함 TypeORM에서 JOIN 사용하는 경우 장점 단일 쿼리로 필요한 데이터 수집 가능, 왕복 횟수 감소로 지연시간 이점 DB가 JOIN과 실행계획을 최적화하는 경우 비용 최소화 기대 1:N, N:M 같은 관계 질의를 쿼리로 명시적으로 표현 가능 필터링, 정렬, 그룹화 등 집계성 처리에서 DB 연산 활용 용이 페이지네이션과 함께 일관된 결과를 만들기 수월함 단점 ...

January 30, 2026

데이터베이스 Collation 정렬·비교 규칙과 인코딩 차이

개요 Collation은 문자열 정렬과 비교 규칙 정의 테이블에 저장되는 바이트 표현은 인코딩(character set)으로 결정되며, 정렬과 비교 결과는 Collation으로 결정됨 둘은 목적과 적용 지점이 다름 핵심 개념 Character set은 문자 ↔ 바이트 매핑 Collation은 특정 문자 집합 위에서 정렬 순서와 동일성 판단 규칙 정의 대소문자 민감도(cs), 대소문자 비민감도(ci), 악센트 민감도(as), 악센트 비민감도(ai) 같은 속성으로 비교 엄격도 제어 언어별 규칙 존재하며 같은 인코딩이라도 Collation에 따라 결과 달라짐 동작 원리와 적용 범위 적용 레벨 계층 구조: 서버 기본값 → 데이터베이스 → 테이블 → 컬럼 → 표현식 단위 오버라이드 가능 영향 받는 연산: ORDER BY, GROUP BY, DISTINCT, LIKE, = 등 문자열 비교 연산 전반 인덱스는 해당 컬럼의 Collation 기준으로 구성되며 정렬 규칙이 범위 검색과 정렬 최적화에 직접 영향 다국어 환경에선 ICU 기반 Collation이나 언어 특화 Collation 선택 필요 ...

January 8, 2026

좋은 시스템 설계 가이드: 상태 최소화와 검증된 컴포넌트 조합의 원칙

개요 좋은 시스템 설계는 복잡해 보이지 않고 긴 시간 동안 별문제 없이 돌아가는 상태를 말함 핵심은 상태를 최소화하고, 검증된 단순한 컴포넌트를 필요할 때만 조합하는 방향 과설계나 과도한 신기술 도입은 문제를 감추거나 유지보수 비용을 키우는 경향 최소 기능의 단순 구조에서 시작 후 관측 기반으로 점진 개선 권장 시스템 설계의 정의와 접근 소프트웨어 설계가 코드 조립이라면 시스템 설계는 서비스를 조합하는 일이라는 관점 주요 구성 요소 팔레트 앱 서버 데이터베이스 캐시 큐와 잡 러너 이벤트 버스 프록시와 게이트웨이 좋은 설계의 징후 ...

January 1, 2026

VARCHAR(n) 길이 기준 정리 — 글자 수인가 바이트 수인가

개념/배경 VARCHAR(n)에서 n을 글자 수로 볼지 바이트 수로 볼지 혼동 많음 표준 SQL의 character varying(n)은 최대 글자 수 의미이나, 실제 구현은 DBMS와 문자셋 설정에 따라 달라짐 멀티바이트 문자셋에서는 저장 바이트 수와 글자 수가 다름. 길이 제한은 글자 수 기준일 수 있으나 내부 저장은 바이트 단위로 이뤄짐 DBMS별 동작 MySQL VARCHAR(n)에서 n은 글자 수 의미 utf8mb4 사용 시 글자 하나가 최대 4바이트까지 소요. 행 크기 제한 등으로 인해 저장 가능 여부는 바이트 한계에도 영향 받음 PostgreSQL character varying(n)에서 n은 글자 수 의미 저장은 바이트 단위이나 제약은 글자 수 기준으로 평가 SQL Server varchar(n)은 n이 바이트 수. 멀티바이트 문자 사용 시 같은 n이라도 담을 수 있는 글자 수 감소 nvarchar(n)은 n이 글자 수. 유니코드 2바이트 단위 저장. 글자 수 기준 제약 필요 시 nvarchar 사용 권장 Oracle VARCHAR2(n)은 기본이 바이트 기준. 세션/시스템에서 CHAR semantics 또는 컬럼 정의 시 VARCHAR2(n CHAR)로 명시하면 글자 수 기준 실무 팁 한글 100자, 영어 100자 모두 허용 기대라면 글자 수 기준 타입 필요 MySQL VARCHAR(100), PostgreSQL varchar(100), SQL Server에서는 nvarchar(100), Oracle에서는 VARCHAR2(100 CHAR) 선택 저장 바이트 한계 고려 필요. MySQL은 행 크기 한계, Oracle/SQL Server도 페이지 크기 등 제약 존재 길이 함수 차이 주의. 바이트 길이와 글자 길이 함수가 다른 경우 존재. 예를 들어 글자 길이 검증은 문자 길이 함수 사용 권장 이모지, 결합 문자 등 특수 유니코드 조합은 사용자 체감 글자 수와 코드 포인트 수가 다를 수 있음. 제품 요구사항에 맞는 길이 기준 정의 필요 정리 VARCHAR(n)이 항상 바이트 무관이라는 주장은 오해 많은 DBMS에서 n은 글자 수지만, SQL Server의 varchar처럼 바이트 기준인 구현 존재 문자셋과 저장 한계를 함께 고려해야 안정적인 길이 설계 가능 한글도 100글자, 영어도 100글자라는 기대를 보장하려면 글자 수 기준 타입과 설정을 명시적으로 선택할 것 참고자료 https://dev.mysql.com/doc/refman/8.0/en/char.html https://www.postgresql.org/docs/current/datatype-character.html https://learn.microsoft.com/sql/t-sql/data-types/char-and-varchar-transact-sql https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html#GUID-3B0B0A24-FA05-4A1F-902E-2E6D0BF85673

December 5, 2025