대용량 데이터를 잘 다루는 일은 소프트웨어와 함께 하드웨어에 대한 이해를 요구하고 컴퓨터 공학의 총망라한 기술이 필요한 어려운 일입니다. 이에 하둡 에코시스템이 등장했고 빅데이터 수집, 저장, 처리, 분석, 표현 등의 과정으로 문제를 세분화하여 각 과정에 최적화된 기술이 개발되었습니다. Sqoop은 빅데이터 수집 과정에 속해있는 기술이고, Top-level Apache 프로젝트로서 하둡의 주요 프로젝트로 자리잡고 있습니다. 이 글을 통해 Sqoop의 기초적인 부분에 대해서 알아봅시다.


1. Sqoop 소개

Sqoop (SQL to Hadoop)은 다음과 같이 관계형 데이터베이스(RDB)와 분산 파일 시스템(HDFS) 사이의 양방향 데이터 전송을 위해 설계된 툴이다. 오직 두 포인트 사이의 데이터 통신을 쉽게 다루기 위해서 개발된 프로젝트이다.

Sqoop의 개발된 동기를 살펴보기 위해서는 빅데이터 플랫폼인 하둡 에코시스템의 전체를 훑어볼 필요가 있다. 급증하는 대용량 데이터를 최적의 비용으로 대응하기 위해 하둡이 등장했다. 초기 하둡은 HDFS와 MapReduce로 시작했지만 활용 범위가 증가되어 다양한 서브 프로젝트가 서로 상호작용하는 에코시스템으로 확대되었다. Spark 시작하기 글의 첫 번째 그림으로 대략적인 모습을 볼 수 있다.

하둡 에코시스템의 주요 기능은 빅데이터 저장과 처리이며 아래 그림을 통해 흐름을 확인할 수 있다. 대용량 데이터를 하둡에서 처리하기 위해서는 HDFS와 같은 분산 파일 시스템에 데이터를 저장할 필요가 있다. Hive, Spark 그리고 MapReduce와 같은 분산 데이터 처리기는 항상 HDFS를 바라보고 데이터를 처리한다.

새롭게 생성되는 데이터는 Kafka와 Flume을 통해 HDFS로 곧장 보낼 수 있지만 기존에 존재하는 RDB에 있는 데이터는 따로 HDFS로 전송할 필요가 있다1. 보통 원천 데이터는 RDB에 존재한다. 스트리밍용 하둡 에코시스템을 구축한다하더라도 메타 데이터 또는 분석 결과물 등을 저장하기 위해서 RDB를 사용할 필요성이 높다. 따라서 어떤 프로젝트든지 RDB와 HDFS 사이의 통신이 필수적인 요소가 되었고 이 일만 전문적으로 하기 위해 Sqoop이 등장했다.

Sqoop의 동작 방식을 간단하게 살펴보자. Sqoop은 하둡의 리소스 관리자인 YARN과 MapReduce, HDFS 위에서 동작한다2. 장점으로 단순히 CLI (Command Line Interface)로 sqoop을 실행할 수 있다. 또한, RDB를 제어하는 다양한 종류3의 CLI를 지원한다[1]. 각 기능마다 한 줄의 CLI로 작성하면 되기에 복잡한 통신 프로세스를 쉘로 통합하여 쉽게 작성할 수 있다.

사용자가 게이트웨이와 같은 서버에서 Sqoop CLI 명령어를 실행하면 Sqoop 프로세스는 먼저 해당 명령어와 관련된 메타 데이터를 RDB에서 가져온다. 가져온 메타 데이터를 참조하여 YARN 리소스 관리자를 통해 MAP 프로세스를 실행한다. 병렬 인자가 있는 CLI라면 여러 개의 MAP 프로세스가 실행되어 병렬성을 가진다. RDB와 통신하기 위해서 JDBC 프로토콜이 요구되기 때문에 각 종류에 맞는 JDBC 드라이버를 미리 준비하여 하둡을 실행하기 전 외부 라이브러리로 포함시켜야 한다4.


2. Sqoop 시작하기

이제 본격적으로 Sqoop CLI를 살펴보자. 여기서는 자주 사용하는 CLI 몇 개만 살펴본다. CLI의 인자는 직감적으로 어떤 의미인지 유추할 수 있기에 특정 부분 빼고는 따로 설명은 하지 않았다. 이외에 다양한 종류의 CLI와 사용 가능한 인자들에 상세 설명은 [1]을 통해 확인할 수 있다.

sqoop-list-databases

RDB의 서버는 보통 여러 개의 데이터베이스를 포함하는 DB 클러스터로 구성된다. 해당 서버의 주소를 통해 모든 데이터베이스의 이름을 추출하는 일을 한다.

$ sqoop list-databases \ 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port" \ 
--username "XXXX" \ 
--password "XXXX" \ 
--verbose

sqoop-list-tables

위와 비슷하게 특정 데이터베이스의 모든 테이블의 이름을 리스트업한다. 한 가지 차이는 `–connect` 인자에 특정 데이터베이스 이름까지 작성해야 한다.

$ sqoop list-tables \ 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--username "XXXX" \ 
--password "XXXX" \ 
--verbose

위의 두 Sqoop CLI는 RDB의 메타 정보를 추출하기 위한 목적이 크고, RDB 계정의 접근 테스트를 간접적으로 할 수 있다. 참고로 Sqoop CLI을 실행하기 전에 해당 RDB 서버와 통신할 수 있는지 여부를 확인할 필요가 있는데 다음과 같이 telnet으로 간단히 체크할 수 있다.

$ telnet hereis.dbcluster.path port

sqoop-eval

특정 데이터베이스를 대상으로 SQL 쿼리문을 날릴 수 있다. 결과는 콘솔에 프린트된다. 보통 Sqoop CLI를 통해 SQL 쿼리 작업을 하지는 않을 것이다. 테스트 용도와 함께 간접적으로 RDB의 정상 동작을 체크하는 목적에 적합하다.

$ sqoop eval \ 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--query "select * from a_dbname.a_table limit 10" \ 
--username "XXXX" \ 
--password "XXXX" \ 
--verbose

sqoop-import

RDB에 있는 특정 테이블의 데이터를 HDFS로 옮길 때 사용한다. Parquet, text, avro 등 다양한 데이터 포맷으로 저장될 수 있다7.

$ sqoop import \ 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--table "a_table" \ 
--delete-target-dir \ 
--target-dir "hdfs://hereis/hdfs/etl/a_dbname/a_table" \ 
--as-parquetfile \ 
--username "XXXX" \ 
--password "XXXX" \ 
-m "4" \
--verbose

중요한 인자는 병렬성(parallelism)을 제어하는 `-m`이다. 이는 `–num-mappers`로도 표현된다. 이 인자의 값으로 MAP 테스크의 개수를 지정할 수 있다. 디폴트로 4개의 MAP 테스크를 가진다. 주의할 점은 MAP 테스크 개수는 RDB에 부하를 주지 않을만큼 지정해야 하고, 하둡 클러스터의 가용한 범위 내에서 선정되어야 한다5.

2개 이상의 MAP 테스크를 쓰기 위해서는 문제를 분할하기 위한 기준이 필요하다. Sqoop에서는 특정 컬럼을 기준으로 분할한다. 공평하게 분할하기 위해서 카디널리티가 큰 기본키를 사용하면 좋다6. Sqoop은 디폴트로 기본키를 분할 컬럼으로 인지한다. 예를 들어, id라는 기본키를 가진 테이블을 4개의 MAP 테스크로 import한다고 하면 다음 쿼리를 하나씩 각 MAP 테스크가 가져간다[1]. id 컬럼의 최소/최대는 각각 0, 1000이라 하자.

SELECT * FROM sometable WHERE id >= 0 AND id < 250
SELECT * FROM sometable WHERE id >= 250 AND id < 500
SELECT * FROM sometable WHERE id >= 500 AND id < 750
SELECT * FROM sometable WHERE id >= 750 AND id < 1001

기본키가 없으면 `split-by` 인자로 특정 컬럼을 지정할 수 있다. 대신 인덱스가 걸려있어야 한다. 아쉽게도 멀티 컬럼은 지원하지 않는다.

$ sqoop import \ 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--table "a_table" \ 
--delete-target-dir \ 
--target-dir "hdfs://hereis/hdfs/etl/a_dbname/a_table" \ 
--as-parquetfile \ 
--username "XXXX" \ 
--password "XXXX" \ 
--split-by "user_id" \
--num-mappers "16" \
--verbose

import 하고자 하는 테이블에 기본키가 없고 `split-by` 인자를 작성하지 않으면 `–num-mappers`의 인자는 1이 되어야 한다. 병렬 import는 전송 속도를 크게 향상하기에 적극적으로 사용하면 좋다.

sqoop-export

HDFS의 데이터를 RDB로 옮길 때 사용한다. 타겟 테이블은 반드시 데이터베이스에서 미리 정의되어 있어야 한다. import와 같이 병렬성을 제어할 수 있다. 다양한 옵션은 [1]을 참고하길 바란다.

$ sqoop export \
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--table a_tbname
--export-dir /hdfspath/a_tbname_data \
--verbose

sqoop-create-hive-table

RDB의 table을 Hive table로 생성하여 Hive 쿼리를 사용할 수 있도록 해준다.

$ sqoop create-hive-table 
--driver "com.mysql.jdbc.Driver" \ 
--connect "jdbc:mysql://hereis.dbcluster.path:port/a_dbname" \ 
--table a_tbname\
--hive-table hive_tbname\
--verbose

위와 같이 독립적인 CLI로 사용하면 HDFS에 데이터를 저장하지 않고 Hive table만 생성한다. sqoop-create-hive-table을 sqoop-import CLI의 인자로도 사용할 수 있는데 이 경우는 HDFS에 데이터를 저장한 다음 Hive Table을 생성한다8.


3. 마무리

Sqoop은 RDB와 HDFS 사이의 데이터 전송해주는 툴입니다. CLI 한 줄로 간단히 실행할 수 있고, 다양한 CLI를 제공하기에 복잡한 RDB의 ETL 작업을 쉘 스크립트로 깔끔하게 작성할 수 있는 장점이 있습니다.


4. 각주

  1. Sqoop은 정형 데이터를, Kafka와 Flume은 비정형 데이터를 다룬다.
  2. Sqoop은 기본으로 분산 데이터 처리기를 MapReduce로 사용한다. [8]을 참고하면 Spark 위에서도 Sqoop이 동작함을 알 수 있다. Spark을 통해 변형(Transformation)을 포함한 완전한 데이터 ETL을 할 수 있다고 한다. 그리고 분산 파일 시스템은 HDFS뿐만 아니라 HBase, AWS의 S3 등을 지원한다.
  3. Spark SQL도 Sqoop과 같은 기능을 한다. Sqoop은 간단한 CLI 동작 방식과 RDB를 제어하는 다양한 기능을 가진다는 점에서 이점이 있다. 예를 들어, ‘sqoop-import-all-tables’을 통해 해당 데이터베이스의 모든 테이블을 읽을 수 있고 기본키를 자동으로 인식할 수 있다.
  4. RDB 종류에 맞는 드라이버(.jar)를 다운받고 sqoop 실행 시 해당 드라이버 파일들을 읽도록 경로 설정을 해준다. 예를 들여, vertica RDB를 사용하려면 vertica JDBC 드라이버를 다운받고 `/var/lib/sqoop` 하위에 위치시킨다.
  5. 큰 테이블을 import할 때 MAP 테스크 개수가 적으면 “GC Overhead limit exceeded error” 에러가 발생할 수 있다. 이 경우에는 dontTrackOpenResources=true 와 useCursorFetch=true 옵션을 `–connect` 인자에 덧붙여주면 된다[9,10]. 또는 sqoop-incremental-imports를 참고하길 바란다[1].
  6. 기본적으로 기본키는 대수 비교가 가능한 숫자형 타입이 적합하다. 텍스트 타입도 가능하지만 `-Dorg.apache.sqoop.splitter.allow_text_splitter=true` 인자를 Sqoop Import CLI에 추가해야 한다.
  7. 만약 테이블에 decimal 타입의 컬럼이 있는 경우 parquet으로 저장하면 정상적으로 로드가 되지 않기도 하다. 이 경우 avro와 같은 다른 타입을 사용하면 해결할 수 있다.
  8. HDFS와 Hive에 데이터를 중복으로 저장하는 셈이다. 참고로 HDFS에 데이터를 저장한 후 Hive의 External Table을 생성하면 Hive에 데이터를 중복으로 저장할 필요가 없다. 그리고 Hive에서 테이블을 Drop해도 실제 데이터는 HDFS에 그대로 존재하게 된다.

5. 참조

  1. (공식) Sqoop User Guide
  2. (공식) Apache Sqoop
  3. (책) 빅데이터 컴퓨팅 기술
  4. Apache Sqoop Tutorial – Learn Sqoop from Beginner to Expert 2019
  5. Managing the Sqoop 1 Client
  6. Sqoop Presentations and Blogs
  7. Apache Sqoop – Part 4: Sqoop best practices
  8. Sqoop on Spark for Data Ingestion
  9. GC Overhead limit exceeded error
  10. MySQL – 큰 테이블을 다루는 jdbc 활용법
  11. Hadoop vs Relational-Databases