Hive Table 복구 회고 with Airflow catchup 설정

# 서론

최근 개발한 데이터 파이프라인을 운영 환경에 적용하기 전 요구사항을 반영하는 과정에서, Airflow 스케줄링을 걸 때 catchup 값을 잘못 설정해서, 과거부터 현재 시점 사이에서 실행되지 않았던 DAG가 모두 실행되는 상황이 발생했다.

catchup 설정 자체는 알고 있었지만, 버전업이 되면서 바뀐 설정 방법을 사용하지 않아 DAG Backfill이 발생해 특정 파티션 데이터를 Hive 테이블에 중복으로 인입시켰다.

해당 Hive 테이블을 복원하면서 사용한 방법과 Airflow의 Backfiil 발생 원인을 정리하여 앞으로 이런 일을 반복하지 않도록 반성의 시간을 갖고 혹시 비슷한 이슈를 겪으신 분들에게 도움이 되고자 이를 공유한다.


# 상황

최근 구축했던 Data Pipeline를 최종 운영하기 전 마지막 반영을 위한 작업을 진행하면서, 최초에 짜놓은 Airflow DAG들에 대해 다음과 같은 고민이 문득 발생했다. 

  • Airflow DAG가 특정 Step에서 실패하고 재시도해도 상관없나?
  • 만약 테이블에 데이터가 잘못 들어가면 어떻게 복구하지?

DAG들을 처음 개발한 시점에서는 이러한 고민을 하지 않아서 테이블에 문제가 발생했을 때, 대응하기 쉬운 구조도 아니였고 복구하는 방법도 생각해보지 않았었다.

 

해당 DAG와 추후 정신건강을 위해 기존 DAG 구조를 분석했고, 실패하거나 잘못 돌아갔을 때 가장 위험도가 높은 부분을 찾고 테이블을 복구하기 위해 가장 적합한 방법을 고민했다.

 

간단하게, 테이블에 문제가 발생했을 때 복구하는 방법과 뭐가 있으면 조금 더 빠르게 복구할 수 있을지를 고민했다.

 

고민 후 한번의 추가 인입에는 바로 대응이 가능하도록 백업 테이블을 관리하는 기능을 추가했고, Airflow Retry Limit과 Delay를 더 넉넉하게 두어 파이프라인의 안정성을 높였다.

테이블에 중복 값이 들어갔으면 DELETE 문으로 삭제하면 되지 않을까란 생각도 들지만
Apache Hive를 다른 팀에서 관리하여 Delete 문을 사용할 수 있는 Tansaction Table을 사용하지 못하는 걸로 알고 있었고,
테이블을 주로 External로 사용했기 때문에 실제 데이터가 외부 저장소에 존재해 자유롭게 DELETE 문을 사용할 수 없는 환경이었다.
사실 Transaction Table을 생성할 수 있을지도 모른다..

아무튼, 테이블 데이터를 삭제하는 방법이 아닌, 테이블을 새로 생성해서 복원하는 과정이 필요했고 메타스토어의 파티션을 조작하는 방법도 숙지가 필요했다. 파티션 조작과 관련된 부분은 추후 포스팅할 예정이다.

 

이렇게 테이블 복원을 하는 방법을 머릿속으로만 생각하고 있었는데,,,,

 

DAG를 수정하고 스케줄링을 적용하는 과정에서 특정 DAG가 여러번 실행되는 상황이 발생하여 테이블을 복원해야하는 상황이 바로 발생했다.

 

해당 DAG에 슬랙 알림을 제공하는 기능도 같이 들어있어서 퇴근 이후에 잘못된 알림을 대량 발생시켜서 이를 확인했을 때 어질어질했다. 슬랙 알림이 있어서 이를 파악할수도 있었던 거지만..

 

해당 슬랙 채널은 비즈니스 관계자 분들과 함께 있어서 다음 주 PM님에게 혼날 예정이다..😢😢


# 문제 원인

문제의 원인은 Airflow DAG의 catchup 값이 True로 설정된 것이다. (정확히는 기본값인 True로 들어간 것)

 

catchup를 True로 설정하면 과거(start_date)부터 현재시점 사이에 실행되지 않았던 스케줄링 시점의 DAG들을 몽땅 실행시킨다. 
따라서, catchup 값을 False로 적용한 후 사용하고 있었는데, 사실 이를 제대로 적용해서 사용하지 않고 있었다..

 

문제가 발생시킨 추가적인 상황도 존재했는데, 이는 다음과 같다.

해당 DAG은 기존에는 스케줄링으로 실행을 했왔지만 데이터 파이프라인이 전부 구축되기 전까지 매뉴얼로 실행하도록 요청하셔서 한달 전 부터 해당 DAG를 매뉴얼로 실행시키고 있었다.

 

이 DAG를 다시 스케줄링으로 바꿨고, catchup 값이 True로 들어가서 매뉴얼로 실행시킨 기간 동안 실행되지 않았던 스케줄링 시점의 DAG들이 전부 실행된 것이다.

 

문제를 바로잡기 위한 수정은 매우 간단했다. 

default_args = {
	'owner': 'airflow',
	'depends_on_past': False,
	'start_date': datetime(~~~),
	'retries': 1,
	'retry_delay': timedelta(minutes=5),
	'catchup': False, # 문제 원인
}

dag = DAG(
	'test_dag',
	default_args=default_args,
	schedule_interval='0 0 * * 0',
)

위 처럼 catchup 설정을 하는 건 Airflow 1.0.0 버전대에 가능했고, Airflow 2.0.0 부터는  catchup 설정을 아래와 같이 DAG 객체 생성 시 인자로만 지정하도록 변경되었다.

dag = DAG(
	'test_dag',
	default_args=default_args,
	schedule_interval='0 0 * * 0',
	catchup=False,
)

 

근본적인 원인은 catchup 설정을 잘못한 것이고, DAG를 매뉴얼로 실행해서 스케줄링 시점의 공백이 발생시킨 것이 간접적인 원인이다.

공식 문서에도 떡하니 catchup의 설정 위치를 확인할 수 있었다.

만약 스케줄링으로만 쭉 돌렸으면 이런 상황이 발생하지 않았을 텐데하며 억울할 수도 있었겠지만, 공식 문서를 제대로 살펴보지도 않고 기술을 사용했던 자신을 반성해야 한다는 마음이 훨씬 컸다.

기본을 지키지 않아 발생한 문제이기 때문에 앞으로 동일한 이슈가 발생하지 않도록 반성하는 계기가 되었다.


Hive 테이블 복원 과정

급하게 테이블을 복원했던 방법이기 때문에 Hive를 완벽히 활용하지 못한 부분이 있을 수 있습니다. 보완할 점이나 지적받을만한 부분들은 댓글로 공유해주시면 감사합니다. :)

 

하이브 테이블에서 DELETE 문을 사용할 수 없는 환경이었기 때문에, 다른 방식으로 테이블을 복원하는 과정을 공유하고자 한다. 

특정 파티션을 기준으로 중복 데이터가 발생하였기 때문에, 전체 파티션에서 중복 데이터가 없는 테이블로 복구하는 것이 목적이였다.

 

절차는 다음과 같다.

  1. 기존 테이블 구조와 일치하는 복원용 테이블을 하나 생성한다.(파티션 칼럼 포함)
  2. 복원용 테이블에 기존 테이블에서 정상적인 데이터를 추가한다(동적 파티션 사용).
  3. 복원용 테이블에 중복된 파티션의 데이터를 다시 만들어 추가한다.
  4. 복원용 테이블 명을 기존 테이블로 바꾼다.

1. 복원용 테이블 생성

CREATE TABLE 복원용_테이블 LIKE 기존_테이블;

위 쿼리를 통해 기존 테이블과 동일한 구조를 갖는 테이블을 생성할 수 있다. 
파티션 칼럼 설정도 함께 복사되어 유지된다.


2. 정상적인 데이터 추가 

set hive.exec.dynamic.partition.mode=nonstrict;
INSERT INTO 복원용_테이블 PARTITION ({파티션 칼럼명})
SELECT *
FROM 기존_테이블
WHERE 1=1
	and {정상적인 데이터 조건}
;

 

이후 파티션을 포함한 정상적인 데이터를 추가해야하는데, 동적 파티션을 지정하여 칼럼 명으로 파티션 값을 추가할 수 있다.
기본적으로 Hive에서 INSERT 문을 사용할 때 파티션에 대한 정적 값이 제공되어야하는데, 동적 파티션 모드를 nonstrict으로 설정하여 유연하게 데이터를 파티션 테이블에 삽입할 수 있다.


3. 문제 데이터 추가

INSERT INTO 복원용_테이블 PARTITION ('{특정 값}')
SELECT *
{ 문제 파티션 데이터를 뽑기 위한 쿼리 }
;

 

중복이 발생한 파티션의 데이터를 제대로 뽑아서 복원용 테이블에 추가하는 과정을 진행했다.
이 과정에서는 특정 값으로 파티션을 지정했다.
해당 데이터를 추가함으로써 복원용 테이블은 문제 발생 전 테이블로 복원되었다.


4. 테이블 명 교체

ALTER TABLE 기존_테이블 RENAME TO 별도_테이블_명;
ALTER TABLE 복원용_테이블 RENAME TO 기존_테이블;

테이블명을 변경하여 기존 테이블과 복원용 테이블을 갈아끼우는 작업을 진행했다.
HIVE에서 테이블 명을 변경하는 것은 제약이 없기 때문에 이를 사용하는 것이 가장 빠르다고 생각했다.

 

 

RDB나 NO SQL 데이터베이스였다면 기존에 알고있던 방식으로 빠르게 대응할 수 있었을 텐데, 구조가 다른 하이브 테이블에서 작업하게 되어서 조금 버벅거리면서 진행하게 되었다.

기존 테이블에서 정상 데이터를 포함해 중복된 파티션 자체도 유니크하게 뽑은 다음 복원용 테이블에 추가할 수도 있었겠다는 아쉬움이 남지만, 쿼리를 작성할 시간이 부족하기도 하고 시행착오를 거칠 시간이 없었기 때문에 스스로 확실한 방법을 사용했다는 변명을 해본다.


# 정리 및 회고

해당 이슈를 파악한 이후 테이블 복구까지 30분이 소요되었다.

처음 이런 상황을 대응하여 시간이 오래걸린 것 같다. 이런 상황이 또 발생하면 안되지만, 이번 경험을 바탕으로 미리 HQL 스크립트를 준비하여 10분 내로 복구할 수 있도록 할 예정이다.


앞서 공유한 테이블 복구 과정을 대응하면서 부족한 역량을 다시한번 짚을 수 있었고, 
기능이나 서비스를 만들기만 하는게 아니라 이슈가 발생했을 때 어떻게 대응해야할지도 고려해야한다는 것을 경험할 수 있었다.
더 나아가 테이블이 날라가거나 하는 사소한(?) 장애 상황에서도 사전에 대응 방법을 파악하고, 더 나아가 이를 SOP화 시키는 것도 필요하다고 생각했다.

이번에 발생했던 윈도우 블루스크린 사건 같은 큰 사건뿐만 아니라 작은 상황에서라도 해결할 수 있는 방안을 미리 고려해야겠다는 계기가 되었다.

물론, 애초에 이런 상황을 스스로 발생시키지 않도록 기본을 지켜야 한다는 반성은 필수적이다.

아직까지도 갈 길이 멀다는 것을 느끼며 포스팅을 마친다.

728x90
반응형