티나 개발 노트
.NET Framework 4.5 System.Data.Odbc HiveServer2 :10000

C#에서 Hive 조회·적재하기

Windows 서버에 배포해 Hive 테이블을 만들고(INSERT OVERWRITE / CTAS) 조회하는 C# 예제 두 가지입니다. 실제 배치에는 권장 코드를, 연결이 되는지 먼저 확인할 때는 간단 코드를 쓰면 됩니다.

🔀

회사에 Trino가 있다면 ODBC 대신 NuGet Trino.Ado로도 붙을 수 있습니다. 차이는 Trino.Ado 소개와 ODBC 연결과의 차이에 정리했습니다.

0공통 준비

🛠️

드라이버 설치, DSN 만들기, 인증 방식 고르기는 Hive ODBC 설정 가이드에 단계별로 정리해 두었습니다. 처음이라면 먼저 보고 오세요.

  1. 서버와 개발 PC에 Cloudera ODBC Driver for Apache Hive(또는 Microsoft Hive ODBC Driver)를 설치합니다.
  2. 비트 수를 맞춥니다. 64비트 드라이버를 설치했다면 프로젝트 속성 > 빌드 > 플랫폼 대상을 x64로 둡니다. Any CPU + "32비트 선호"면 32비트로 떠서 "데이터 원본 이름이 없고 기본 드라이버를 지정하지 않았습니다(IM002)" 오류가 납니다.
  3. 연결 문자열의 Driver={...} 이름은 ODBC 데이터 원본 관리자의 드라이버 탭에 보이는 이름(PowerShell Get-OdbcDriver -Name "*Hive*")과 글자까지 같아야 합니다. Microsoft 드라이버라면 {Microsoft Hive ODBC Driver}입니다.
  4. AuthMech는 클러스터 인증 방식에 맞춥니다. 0 = 인증 없음, 2 = 사용자 이름만, 3 = 사용자 이름+비밀번호(LDAP), 1 = Kerberos.

파일은 HiveJob.cs 하나에 클래스 두 개(HiveClient, Program)가 들어 있고, 접속 정보는 App.config에 따로 둡니다. 전일 파티션을 INSERT OVERWRITE로 다시 만들고, 건수를 조회해 확인한 뒤 종료 코드로 성공·실패를 알립니다.

HiveJob.cs

HiveJob.cs
// HiveJob.cs
// 대상: .NET Framework 4.5 콘솔 앱 (C# 5 문법만 사용)
// 참조 추가: System.Configuration  (System.Data 는 기본 참조)
// 실행: HiveJob.exe            -> 전일(어제) 파티션 생성
//       HiveJob.exe 20261026   -> 지정한 날짜 파티션 생성
using System;
using System.Configuration;
using System.Data;
using System.Data.Odbc;
using System.Globalization;

namespace HiveJob
{
    /// <summary>
    /// HiveServer2 에 ODBC 로 접속해 조회/실행하는 작은 래퍼.
    /// 호출마다 연결을 열고 닫는다 (ODBC 연결 풀링이 재사용을 처리).
    /// </summary>
    public sealed class HiveClient
    {
        private readonly string _connectionString;
        private readonly int _commandTimeoutSeconds;

        public HiveClient(string connectionString, int commandTimeoutSeconds)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException("연결 문자열이 비어 있습니다.", "connectionString");

            _connectionString = connectionString;
            _commandTimeoutSeconds = commandTimeoutSeconds;
        }

        /// <summary>SELECT 결과를 DataTable 로 돌려준다. ? 자리에 args 가 순서대로 들어간다.</summary>
        public DataTable Query(string sql, params object[] args)
        {
            using (var conn = new OdbcConnection(_connectionString))
            using (var cmd = CreateCommand(conn, sql, args))
            using (var adapter = new OdbcDataAdapter(cmd))
            {
                var table = new DataTable();
                conn.Open();
                adapter.Fill(table);
                return table;
            }
        }

        /// <summary>INSERT OVERWRITE, CREATE TABLE AS SELECT, SET 등 결과가 없는 문장을 실행한다.</summary>
        public void Execute(string sql, params object[] args)
        {
            using (var conn = new OdbcConnection(_connectionString))
            using (var cmd = CreateCommand(conn, sql, args))
            {
                conn.Open();
                // Hive 드라이버는 영향받은 행 수를 대부분 -1 로 돌려주므로 반환값은 쓰지 않는다.
                cmd.ExecuteNonQuery();
            }
        }

        private OdbcCommand CreateCommand(OdbcConnection conn, string sql, object[] args)
        {
            var cmd = conn.CreateCommand();
            cmd.CommandText = sql;
            cmd.CommandTimeout = _commandTimeoutSeconds;   // 0 이면 무제한

            if (args != null)
            {
                foreach (var value in args)
                {
                    var p = cmd.CreateParameter();
                    p.Value = value ?? DBNull.Value;
                    cmd.Parameters.Add(p);                 // ODBC 는 이름이 아니라 순서로 바인딩
                }
            }
            return cmd;
        }
    }

    internal static class Program
    {
        private static int Main(string[] args)
        {
            try
            {
                var setting = ConfigurationManager.ConnectionStrings["Hive"];
                if (setting == null)
                    throw new ConfigurationErrorsException("App.config 의 connectionStrings 에 'Hive' 항목이 없습니다.");

                int timeout;
                if (!int.TryParse(ConfigurationManager.AppSettings["HiveCommandTimeout"], out timeout))
                    timeout = 3600;

                var hive = new HiveClient(setting.ConnectionString, timeout);

                // 기준일: 인자가 있으면 그 날짜(yyyyMMdd), 없으면 전일
                DateTime baseDate = args.Length > 0
                    ? DateTime.ParseExact(args[0], "yyyyMMdd", CultureInfo.InvariantCulture)
                    : DateTime.Today.AddDays(-1);
                string dt = baseDate.ToString("yyyyMMdd", CultureInfo.InvariantCulture);

                Log("작업 시작 dt=" + dt);

                // 파티션 값은 위에서 DateTime 으로 만든 문자열이라 외부 입력이 섞이지 않는다.
                // (PARTITION 절에는 ? 파라미터를 쓸 수 없어 문자열로 넣는다.)
                string insertSql = string.Format(
                    "INSERT OVERWRITE TABLE mart.daily_sales PARTITION (dt = '{0}') " +
                    "SELECT store_id, SUM(amount) AS total_amount, COUNT(*) AS order_cnt " +
                    "FROM raw.orders " +
                    "WHERE dt = '{0}' " +
                    "GROUP BY store_id", dt);

                hive.Execute(insertSql);
                Log("INSERT OVERWRITE 완료");

                // 일반 조건절 값은 ? 파라미터로 넘긴다.
                DataTable result = hive.Query(
                    "SELECT COUNT(*) AS cnt FROM mart.daily_sales WHERE dt = ?", dt);
                long count = Convert.ToInt64(result.Rows[0][0], CultureInfo.InvariantCulture);
                Log("적재 건수=" + count);

                if (count == 0)
                {
                    Log("적재 건수가 0 건이라 실패로 처리합니다.");
                    return 1;
                }
                return 0;   // 작업 스케줄러에서 0 = 성공
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(Stamp() + " 오류: " + ex);
                return 1;   // 0 이 아니면 실패로 감지
            }
        }

        private static void Log(string message)
        {
            Console.WriteLine(Stamp() + " " + message);
        }

        private static string Stamp()
        {
            return DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
        }
    }
}

App.config

App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <connectionStrings>
    <!-- 드라이버 이름은 ODBC 데이터 원본 관리자 > 드라이버 탭에 보이는 이름과 정확히 같아야 합니다. -->
    <add name="Hive"
         connectionString="Driver={Cloudera ODBC Driver for Apache Hive};Host=hive-server01;Port=10000;Schema=default;HiveServerType=2;AuthMech=3;UID=etl_user;PWD=********;" />
    <!-- DSN 을 미리 만들어 둔 경우: connectionString="DSN=HiveProd;UID=etl_user;PWD=********;" -->
  </connectionStrings>
  <appSettings>
    <!-- 명령 타임아웃(초). 대용량 INSERT OVERWRITE 는 넉넉하게. 0 = 무제한 -->
    <add key="HiveCommandTimeout" value="3600" />
  </appSettings>
</configuration>

코드 설명

HiveClient.Query

SELECT 결과를 DataTable로 받아옵니다. 연결은 호출마다 열고 using으로 바로 닫습니다.

HiveClient.Execute

INSERT OVERWRITE, CTAS, SET처럼 결과 집합이 없는 문장용입니다. Hive 드라이버는 영향받은 행 수를 보통 -1로 주기 때문에, 적재 결과는 따로 COUNT로 확인합니다.

CommandTimeout

기본 30초로는 MapReduce/Tez 작업이 끝나기 전에 끊깁니다. App.config의 HiveCommandTimeout(초)으로 조절하고 0이면 무제한입니다.

? 파라미터

ODBC는 이름이 아니라 순서로 바인딩합니다. 다만 PARTITION (dt=…) 절에는 파라미터를 쓸 수 없어서, 코드에서 DateTime으로 만든 값만 문자열로 넣습니다.

기준일 인자

인자 없이 실행하면 전일, HiveJob.exe 20261026처럼 주면 그 날짜를 다시 적재합니다. 재처리할 때 편합니다.

종료 코드 0 / 1

성공이면 0, 예외가 나거나 적재 건수가 0이면 1을 돌려줍니다. 작업 스케줄러의 "마지막 실행 결과"나 배치 툴에서 실패를 바로 잡아냅니다.

💡

CTAS로 바꾸려면 insertSql을 CREATE TABLE mart.daily_sales_tmp STORED AS ORC AS SELECT …로 바꾸고 hive.Execute로 실행하면 됩니다. 이미 있는 테이블이면 먼저 DROP TABLE IF EXISTS를 실행하세요. 테이블·컬럼 이름(mart.daily_sales, raw.orders)은 예시이니 실제 이름으로 바꿔 주세요.

2간단 코드 연결 테스트용

콘솔 앱 Program.cs 하나로 끝납니다. 연결 문자열을 코드에 직접 쓰고, 테이블 10행을 읽어 탭으로 구분해 출력합니다. 드라이버 설치와 방화벽, 계정이 맞는지 처음 확인할 때 쓰기 좋습니다.

Program.cs
// Program.cs  (.NET Framework 4.5 콘솔 앱, 추가 참조 없음)
using System;
using System.Data.Odbc;

class Program
{
    static void Main()
    {
        string connStr =
            "Driver={Cloudera ODBC Driver for Apache Hive};" +
            "Host=hive-server01;Port=10000;Schema=default;" +
            "HiveServerType=2;AuthMech=3;UID=etl_user;PWD=********;";

        using (var conn = new OdbcConnection(connStr))
        {
            conn.Open();

            using (var cmd = new OdbcCommand("SELECT * FROM sample_table LIMIT 10", conn))
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    var values = new object[reader.FieldCount];
                    reader.GetValues(values);
                    Console.WriteLine(string.Join("\t", values));
                }
            }
        }

        Console.WriteLine("완료. 아무 키나 누르세요.");
        Console.ReadKey();
    }
}

코드 설명

3두 방식의 차이점

항목간단 코드권장 코드
목적연결 확인, 빠른 조회Windows 서버에서 도는 정기 적재 배치
파일 구성Program.cs 1개HiveJob.cs 1개 + App.config
접속 정보코드에 하드코딩 (바꾸면 재빌드)App.config (서버별로 파일만 수정)
코드 구조Main 안에 모두 작성HiveClient로 접속 로직 분리, Main은 작업 흐름만
조회 방식DataReader로 한 행씩 출력DataTable로 받아 후속 처리 가능
데이터 생성없음 (SELECT만)INSERT OVERWRITE / CTAS 실행 후 건수 검증
타임아웃기본 30초설정값 (기본 3600초, 0 = 무제한)
파라미터없음? 순서 바인딩 지원
날짜 처리없음기본 전일, 인자로 재처리 날짜 지정
오류 처리없음 (예외 시 프로그램 중단)try/catch + 오류 로그를 stderr로
로그결과만 출력시각이 붙은 단계별 로그
종료 코드항상 0성공 0 / 실패 1 (스케줄러가 실패 감지)
무인 실행ReadKey에서 멈춤가능

4Windows 서버 배포 체크

  1. 빌드 결과 폴더의 HiveJob.exe와 HiveJob.exe.config(App.config가 빌드 시 이 이름으로 바뀜)를 함께 복사합니다.
  2. 서버에도 개발 PC와 같은 비트 수의 Hive ODBC 드라이버를 설치합니다.
  3. 서버에서 HiveServer2 호스트의 10000 포트로 나갈 수 있는지 확인합니다. PowerShell: Test-NetConnection hive-server01 -Port 10000
  4. 작업 스케줄러에 등록하고, "마지막 실행 결과"가 0x0이면 성공, 0x1이면 실패입니다. 로그를 남기려면 동작을 cmd /c HiveJob.exe >> C:\logs\hivejob.log 2>&1로 등록합니다.
⚠️

App.config의 비밀번호는 평문입니다. 운영에서는 서버 접근 권한을 제한하거나 DSN에 저장하는 방식을 검토하세요.

🛠️ 연결이 안 되나요?Hive ODBC 설정 가이드 · 자주 나는 오류 →