BTP

HANA 루프 — SET으로 30초 만에 전환하기 #shorts #SAP #HANA

▶ YouTube에서 보기

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

아직 댓글이 없습니다.