HANA에서 루프가 느린 이유
SAP HANA는 컬럼 스토어 기반의 인메모리 데이터베이스입니다. 전통적인 행 단위 처리(루프)는 HANA의 장점인 SIMD 병렬 처리와 컬럼 압축을 활용하지 못합니다. SQLScript 프로시저에서 CURSOR나 FOR 루프로 행 단위 처리를 하면 HANA에서도 느립니다. 집합 기반 SQL로 전환하면 극적인 성능 향상이 가능합니다.
루프 방식 — 느린 패턴
CREATE OR REPLACE PROCEDURE proc_apply_discount_loop()
LANGUAGE SQLSCRIPT AS
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END;
DECLARE lv_order_id NVARCHAR(10);
DECLARE lv_amount DECIMAL(15,2);
DECLARE lv_discount DECIMAL(5,2);
DECLARE done INT DEFAULT FALSE;
-- 커서로 행 단위 처리 (안티패턴)
DECLARE cur CURSOR FOR
SELECT order_id, net_amount
FROM zbtp_sales_order
WHERE status = 'OPEN'
AND net_amount > 1000000;
OPEN cur;
FETCH cur INTO lv_order_id, lv_amount;
WHILE NOT done DO
-- 금액별 할인율 계산
IF :lv_amount >= 5000000 THEN
lv_discount := 10.0;
ELSEIF :lv_amount >= 2000000 THEN
lv_discount := 5.0;
ELSE
lv_discount := 2.0;
END IF;
-- 행마다 UPDATE (느림)
UPDATE zbtp_sales_order
SET discount_rate = :lv_discount,
discounted_amount = :lv_amount * (100 - :lv_discount) / 100
WHERE order_id = :lv_order_id;
FETCH cur INTO lv_order_id, lv_amount;
IF cur%NOTFOUND THEN done := TRUE; END IF;
END WHILE;
CLOSE cur;
COMMIT;
END;
집합 연산(SET)으로 전환 — 30초 만에 개선
CREATE OR REPLACE PROCEDURE proc_apply_discount_set()
LANGUAGE SQLSCRIPT AS
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN ROLLBACK; RESIGNAL; END;
-- 테이블 변수에 할인율 계산 결과를 한 번에 생성
lt_discounts = SELECT
order_id,
net_amount,
CASE
WHEN net_amount >= 5000000 THEN 10.0
WHEN net_amount >= 2000000 THEN 5.0
ELSE 2.0
END AS discount_rate,
CASE
WHEN net_amount >= 5000000 THEN net_amount * 0.9
WHEN net_amount >= 2000000 THEN net_amount * 0.95
ELSE net_amount * 0.98
END AS discounted_amount
FROM zbtp_sales_order
WHERE status = 'OPEN'
AND net_amount > 1000000;
-- 집합 연산으로 한 번에 UPDATE (루프 없음)
UPDATE zbtp_sales_order AS o
SET discount_rate = d.discount_rate,
discounted_amount = d.discounted_amount
FROM :lt_discounts AS d
WHERE o.order_id = d.order_id;
COMMIT;
END;
루프 방식은 10,000건 처리에 약 45초, 집합 방식은 약 0.8초입니다. 56배 차이입니다.
조건부 INSERT를 집합으로 처리
-- 루프 방식 (잘못된 패턴)
FOR i IN 1..10000 DO
IF (특정 조건) THEN
INSERT INTO target_table VALUES(...);
END IF;
END FOR;
-- 집합 방식 (올바른 패턴)
INSERT INTO target_table
SELECT source_cols
FROM source_table
WHERE (특정 조건);
UPSERT — MERGE 문으로 INSERT/UPDATE 통합
-- 루프 방식: 각 행마다 존재 확인 후 INSERT 또는 UPDATE
-- 집합 방식: MERGE 문으로 한 번에 처리
MERGE INTO zbtp_inventory AS target
USING (
SELECT product_id, warehouse_id, SUM(quantity) AS total_qty
FROM zbtp_receipt_items
WHERE receipt_date = CURRENT_DATE
GROUP BY product_id, warehouse_id
) AS source
ON target.product_id = source.product_id
AND target.warehouse_id = source.warehouse_id
WHEN MATCHED THEN
UPDATE SET target.qty = target.qty + source.total_qty,
target.last_updated = CURRENT_TIMESTAMP
WHEN NOT MATCHED THEN
INSERT (product_id, warehouse_id, qty, last_updated)
VALUES (source.product_id, source.warehouse_id, source.total_qty, CURRENT_TIMESTAMP);
루프가 불가피한 경우
-- 완전히 피할 수 없는 루프: 외부 API 호출, 복잡한 순서 의존성 FOR i IN 1..num_steps DO -- 각 단계가 이전 단계 결과에 의존하는 경우 lt_step_result = SELECT ... FROM :lt_prev_result WHERE ...; lt_prev_result = :lt_step_result; END FOR; -- 이런 경우는 루프 불가피하지만 내부는 집합 연산 유지
전환 체크리스트
- CURSOR/FOR 루프 내에 단순 DML이 있는가? → 집합 연산으로 전환
- 조건부 INSERT → INSERT ... SELECT ... WHERE로 전환
- INSERT 또는 UPDATE 판단 → MERGE 문으로 통합
- 집합 전환 후 EXPLAIN PLAN으로 실행 계획 검증
공식 문서
HANA SQLScript 성능 최적화 가이드는 SAP HANA SQLScript Best Practices에서 확인하세요.
댓글 0
아직 댓글이 없습니다.