[MFC] Parquet 파일 생성(Apache Arrow 기반)

반응형

이번 포스팅에서는 Apache Arrow를 정적 라이브러리 형태로 MFC 프로젝트에 연동하여 CSV 파일을 읽고, 이를 Parquet 파일로 zstd 압축을 통해 저장하는 구체적인 예제를 제공합니다. 반대로, 압축된 Parquet 파일을 다시 읽어오는 예제까지 실습하여 데이터를 효율적으로 처리하는 방법을 단계별로 안내드릴 예정입니다.

1. Apache Arrow란?

Apache Arrow는 컬럼 기반 메모리 데이터 표현을 표준화한 오픈소스 프로젝트로, 데이터를 효율적으로 처리하고 다양한 언어 간 빠른 데이터 교환을 지원합니다. 데이터 분석 성능을 크게 향상시키며, 특히 데이터가 많은 환경에서 빛을 발하는 라이브러리입니다.
Apache Arrow는 Parquet, CSV 등 여러 데이터 형식의 파일을 효과적으로 다룰 수 있도록 설계되었습니다.

Arrow는 내부적으로 다음과 같은 주요 패키지 및 외부 의존성을 포함하고 있습니다.

  • Parquet: 컬럼 기반의 파일 형식으로, 압축률이 뛰어나고 쿼리 성능이 우수합니다.
  • zstd 압축 라이브러리: Parquet 파일 저장 시 데이터를 zstd 포맷으로 압축하여 저장하면 더 작은 파일 크기와 빠른 데이터 접근 속도를 보장합니다.

자세한 내용은 다음 경로에서 확인 가능합니다.

https://arrow.apache.org/

 

Apache Arrow

The universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics

arrow.apache.org

 

2. What is Parquet? What are its differences compared to the CSV format?

Parquet는 컬럼 기반의 저장 포맷으로, 빅데이터 환경에서 특히 많이 사용되는 데이터 파일 형식입니다. 

Parquet 파일은 데이터를 컬럼 단위로 저장하기 때문에 열 기반 분석 및 압축 효율성이 뛰어나며, 쿼리 속도 또한 매우 빠릅니다.
반면, CSV(Comma-Separated Values) 파일은 행 기반 저장 방식으로 데이터를 단순히 쉼표로 구분하여 저장합니다.

CSV는 단순하고 직관적이지만, 대규모 데이터 처리에서는 성능 저하가 발생할 수 있습니다.

특징 Parquet CSV
데이터 저장 방식 컬럼(columnar) 기반 저장 행(row) 기반 저장
성능 및 속도 빠른 쿼리 속도, 높은 압축률 느린 쿼리 속도, 낮은 압축률
용도 빅데이터, 분석 작업에 최적화된 환경에 추천 작고 단순한 데이터에 적합

 

3. Building Arrow C++

윈도우기반 C++용 Arrow를 빌드해봅니다. 빌드에 대한 자세한 내용은 다음의 링크에서 확인하실 수 있습니다.

윈도우에서 Arrow 라이브러리를 사용하는 방법은 크게 vcpkg를 이용하여 참조하는 방법과 cmake로 컴파일하여 프로젝트에 라이브러리 파일을 포함하는 방법이 있습니다.

제가 cmake 컴파일 방식을 3일넘게 시도하다가 포기하고 vcpkg로 install 후 integrate install을 하였습니다. 의존성 걸린 모듈이 여러개 있는데 모든 모듈을 MT_StaticLibrary 방식으로 빌드되도록 /MT 옵션을 주어도 arrow가 자체 빌드하는 몇몇 모듈들은 /MT 적용이 안되고 /MD로만 빌드가 되어 직접 빌드하는 것은 포기하였습니다.

 

https://arrow.apache.org/docs/developers/cpp/building.html

 

Building Arrow C++ — Apache Arrow v19.0.1

By default, the C++ build system creates a fairly minimal build. We have several optional system components which you can opt into building by passing boolean flags to cmake. Some features of the core Arrow shared library can be switched off for improved b

arrow.apache.org

3.1. System setup

Arrow 소스코드 내의 예제파일 등을 참조하실 분은 다음과 같이 git에서 arrow 소스코드를 받아보시면 됩니다.

 

D:\git>git clone https://github.com/apache/arrow.git
Cloning into 'arrow'...
remote: Enumerating objects: 260054, done.
remote: Counting objects: 100% (96/96), done.
remote: Compressing objects: 100% (66/66), done.
remote: Total 260054 (delta 57), reused 32 (delta 30), pack-reused 259958 (from 2)
Receiving objects: 100% (260054/260054), 203.69 MiB | 10.84 MiB/s, done.
Resolving deltas: 100% (183999/183999), done.
Updating files: 100% (5677/5677), done.

 

Arrow를 빌드하려면 의존성 걸려있는 패키지 라이브러리들이 필요한데 윈도우에서는 vcpkg로 관리하는게 편하므로 vcpkg가 설치되어 있는지 확인합니다. 

// vcpkg 여부 확인
D:\>where vcpkg
H:\vcpkg\vcpkg.exe
D:\>

없으면 vcpkg를 다운받고 설치스크립트를 실행합니다.

vcpkg가 visual studio 경로에 번들설치된게 있는데 저처럼 별도의 vcpkg를 사용하고 싶으면

PowerShell에서 환경변수 VCPKG_ROOT를 수정합니다.

// 환경변수 설정 예제(Powershell)
> SETX VCPKG_ROOT D:\vcpkg

VS Prompt는 VCPKG_ROOT값이 Visual Stduio 경로로 덮어쓰기 되므로 CMD창 실행시 set 명령으로 VCPKG_ROOT를 수정하고 시작합니다.

그리고 어떤 vcpkg를 참조하는지 확신이 안서면 vcpkg install 명령 실행시 다음과 같이 " --vcpkg-root" 옵션으로 vcpkg 경로를 입력해줍니다. 그렇지 않으면 다음과 같이 두 경로의 vcpkg 충돌 경고가 발생합니다.

warning: The vcpkg H:\vcpkg\vcpkg.exe is using detected vcpkg root H:\vcpkg and ignoring mismatched VCPKG_ROOT environment value C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg. To suppress this message, unset the environment variable or use the --vcpkg-root command line switch.

 

3.2. Building

윈도우 기본 CMD창이 아닌 Visual Studio Developer Command Prompt를 띄워서 작업해야 합니다.

Arrow 루트 경로의 cpp 폴더로 들어가서 폴더를 하나 생성하고 그 안에서 cmake 명령으로 비주얼스튜디오 프로젝트를 생성합니다. 이 때 사용할 컴포넌트에 대한 옵션을 켜줍니다. 저는 Parquet, CSV 를 켜고 압축기능도 전부 켰습니다.

 

cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE=H:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=../install -DARROW_BUILD_SHARED=OFF -DARROW_BUILD_STATIC=ON -DARROW_USE_STATIC_CRT=ON -DARROW_DEPENDENCY_SOURCE=VCPKG -DARROW_BUILD_TESTS=OFF -DCMAKE_UNITY_BUILD=ON -DMSVC_LINK_VERBOSE=ON -DVCPKG_TARGET_TRIPLET=x64-windows-static -DARROW_DEPENDENCY_USE_SHARED=OFF -DARROW_JSON=ON -DARROW_FILESYSTEM=ON -DARROW_CSV=ON -DARROW_PARQUET=ON -DARROW_DATASET=ON -DARROW_WITH_SNAPPY=ON -DARROW_WITH_ZLIB=ON -DARROW_WITH_BZ2=ON -DARROW_WITH_BROTLI=ON -DARROW_WITH_LZ4=ON -DARROW_WITH_ZSTD=ON

// triplet을 x64-windows-static로 지정해도 x64-windows(shared)도 생성됩니다.
E:\git\arrow\cpp\build>cmake .. -G "Visual Studio 17 2022" -A x64 -DCMAKE_TOOLCHAIN_FILE=H:\vcpkg\scripts\buildsystems\vcpkg.cmake -DCMAKE_INSTALL_PREFIX=../install -DARROW_BUILD_SHARED=OFF -DARROW_BUILD_STATIC=ON -DARROW_USE_STATIC_CRT=ON -DARROW_DEPENDENCY_SOURCE=VCPKG -DARROW_BUILD_TESTS=OFF -DCMAKE_UNITY_BUILD=ON -DMSVC_LINK_VERBOSE=ON -DVCPKG_TARGET_TRIPLET=x64-windows-static -DARROW_DEPENDENCY_USE_SHARED=OFF -DARROW_JSON=ON -DARROW_FILESYSTEM=ON -DARROW_CSV=ON -DARROW_PARQUET=ON -DARROW_DATASET=ON -DARROW_WITH_SNAPPY=ON -DARROW_WITH_ZLIB=ON -DARROW_WITH_BZ2=ON -DARROW_WITH_BROTLI=ON -DARROW_WITH_LZ4=ON -DARROW_WITH_ZSTD=ON
-- Building using CMake version: 3.31.7
-- Using vcpkg to find dependencies
-- Using CMAKE_TOOLCHAIN_FILE: H:\vcpkg\scripts\buildsystems\vcpkg.cmake
-- Using VCPKG_ROOT: H:/vcpkg
-- vcpkg.json manifest found. Using VCPKG_MANIFEST_MODE: ON
-- Using vcpkg installed libraries directory:
-- Using VCPKG_TARGET_TRIPLET: x64-windows-static
-- Using VCPKG_BUILD_TYPE: release
-- Using VCPKG_LIBRARY_LINKAGE: static
-- Running vcpkg install
Detecting compiler hash for triplet x64-windows...
Compiler found: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/bin/Hostx64/x64/cl.exe
Detecting compiler hash for triplet x64-windows-static...
Compiler found: C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.43.34808/bin/Hostx64/x64/cl.exe
The following packages will be built and installed:

 

프로젝트 생성이 완료되었으면 Arrow 소스코드를 빌드하여 라이브러리 파일을 생성할 차례입니다. 

// Debug 라이브러리 생성
cmake --build . --config Debug--target INSTALL

// Release 라이브러리 생성
cmake --build . --config Release --target INSTALL

 

3.3. Arrow Install

vcpkg로 Arrow를 설치하려면 다음과 같이 하면 됩니다.

vcpkg 공지사항에 따르면 visual studio를 설치할 당시에 번들로 설치한 vcpkg를 이용할 경우에만 vcpkg integrate install 명령으로 모든 visual studio가 자동으로 헤더파일과 라이브러리 링크 기능을 사용할 수있고 별도로 설치한 vcpkg는 헤더파일 경로와 라이브러리파일 경로를 수동으로 설정해줘야 한다고 합니다.

> git clone https://github.com/Microsoft/vcpkg.git
> cd vcpkg
> bootstrap-vcpkg.bat

// Visual studio 경로의 기본 vcpkg를 사용할 경우에만 유효한 명령
> vcpkg integrate install

// 기본적으로 triplet x64-windows 가 적용되며 동적빌드버전(MD_Dynamic)
> vcpkg install arrow

// triplet이 x64-windows-static 적용되며 MT_Static
> vcpkg install arrow:x64-windows-static

 

vcpkg로 설치한 패키지들은 다음의 경로에 존재합니다.

// debug 모드 파일
%VCPKG_ROOT%\installed\<triplet>\debug\lib

// release 모드 파일
%VCPKG_ROOT%\installed\<triplet>\lib

4. Linking Arrow C++

라이브러리 파일이 생성되었으면 MFC 프로젝트에 포함할 차례입니다.

먼저 생성된 라이브러리 파일을 확인하겠습니다.

bundled의 의미는 arrow 빌드에 필요한 패키지를 찾지 못하여 arrow가 내장하고 있는 번들 라이브러리를 사용했다는 의미입니다. vcpkg install로 모든 라이브러리 파일을 생성해 두었으므로 해당 라이브러리가 사용되어야 설정한 CRT 모드가 일치하고 그렇지 않을 경우에는 CRT 모드가 충돌하여 링크 에러가 발생할 수 있습니다.

cmake build 명령 실행 전 "-DARROW_DEPENDENCY_SOURCE=VCPKG"로 설정되었는지 확인해야 하고 ARROW_DEPENDENCY_SOURCE 값이 SYSTEM 이거나 기타 다른값이면 cmake config 단계에서 vcpkg 경로를 찾지 못하여 기본값으로 설정된 것입니다. 이 경우에는 Cmake가 vcpkig toolchain file을 인식하지 못한 것이므로  CMake 호출 시 "-DCMAKE_TOOLCHAIN_FILE=<경로>" 를 추가로 설정해줍니다. 이 때 CMake 캐시 파일이 이미 생성되어 있으면 설정이 바뀌지 않으므로 CMakeCache.txt를 지우거나 빌드 폴더를 통째로 삭제합니다.

 

Arrow c++ library files

Debug/Release 각각 생성하였는데 Debug 모드의 arrow_static.lib 사이즈가 750MB나 됩니다.

헤더파일은 396개가 생성되었고 용량은 5MB정도 됩니다.

4.1. 소스 트리 구성

다음과 같이 프로젝트의 하위 폴더에 파일들을 복사 합니다. 

prebuilt/arrow

폴더 트리 구조는 다음과 같습니다.

folder tree

4.2. Include Directory 설정

프로젝트 속성창 Configuration Properties > C/C++ > General > Additonal Include Directories에 헤더파일 폴더 위치를 추가합니다.

 

4.3. 전처리기 정의 추가

정적 라이브러리로 Arrow를 사용하려면 Arrow 헤더 파일이 DLL 가져오기(dllimport/dllexport) 지시어 없이 올바르게 작동하도록 하기 위해 ARROW_STATIC 매크로를 정의해야 합니다. Arrow Flight 모듈을 사용할거면 ARROW_FLIGHT_STATIC과 ARROW_FLIGHT_SQL_STATIC도 정의하라고 하는데 저는 그냥 다 했습니다.

프로젝트 속성창을 띄워서 Configuration Properties > C/C++ > Preprocessor 항목의 Preprocessor Definitions에 다음 내용을 추가합니다.

ARROW_STATIC;ARROW_FLIGHT_STATIC;ARROW_FLIGHT_SQL_STATIC

전처리기 정의 추가

4.4. 라이브러리 경로 및 종속성 추가

빌드의 결과물로 lib 폴더에 생성된 라이브러리 파일들을 프로젝트에 링크하도록 설정해야 합니다.

프로젝트 속성창 Configuration Properties > Linker > General 항목의 Additional Library Directories에 Arrow 라이브러리 파일이 있는 폴더의 경로를 추가합니다.

라이브러리 경로 추가(Debug/Release 구분)

 

Linker > Input 항목으로 이동하여 Additional Dependencies 항목에 라이브러리 파일 이름들을 추가합니다.

Additional Dependencies

 

4.5. 버전 출력 테스트 코드 작성

다음과 같이 빌드정보를 출력해보았습니다.

#include <arrow/api.h>

arrow::BuildInfo stArrow = arrow::GetBuildInfo();

DPRINTF("[INFO] Arrow Build(%s) Version(%d)\nArrow compiler id(%s) version(%s) flags(%s)\nArrow git_id(%s) git_desc(%s) git_package(%s)\n",
	(LPCTSTR)stArrow.build_type.c_str(), stArrow.version, (LPCTSTR)stArrow.compiler_id.c_str(), (LPCTSTR)stArrow.compiler_version.c_str(), (LPCTSTR)stArrow.compiler_flags.c_str(), (LPCTSTR)stArrow.git_id.c_str(), (LPCTSTR)stArrow.git_description.c_str(), (LPCTSTR)stArrow.package_kind.c_str());

arrow::GetBuildInfo

4.6. 링크 오류 해결

arrow 헤더파일 추가시 몇 가지 매크로가 타 서드파티 라이브러리의 매크로와 동일한 이름으로 충돌이 발생할 수 있습니다. 저의 경우에는 ta-lib 등의 매크로와 이름이 같아서 충돌이 발생했습니다. 이 때에는 같은 .cpp 파일에서 두 라이브러리의 헤더파일을 참조하지 않도록 .cpp 파일을 분리하는 것이 #pragma 지시어를 사용하지 않고 가장 간단한 해결방법입니다.

 

다음과 같이 max 매크로 충돌이 발생할 경우에는 기본 min/max 를 사용하지 않도록 합니다.

// max 매크로 충돌 발생시 프로젝트 전체에서 Windows의 min/max 매크로를 막습니다.
constexpr int64_t kBinaryMemoryLimit = std::numeric_limits<int32_t>::max() - 1;

// 프로젝트 속성 C/C++ > Preprocessor > Preprocessor Definitions에 "NOMINMAX" 추가
// 또는 "afxwin.h" 파일을 include 하는 코드 최상단에 가서 다음 코드 추가
// MFC signle doc project 에서는 framework.h 파일에 있습니다.
#ifndef NOMINMAX
#  define NOMINMAX
#endif
#include <afxwin.h>

 

pkg-config 오류 발생시 "PKG_CONFIG_PATH"에 경로 설정을 해줍니다.

// 다음과 같은 pkgconf 오류 발생시
pkg-config package for libzstd that is used by arrow for static link isn't found
// 위 오류 무시하고 진행시 링킹단계에서 undefined refer 오류 발생. 라이브러리 파일이 깡통으로 생성됩니다.

// debug 용 라이브러리 경로
> set PKG_CONFIG_PATH=vcpkg_installed\x64-windows-static\debug\lib\pkgconfig

// release용 라이브러리 경로
> set PKG_CONFIG_PATH=vcpkg_installed\x64-windows-static\lib\pkgconfig

 

5. Parquet 파일 생성

메모리상의 데이터를 Apache Arrow의 columnar 형식으로 변환하여 Paruqet 파일로 저장해봅니다.

Apache Arrow는 데이터를 효율적으로 처리하기 위해 columnar 형식으로 데이터를 표현합니다.

이는 행 기반 형식과 달리 각 칼럼의 데이터가 연속적인 메모리 블록에 저장되어 있어, 특정 컬럼에 대한 연산을 매우 빠르게 수행할 수 있도록 합니다. Parquet은 이러한 columnar 데이터를 저장하기 위한 효율적인 파일 포맷입니다.

데이터 저장 과정은 다음과 같습니다.

// data structure 예시
typedef struct record_s {
    int32_t varA;
    int64_t varB;
} TST_RECORD;
  1. Arrow Array Builder  생성
  2. Builder를 Arrow Array로 변환
  3. Arrow Schema 정의
  4. Arrow Table 생성
  5. Parquet Writer 옵션 설정
  6. 파일 스트림에 기록 후 저장
#include <memory>
#include <vector>
#include <string>
#include <arrow/api.h>
#include <arrow/io/api.h>
#include <parquet/arrow/writer.h>

// Arrow Array Builder 생성
arrow::Int32Builder builder_a;
arrow::Int64Builder builder_b;

// builder에 메모리 데이터 저장
for (const auto& rec : data) {
    builder_a.Append(rec.a);
    builder_b.Append(rec.b);
}

// Builder를 실제 Array로 변환
 std::shared_ptr<arrow::Array> array_a, array_b;
 builder_a.Finish(&array_a);
 builder_b.Finish(&array_b);
 
 // Schema 정의 (필드 이름과 타입)
 auto schema = arrow::schema({
 	arrow::field("col_a", arrow::int32()),
    arrow::field("col_b", arrow::int64())
 });
 
 // Arrow Table 생성
 auto table = arrow::Table::Make(schema, { array_a, array_b });
   
 // Parquet Writer 옵션(압축 지정)
 parquet::WriterProperties::Builder prop_builder;
 prop_builder.compression(parquet::Compression::ZSTD);
 std::shared_ptr<parquet::WriterProperties> props = prop_builder.build();
 
 // 파일 출력 스트림 열기
 std::shared_ptr<arrow::io::FileOutputStream> outfile;
 PARQUET_ASSIGN_OR_THROW(outfile, arrow::io::FileOutputStream::Open(filename));

// Table 전체 Flush
const int64_t chunk_size = 1024;
PARQUET_THROW_NOT_OK(parquet::arrow::WriteTable(*table, arrow::default_memory_pool(),
outfile, chunk_size, props));
outfile->Close();
반응형