SQL (ANSI)프로그래밍수석 데이터베이스 엔지니어

시간 중첩 밀도의 분석이 필요한 시나리오에서, 프로시져 반복 없이 순수하게 **ANSI SQL** 집합 기반 논리를 사용하여 리소스 활용도가 절대적으로 최고조에 달한 정확한 순간을 어떻게 계산하시겠습니까?

Hintsage AI 어시스턴트로 면접 통과

질문에 대한 답변

질문의 역사

이 도전은 용량 계획 및 리소스 할당 영역에서 발생하며, 특히 호텔 예약 플랫폼, 클라우드 인프라 자동 확장 및 의료 시설 일정 관리 시스템과 관련이 있습니다. 초기 솔루션은 커서 기반 반복이나 외부 애플리케이션 논리를 통해 시간대를 반복하며 대규모 데이터 세트에서 심각한 성능 저하를 겪었습니다. ANSI SQL:2003 윈도우 함수의 출현은 순수한 관계적 접근 방식을 통해 시간 분석을 가능하게 하였으며, 데이터베이스가 복잡한 간격 산술을 효율적으로 처리할 수 있게 해주었습니다.

문제

start_timeend_time 타임스탬프가 있는 리소스 예약 테이블이 주어졌을 때, 목표는 임의의 순간에 활성화된 동시 예약의 최대 수를 결정하고 이 정점이 발생한 특정 시간대(들)를 식별하는 것입니다. 복잡성은 표준 집계가 시간 데이터의 축소를 초래하고, 단순 조인이 간격이 겹칠 때 카르테시안 폭발을 일으키기 때문에 발생합니다. 강력한 솔루션은 시간 간격의 시작과 끝을 개별 사건으로 취급하여 각 전환 지점에서 활성 리소스의 실행 중인 수를 계산해야 합니다.

해결책

표준적인 접근 방식은 간격을 UNION ALL을 사용하여 시작(가중치 +1)과 끝(가중치 -1)으로 구분하여 개별 사건으로 변환한 다음, **SUM() OVER (ORDER BY timestamp)**를 통해 동시성을 추적하는 누적 합계를 적용하는 것입니다. 동시에 시작/끝이 동일한 타임스탬프에 있는 경우를 결정론적으로 처리하기 위해, 동일한 타임스탬프에서 끝 사건은 시작 사건보다 먼저 처리되어야 합니다(보조 정렬 키 사용). 마지막으로, 최대 동시성 값을 필터링하기 위해 이를 CTE로 감싸야 합니다.

WITH events AS ( SELECT start_time AS ts, 1 AS delta, 0 AS is_end FROM reservations UNION ALL SELECT end_time AS ts, -1 AS delta, 1 AS is_end FROM reservations ), concurrency AS ( SELECT ts, SUM(delta) OVER (ORDER BY ts, is_end, delta ROWS UNBOUNDED PRECEDING) AS concurrent_count FROM events ) SELECT MAX(concurrent_count) AS peak_concurrency FROM concurrency;

정점 사용의 특정 시간대를 찾기 위해서는 최대 수치와 동일한 카운트를 가진 연속 타임스탬프 간의 기간을 식별하기 위해 다시 조인해야 합니다.

실생활에서의 사례

SaaS 플랫폼은 jobs라는 테이블에서 수백만 개의 비디오 트랜스코딩 작업을 추적하며, 여기에는 started_atcompleted_at 타임스탬프가 포함됩니다. 운영 팀은 GPU 활용도가 100%에 도달한 정확한 기간을 식별하여 큐 스케줄링을 최적화할 필요가 있었습니다.

고려된 한 가지 접근 방식은 커서를 사용하여 연대기적으로 반복하며 시작 시 카운터를 증가시키고 끝 시 감소시키는 것이었습니다. 이는 명령형 언어에 익숙한 개발자에게는 간단했지만, 이 방법은 행을 순차적으로 처리하여, 프로덕션 데이터에서 45분 이상 소요되며 테이블을 잠그게 되었습니다. 또한, 읽기 일관성을 보장하기 위해 복잡한 트랜잭션 관리가 필요했습니다.

또 다른 대안은 분 단위로 한 행씩 생성하는 시간 시리즈 테이블을 만들어 간격에 대해 BETWEEN 조건을 사용하여 조인하는 것이었습니다. 이는 정확한 결과를 제공했지만, 1년 동안 분 단위 정밀도를 위해 수십억 개의 행이 필요하여 테라바이트의 임시 저장소를 소모하고, 서브 분 단위 피크 스파이크를 잡아내기 실패했습니다.

팀은 사건 기반 UNION ALL 접근 방식을 ANSI SQL 윈도우 함수와 함께 선택했습니다. 시작과 끝을 +1/-1 사건으로 취급함으로써, 쿼리는 타임스탬프 열에 대한 표준 B-tree 인덱스를 사용하여 12초 만에 실행되었습니다. 이 방법은 작업이 다른 작업이 시작되기 바로 전에 종료된 경우와 같은 극한 사례를 올바르게 처리했습니다.

분석 결과, 정점 동시성은 UTC 기준으로 02:00에서 02:07 사이의 야간 배치 처리 중 발생하여 847개의 동시 작업이 있음을 발견했습니다. 이 창에 맞춰 동적 큐 제어를 구현함으로써, 연속적인 장애를 방지하고 인프라 과도 프로비저닝을 30% 줄일 수 있었습니다.

지원자가 종종 놓치는 점

동시 영구 간격(시작 시간 = 종료 시간)을 처리할 때 동시성 수치를 잘못 증가시키지 않으려면 어떻게 해야 합니까?

영구 간격은 즉각적인 사건을 나타내며, 이는 동시 수치에 기여해서는 안 됩니다. 표준 간격으로 간주되면, 자신의 종료 사건 동안 활성으로 카운트될 수 있습니다. 해결책은 엄격한 순서 키를 할당하는 것입니다: 타임스탬프가 충돌할 때 끝 사건(-1)을 시작 사건(+1)보다 먼저 처리하고, 비즈니스 로직에 따라 영구 간격을 이벤트 스트림에서 완전히 제외하거나 델타를 0으로 할당해야 합니다. ANSI SQL에서는 판별자 열을 추가하여 ORDER BY ts, is_end ASC, delta ASC로 구현되고, 종료가 같은 타임스탬프에서 새 할당이 증가하기 전에 카운트를 감소시킴을 보장합니다.

시작 사건과 끝 사건 결합 시 UNION ALL 대신 UNION을 사용할 경우, 사건 기반 접근 방식이 잘못된 결과를 반환할 수 있는 이유는 무엇입니까?

UNION은 암묵적으로 DISTINCT 작업을 수행하여 중복 타임스탬프를 축소합니다. 두 개의 예약이 정확히 2023-10-01 10:00:00에 시작하는 경우, UNION은 이를 단일 행으로 줄여 누적 합계에서 +1 증가를 놓치게 됩니다. 이것은 동시성을 과소 계산하는 결과를 초래합니다. UNION ALL은 모든 개별 간격 경계를 별도의 사건으로 보존하며, 이는 각 예약이 총 로드에 독립적으로 기여하기 때문에 수학적으로 필요합니다. 지원자들은 종종 이 구분을 무시하고 고유한 타임스탬프의 존재를 가정하여 적절한 집계를 위해 필요합니다.

최대값만이 아니라 정점 동시성의 특정 시간 창을 계산할 때, 여러 연속 시간 기간이 동일한 정점 값을 공유할 경우 출력에 격차가 발생하지 않도록 어떻게 합니까?

최대 동시성 값을 확인한 후, 이러한 값이 발생하는 모든 타임스탬프를 찾기 위해 다시 조인하면 개별 포인트가 됩니다. 연속 기간 블록을 재구성하기 위해서는 Gaps and Islands 기법을 적용해야 합니다: **LAG()**를 사용하여 이전 행이 정점에 있었는지 확인하고, **LEAD()**를 사용하여 다음 행이 정점에 있는지 확인해야 합니다. 이전 값이 다를 때(섬 시작) 또는 다음 값이 다를 때(섬 종료) 만 출력해야 합니다. 그런 다음 **ROW_NUMBER()**를 사용하여 시작-끝 쌍을 생성하여 짝지어야 합니다. 지원자들은 종종 원시 타임스탬프 목록을 출력하거나 카운트 값에 대해 GROUP BY를 사용하여 서로 다른 정점 사건을 하나의 연속 정점 기간으로 구별하는 데 필요한 시간 인접 정보가 손실됩니다.