효율적인 하둡 플랫폼 운영을 위한 “Hive 사용량 통계 레포트” 개발기

서원국
네이버 플레이스 개발 블로그
16 min readNov 17, 2022

Hive 사용량 통계가 필요한 이유

안녕하세요 G플레이스데이터개발 팀에서 근무하고 있는 서원국이라고 합니다. 저희 팀은 Glace CIC의 데이터를 하둡 에코시스템에 적재 후 활용 할 수 있는 Data Lakehouse를 만들고 있습니다. Data Lakehouse 구성 요소 가운데 SQL on Hadoop 솔루션으로 Hive와 Trino를 활용하고 있습니다. Data Lakehouse를 안정적으로 운영하기 위해서는 시스템 사용 현황을 잘 모니터링 할 필요가 있습니다. 만약 컴퓨팅 리소스의 사용현황 뿐 아니라, 어플리케이션 영역에서 사용자들이 어떤 데이터를 얼마나 사용하고 있는지 현황까지 알 수 있다면 시스템 운용 측면에서 어떻게 달라질까요? 구체적인 예로써는 다음과 같은 경우가 생각해 수 있습니다.

  1. 필요한 것 이상으로 많은 데이터를 조회하는 경우
  2. 더 이상 필요 없는 데이터를 계속 적재하고 있는 경우
  3. 더 이상 수행이 불필요한 과거의 Batch 작업이 계속해서 남아있는 경우

위와 같은 정보를 잘 파악하고 있다면 불필요한 데이터 저장과 ETL Pipeline 구축 비용을 절약할 수 있게 되어 보다 효율적인 시스템을 구축할 수 있게 될 것입니다.
저희 팀의 Data Lakehouse는 여러 팀들이 손쉽게 분석에 필요한 정보를 조회할 수 있는 데이터 허브 역할을 제공하고 있습니다. 따라서 쿼리 사용 패턴을 분석해 효율적인 시스템을 항상 유지해 나갈 수 있도록 이용 현황을 모니터링할 수 있는 방법이 필요했습니다. 매번 사용자들의 사용 계획과 현황을 인터뷰하는 방식으로는 많은 커뮤니케이션 비용을 발생시킴에도 불구하고 금세 실제 사용 패턴과 차이가 발생하는 문제가 있습니다.

이런 관점에서 Hive 사용량 통계가 등장합니다. Hive 쿼리의 이력 및 쿼리를 실행한 계정의 데이터를 관리하고 이용한다면 위의 문제를 쉽게 해결할 수 있게 되는 겁니다.

  • 시간이 오래 걸리는 쿼리, 과도하게 요청하고 있는 계정같이 일반적이지 않은 use-case를 쉽게 발견할 수 있다.
  • 문제가 생기는 쿼리, 사용하고 있지 않은 테이블 등 데이터 기반으로 직관적으로 커뮤니케이션을 할 수 있다.

또 이러한 사용 통계 데이터를 바탕으로 다음 필요한 개선 작업들의 우선순위를 세우거나 여러 팀이 원활히 사용 가능하도록 시스템 확충을 수행할 수 있을 것입니다.

Hive 사용량 통계 예시

Hive의 사용량 통계를 관리하는 것은 엔터프라이즈 Hadoop 플랫폼에서도 확인할 수 있습니다. CDH의 경우에는 Cloudera Navigator에서 Hive Operation의 이력을 아래와 같이 조회할 수 있습니다.

출처

STEP 1) CDH Navigator 의 통계 데이터 활용

개발기

CDH는 위에서 소개한것 과 같이 Cloudera Navigator를 지원하고 있습니다. 이때 저희는 CDH를 사용하고 있었기 때문에, Cloudera Navigator를 쓸 수 있었습니다. 그리고 이미 Cloudera Navigator에서 Hive의 사용량에 대한 데이터를 조회할 수 있기 때문에 단순히 데이터를 가져와서 원하는 대로 변환해서 사용하는 프로그램을 작성했습니다.

Cloudera Navigator에서 데이터를 가져오는 방법은 두 가지가 있습니다.

  • Cloudera Navigator의 Web UI에서 데이터를 크롤링 하는 방법
  • Cloudera Navigator에서 제공하는 API를 사용하는 방법

사실 두 가지 방법 중에서 성능적, 개발 편의성 어떤 측면에서도 REST API를 이용하는 것이 더 유리합니다. 하지만 개발 당시에는 Cloudera Navigator에서 REST API를 제공한다는 것을 인지하지 못해서 Web UI의 내용을 크롤링 하는 방향으로 진행했습니다.

Selenium 활용

크롤링 도구로는 Python 라이브러리 Selenium을 사용했습니다. Selenium은 웹 브라우저 드라이버를 이용해서 실제 웹 브라우저를 열고 정의된 명령을 실행하는 방식으로 작동합니다. 그래서 처리 과정과 문제가 생기는 부분을 바로바로 확인할 수 있는 장점이 있지만, 실제 브라우저가 뜨고 렌더링 하는 것을 기다려야 하기 때문에 굉장히 느려지게 됩니다.

Selenium으로 Cloudera Navigator의 화면을 크롤링했던 코드

Chrome 드라이버를 이용해서 Chrome 브라우저로 크롤링을 진행했을 때, 한 달 치 통계 데이터를 가져오는 작업이 대략 4~5 시간 정도 소요되었습니다.

Ray 를 이용해서 병렬 처리하기

이렇게 크롤링 작업이 오래 걸리는 문제를 해소하기 위해서 병렬 처리 방식을 도입해 보았습니다. 처음에는 Python에서 기본으로 제공하는 multiprocessing 모듈을 이용하다가 성능이 더 좋은 병렬 처리 라이브러리인 Ray로 갈아타게 됩니다.

Ray를 활용해 병렬 처리하도록 개선한 코드

크롤링 작업을 병렬로 처리하기 위해서 여러 개의 쓰레드에서 각각 하나씩의 Chrome 브라우저를 실행하고 Cloudera Navigator의 조회 결과를 페이지별로 나누어서 처리하도록 작성했습니다. 병렬로 처리를 하도록 했지만 성능 자체는 기대했던 만큼, 몇 배로 오르진 않았습니다. 여러 개의 Chrome 브라우저에서 많은 양의 요청을 처리하는 만큼 렌더링도 많이 하게 되고 리소스를 많이 사용하기 때문이었습니다.

한계 및 문제점

  • Cloudera Navigator의 데이터를 가져와서 사용한다는 점에서 의존성이 생기는 한계가 있습니다. 알고 싶은 건 Hive의 사용량 정보인데 정작 의존성은 Cloudera Navigator 쪽에 생기는 것은 문제라고 볼 수 있습니다.
  • 웹 크롤링으로 데이터를 가져온다는 점에서 성능적 한계가 있습니다.
  • Cloudera Navigator에서 얻어온 데이터를 따로 적재하지 않고 단순히 뽑은 데이터를 MD 파일로 정리하는 프로그램이었기 때문에, 같은 데이터를 여러 번 가져와야 하는 상황에 프로그램을 여러 번 실행해야 하는 문제가 있습니다.

후기

처음으로 웹 크롤링을 해봤기 때문에, 흥미롭게 개발을 진행했던 프로그램입니다. 다만 REST API 지원에 대한 조사 없이 무턱대고 웹 크롤링으로 진행한 바람에 성능적으로 바람직하지 못한 결과가 나오게 되어 아쉬웠습니다. 그렇다고 웹 크롤링이나 Selenium 같은 웹브라우저 제어 기능이 필요 없냐고 하면 그건 아닙니다. 만약 REST API를 지원하지 않는 외부 웹 UI에서 특정한 데이터를 가져올 필요가 있다거나, 웹 프로젝트의 End-to-End 테스트를 자동화할 필요가 있다면 정말 유용하게 사용할 수 있을 거라 생각합니다.

개발을 하면서 깨달은 점이 있다면 실제 구현에 앞서 더 좋은 방법이 있는지 충분히 고려해야 한다는 것입니다. 예를 들어 외부 서비스를 써야 하는 경우에 우리의 요구사항을 어떤 방식으로 만족시켜 줄 수 있는지 꼼꼼히 살펴서 가장 좋은 방식을 선택하도록 노력할 필요가 있겠습니다.

STEP 2) Hive 로그 파일을 분석해서 통계 데이터 만들기

개발기

이번에 저희 팀에 큰 변화가 생겼습니다. 바로 CDH를 더 이상 사용하지 않고 Hortonworks DataPlatform(HDP) 기반으로 작성된 Naver 전사 하둡 플랫폼인 C3S 를 사용하게 된 것입니다. 그래서 유료 라이센스에서만 사용 가능하던 Cloudera Navigator를 사용하지 못하게 되었고 위에서 설명한 기존의 Hive 사용량 통계 프로그램도 사용하지 못하게 됐습니다. 또 C3S에서는 저희가 필요로 했던 Hive 사용량 통계를 기본 기능으로 제공하지는 않아 대안을 마련할 필요가 생겼습니다.

조사해 본 결과 쿼리가 실행되면 쿼리가 호출된 시각, 본문, 계정 같은 정보를 로그로 출력하고 있다고 확인했습니다. 그래서 Hive 로그를 분석해서 사용량 데이터를 만들었습니다.

가장 먼저, Hive 로그 중에서 필요한 것들을 추려야 했습니다. 4가지 타입으로 필요한 Hive 로그를 분류했습니다.

Hive 서버 로깅 패턴에 따라 자체 분류한 로그 타입

이렇게 분류된 로그들의 데이터를 모아서 스키마를 작성했습니다.

HiveUsageReport {
log_timestamp INT64 // 로그가 출력된 시간
session_id STRING // 세션 아이디
query_id STRING // 쿼리 아이디
query STRING // 쿼리 본문
principal STRING // 쿼리를 실행한 계정
execution_time_ms INT64 // 쿼리 실행에 소요된 시간(ms)
used_tables LIST[STRING] // 쿼리에서 사용된 테이블 리스트
used_tables_length INT64 // 쿼리에서 사용된 테이블의 갯수
log_type LogType // 로그의 타입
}

각 타입의 로그들은 query id 또는 session_id를 갖고 있습니다. 같은 쿼리라면 같은 query_id, 같은 세션에서 실행된 쿼리라면 같은 session_id를 가지고 있기 때문에 서로 Join 하여 하나의 데이터로 SELECT 할 수 있습니다. 이렇게 SELECT 된 데이터를 Hive 사용량 데이터로 사용하게 됩니다.

또, Join 조건을 만들면서 Window를 이용하는 방법을 익히게 되어 소개해 볼까 합니다.

예를 들어, 위와 같은 데이터가 있을 때, id가 같은 데이터 중에 value가 null이 아니면서 현재 Row에서 가장 가까운 Row의 value 값을 가져오고 싶었습니다.

이런 식으로 말이죠. 꽤나 복잡한 서브쿼리와 조건이 필요할 것으로 보입니다만, Window를 사용하면 간단하게 조회할 수 있습니다.

last_value(value, TRUE) OVER(PARTITION BY id ORDER BY no ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as value2

일단 OVER 아래 내용을 확인해보겠습니다. OVER 절에서는 Window의 범위를 지정합니다. 여기서는 id로 파티션하고, no로 정렬한 데이터중에서 처음부터 현재 Row 까지를 범위로 지정했습니다. 거기서 last_value 즉, 마지막 값을 가져오게 되는데 last_value() 함수에 첫 번째로 넣은 인자로 가져올 필드를 지정하고, 두 번째로 넣은 인자는 null 을 무시할 지 여부를 지정합니다.

spark-submit 가능한 어플리케이션 만들기

Hive 로그를 읽어서 통계 데이터를 얻도록 변경되면서, 기존과는 다른 병렬 처리 방법이 필요하게 되었습니다. 처음으로 고안한 방법은 멀티 쓰레드를 활용해 Hive 로그 파일을 읽어서 처리하는 것이었습니다.

하지만 이런 방법으로 작업을 나누게 되면 만약 쓰레드 개수보다 적은 양의 로그 파일을 읽게 되면 설정된 모든 쓰레드를 활용하지 못하게 되고, 쓰레드 간 작업의 분배도 파일의 크기 차이에 따라 들쑥날쑥하게 됩니다.

이런 문제를 해결하기 위해서 Spark를 도입하게 됩니다. Spark를 사용하게되면 병렬 처리에 대한 작업을 Spark Driver에게 위임하게 됩니다. Spark Driver는 파일을 읽으면 일정한 파티션으로 나누어서 읽게 되는데 설정에 따라 파티션 별로 Spark Executor 가 병렬 처리할 수 있도록 지원합니다. 그래서 병렬처리에 대한 코드를 작성할 필요가 아예 없어지면서도 Executor마다 균등하게 작업을 분배할 수 있게 됩니다.

또 팀 내 다른 많은 작업들이 Spark로 작성되어 있기에 통일성을 맞추는 장점도 있었습니다.

이렇게 Daily 로그를 Hive 컨테이너에 작성한 스크립트로 HDFS에 업로드하고, Spark에서 업로드된 Hive 로그를 읽어서 사용량 데이터를 얻어낸 뒤 적재하는 작업을 매일 한 번씩 실행하도록 했습니다.

실시간으로 Hive 로그를 가져와 spark-streaming 으로 처리하기

매일 한 번씩 Hive의 Daily 로그를 가져오는 방법에 문제가 생겼습니다. 로그 파일 자체가 컨테이너의 로컬 파일이기 때문에, Hive 앱이 종료되면 로그 파일도 유실되는 문제였습니다. 이를 해결하기 위한 방법으로 생각해낸 것이 Hive 로그를 실시간으로 가져와서 사용하는 것이었습니다.

로그를 실시간으로 전송하는 방법으로 두 가지를 고려했습니다.

  1. Apache Flume
  2. Filebeat, Logstash, Kafka

저희는 Filebeat, Logstash, Kafka를 사용하도록 결정했습니다. 그 이유는 커뮤니티가 더 활성화되고 널리 사용된다는 점과 사내의 Nelo라고 하는 로그 수집 툴에서도 Filebeat를 지원하고 있기 때문이었습니다.

로그를 가져오는 방법은 위의 그림과 같습니다. Hiveserver2 앱의 컨테이너에 Filebeat를 같이 배포하고, Filebeat로 컨테이너 내부의 로그 파일을 읽어서 Logstash로 전송합니다. Logstash는 전송된 로그를 받아서 Kafka로 프로듀싱합니다.

로그를 실시간으로 수집하게 되면서, 기존에 작성한 Spark 작업도 spark-streaming 작업으로 변경해서 실시간으로 처리할 수 있도록 하는 계획을 세우게 됩니다. 실시간 작업을 하게 되면 두 가지의 장점을 얻을 수 있었습니다.

  • 한 번에 처리해야 할 로그의 개수가 줄어들기 때문에 적은 양의 메모리로도 작업을 처리할 수 있게 된다.
  • 추가된 데이터를 바로바로 조회가 가능하다.

결과적으로 Kafka에 프로듀싱된 Hive 로그를 2분마다 컨슈밍한 뒤, 분석해서 Hive 사용량 통계 데이터를 적재하는 형태가 되었습니다.

Apache Iceberg 추가하기

Hive 사용량 통계를 작성하는 한편 팀 내에서 Apache Iceberg(이하 Iceberg)를 도입하고 있었습니다. Iceberg는 데이터를 Iceberg 테이블의 형태로 HDFS에 저장하는 도구 입니다. Iceberg의 기능과 통계 데이터의 궁합이 맞는 점이 있다고 판단해서 Iceberg를 도입하게 됩니다.

궁합이 잘 맞는다고 판단한 이유는 Hidden Partitioning 때문입니다. Iceberg에서는 데이터의 물리적으로 파티셔닝과 관계없이 더 세밀한 단위의 조건에도 원하는 파일만을 읽어서 조회할 수 있는 기능을 제공합니다.

만약, 그림과 같이 연도로 데이터를 파티션 한 데이터를 2022년 9월 보다 크거나 같은 날짜로 조회한다고 했을 때, 일반적인 경우라면 파티션 안에 모든 파일을 읽겠지만 히든 파티션의 경우 해당 월에 해당하는 파일만 읽게 되는 겁니다.

기존에 저희들은 데이터가 쓰인 시간으로 year, month, day, hour까지 파티션을 만들었습니다. 하지만 Hidden Partitioning을 사용한다면 Hive 쿼리 이력같이 시간 범위를 기준으로 조회할 일이 대부분이고, 다른 조건으로 조회할 일이 잘 없는 데이터의 파티션 키를 조회 성능에 상관없이 줄일 수가 있게 됩니다. 파티션이 줄어들면 파일의 개수도 줄어들게 되고 결과적으로 네임노드의 메모리를 더 효율적으로 사용하는 일이 됩니다.

또, 파티션의 물리적 구조를 알 필요 없이 일반적인 필드처럼 조회 조건을 작성할 수 있게 되어, 데이터를 조회하는 사용자의 편의성에도 유리한 점이 있습니다.

Iceberg 스냅샷이 과도하게 생성되는 문제

Iceberg는 데이터를 쓸 때마다 스냅샷을 생성합니다. 또 스냅샷이 생성되면 새로운 메니페스트 파일이 생성됩니다. 저희의 경우 2분 간격으로 마이크로 배치를 실행하고 있었기 때문에 메니페스트 파일도 2분마다 하나씩 생성되어 많은 양의 메니페스트 파일을 생성하게 되었습니다. 쓸모없는 파일이 너무 많이 생성되는 것은 그 자체로도 문제지만 Hadoop에서는 특히 작은 파일이 많이 생기는 것이 네임노드 성능에 영향을 미치기 때문에 더 큰 문제가 됩니다. 그래서 write 주기를 더 늘려야 할 필요가 있었습니다. 하지만 마이크로 배치의 간격을 늘리게 되면 Spark Executor 가 배치마다 처리해야 하는 데이터의 수가 늘어나게 되고 Spark Executor의 메모리 요구량이 많아지게 됩니다. 그래서 마이크로 배치의 간격은 그대로 두면서 Iceberg 테이블의 write 주기를 늘리는 방법 찾기 위해 논의를 하게 됩니다.

결론적으로 데이터를 2분마다 parquet로 적재를 한 뒤, 한 시간마다 적재된 데이터를 취합해서 Iceberg 테이블에 쓰도록 만들어 마이크로 배치의 간격은 그대로 두고 Iceberg 테이블의 write 주기를 늘리는 것으로 했습니다.

후기

통계 기능을 구현하고 개선해 나가면서 스파크의 다양한 기능을 직접 찾아보고 테스트해보게 되어 Spark에 대한 이해가 더 깊어지는 기회가 되었습니다. 로그를 수집하고 분석해서 유의미한 데이터를 얻어낸다는 작업이 꽤나 보람차다고 생각해서 흥미롭게 개발할 수 있었습니다. 다만 아쉬운 점은 개발을 하다가 중간에 문제가 발생해서 개발 방향을 여러 번 바꾸는 일이 있었습니다. 계획 단계에서 여러 문제를 고려하고 예측해야 하지만 그걸 못하고 말았습니다. 제가 데이터 엔지니어링에 대해 아직 미숙한 부분이 많아서 놓친 부분이 있었다고 말할 수도 있겠습니다만, 처음부터 데이터 엔지니어링에 경험과 노하우가 있는 팀 동료들과 충분히 논의하고 탄탄하게 설계를 했다면 이런 일이 없었을 거라 생각이 듭니다.

STEP 3) 리포트 작성하기

Tableau는 시각화 도구로 데이터를 조회해서 그래프, 표 등으로 표현해서 시트를 제작할 수 있습니다. 또 Hive 커넥션을 지원하기 때문에 HDFS에 적재된 통계 데이터를 쉽게 조회해서 사용할 수 있는 장점이 있습니다. Tableau에서 만든 시트를 모아서 대시보드를 만들 수 있습니다. 그리고 만든 대시보드를 구독함으로써 정기적으로 메일로 수신할 수 있습니다.

월간 리포트

월간 리포트

주간 리포트

주간 리포트

후기

데이터를 시각화하는 일을 쉽게 생각했었는데 깔끔하다, 한눈에 잘 보인다 이런 디자인적 기준은 끝이 없는 것 같습니다. 미세하게 이리저리 바꿔가면서 작업을 했습니다. 시각화된 리포트를 보니 제가 만든 데이터가 중요한 운영 지표로 사용될 것이 눈에 보여 꽤나 보람찼습니다. 또, 시각화에 사용할 여러 지표들을 생각하다 보니 데이터를 활용해서 진행할 수 있는 새로운 아이템들이 많이 떠올라서 흥미로웠습니다.

--

--