📚 SQL Struct: 데이터베이스가 책장처럼 생각하는 법을 배울 때

SQL Struct: 데이터베이스가 책장처럼 생각하는 법을 배울 때 데이터베이스를 거대한 도서관이라고 생각해보자. 지금까지 수십 년 동안, 우리는 전통적인 도서관 목록 시스템처럼 데이터를 정리해왔다. 모든 책의 정보를 각각 다른 카드에 적어서 별도의 서랍에 보관하는 방식 말이다. 제목 카드는 여기, 저자 카드는 저기, 출판연도는 또 다른 곳에. 이게 바로 데이터베이스에서 말하는 **정규화(normalization)**다. 그런데 만약 도서관이 책의 모든 정보를 하나의 똑똑한 봉투에 담을 수 있다면 어떨까? 제목, 저자, 출판 정보, 심지어 리뷰까지 모두 깔끔하게 한 곳에 모아둘 수 있다면? 이게 바로 현대 SQL 데이터베이스에서 struct가 해주는 일이다. ...

6월 17, 2025

💯 dbt 테스트 이해하기: 기본부터 중급까지

dbt로 데이터를 변환하고 있다면 이미 잘하고 있는 겁니다. 🙌 그런데 dbt에는 데이터를 더 깨끗하고 신뢰할 수 있게 유지하는 강력한 테스트 기능도 있다는 거, 알고 계셨나요? 이번 글에서는 다음을 다룹니다: ✅ 기본 dbt 테스트 — 빠르게 적용 가능한 기본기 🚀 중급 테스트 — 사용자 정의 로직과 재사용 가능한 매크로 ✅ 기본 dbt 테스트 (Built-in) dbt는 모델의 .yml 파일 안에서 바로 사용할 수 있는 기본 테스트들을 제공합니다. 예시: version: 2 models: - name: customers description: 고객 마스터 테이블 columns: - name: customer_id tests: - not_null - unique - name: email tests: - not_null 🔧 각 테스트가 하는 일: not_null: 컬럼에 NULL 값이 없는지 확인 unique: 값이 고유한지 검증 accepted_values: 허용된 값만 포함되어 있는지 체크 relationships: 외래 키가 참조 대상 테이블과 매칭되는지 확인 accepted_values 예시 - name: status tests: - accepted_values: values: ['active', 'inactive', 'suspended'] 이런 기본 테스트들은 생산 대시보드에 오류가 반영되기 전에 단순한 데이터 이상을 빠르게 잡을 수 있습니다. ...

6월 10, 2025

PostgreSQL의 의외의 함정: Boolean, 텍스트 I/O, 그리고 ETL 이슈

PostgreSQL은 강력하고 표준을 잘 따르는 데이터베이스입니다. 하지만 의외의 작은 함정들도 있죠. 그중 하나는 바로 boolean 값을 다루는 방식, 특히 데이터를 텍스트 형식으로 내보낼 때의 이야기입니다. 🧠 PostgreSQL의 Boolean 처리 방식: 생각과 다르다 PostgreSQL은 boolean 값을 내부적으로 1비트(bit) 만으로 효율적으로 저장합니다. 예상한 대로죠. 하지만 그 값을 텍스트로 변환하거나 COPY 같은 방식으로 내보내면, 결과는 좀 다릅니다: SELECT true::text; -- 결과: 't' SELECT false::text; -- 결과: 'f' 맞습니다 — true는 't', false는 'f' 로 표현됩니다. 이건 PostgreSQL의 텍스트 I/O 기본 동작 방식인데, 이 동작 때문에 시스템 간 데이터를 주고받을 때 미묘한 버그가 생기기도 합니다. ...

6월 10, 2025

Spark → Kafka → Postgres 파이프라인에서 UUID가 터트린 지뢰

Kafka와 Spark Structured Streaming을 이용해서 데이터 파이프라인을 구축하고 있었습니다. 완전히 컨테이너화된 시스템. 스택 구성은 이렇습니다: Kafka → 거래 데이터를 스트리밍으로 전송 Spark Structured Streaming → 실시간 처리 및 이상 거래 탐지 Postgres → 데이터 웨어하우스 모든 게 순조로웠습니다. 그런데 갑자기 등장한 한 놈. UUID 필드. 맞습니다 — UUID. 이제 어떤 일이 벌어졌는지 정확히 보여드릴게요. ✅ 원래 설계 Postgres 테이블을 이렇게 설계했죠: CREATE TABLE fact_transaction ( transaction_id UUID PRIMARY KEY, customer_id UUID REFERENCES dim_customer(customer_id), merchant_id UUID REFERENCES dim_merchant(merchant_id), ... ); Kafka는 UUID를 문자열로 직렬화해서 이벤트를 잘 뿌려주고 있었습니다 (JSON은 원래 UUID 타입이 없으니까요). ...

6월 7, 2025

🛡️ Spark Docker 스트리밍에서 Kerberos 사용자 인증 문제 해결

Spark, Kafka, Docker를 사용하여 실시간 스트리밍 파이프라인을 구축하던 중, Kerberos를 사용하지도 않았는데 Kerberos 인증과 관련된 Spark 오류가 발생했습니다. org.apache.hadoop.security.KerberosAuthException: failure to login: javax.security.auth.login.LoginException: java.lang.NullPointerException: invalid null input: name ❓ 문제가 발생한 원인은? 공식 apache/spark:3.5.0 Docker 이미지를 사용하고 있었습니다. Docker 내부의 Spark가 Hadoop의 기본 인증 메커니즘을 해결하려고 시도했습니다. Hadoop은 다음을 통해 현재 OS 사용자를 검색하려고 했습니다: UnixPrincipal(name) Docker 컨테이너 내부에서 앱이 적절한 사용자명 매핑이 없는 UID/GID로 실행되고 있었습니다. 이로 인해 다음과 같은 오류가 발생했습니다: invalid null input: name UnixPrincipal()이 null을 받았기 때문입니다. ...

6월 7, 2025

🫙 Spark Streaming의 마지막 장애물: 왜 --jars 만으로 Kafka가 안 될까

데이터 엔지니어로서 복잡한 분산 시스템을 구축하면서 마지막 장애물을 넘었을 때 느끼는 성취감은 정말 특별합니다. 오늘은 Apache Spark Structured Streaming + Kafka를 사용할 때 굉장히 답답한 문제를 공유하려고 합니다: 👉 바로 악명 높은 Failed to find data source: kafka 에러입니다. 🧨 문제: 모든 게 정상인데 Kafka만 안 된다 상황을 떠올려봅시다: Spark 클러스터 정상 구동 Postgres 연결도 문제 없음 Kafka에서 이벤트도 잘 발행됨 코드에서 .readStream.format("kafka") 호출 그런데 갑자기 다음과 같은 에러가 발생: ...

6월 7, 2025

🧚 왜 dbt를 Airflow Docker 컨테이너 안에서 실행하는가

데이터 엔지니어링 파이프라인에서 dbt와 Airflow는 종종 함께 사용된다. 여기서 자주 마주하는 설계 결정이 있다: dbt를 Airflow와 어떻게 함께 실행할 것인가? dbt를 별도의 컨테이너에서 실행하고 API나 CLI 호출로 오케스트레이션할 것인가? 아니면 dbt를 Airflow의 Docker 컨테이너 안에서 직접 실행할 것인가? 둘 다 실험해본 결과, dbt를 Airflow 컨테이너 안에서 실행하는 방식을 선호한다. 이유는 다음과 같다. ✅ 하나의 컨테이너 = 하나의 환경 Airflow DAG가 동일한 컨테이너 안에서 dbt 명령어를 직접 실행한다. 이를 통해 다음이 보장된다: 동일한 Python 버전 동일한 dbt 버전 동일한 의존성 버전 (dbt 패키지, 어댑터) 컨테이너 간 네트워킹 이슈 없음 별도의 컨테이너로 분리할 경우 다음을 관리해야 한다: ...

6월 4, 2025

🧹 데이터 클렌징: 왜 스테이징 레이어에서 클렌징을 해야 하는가

실제 데이터 엔지니어링 파이프라인에서 가장 흔한 실수 중 하나는 데이터 클렌징을 너무 뒤로 미루는 것입니다. 업스트림 데이터가 깨끗할수록 다운스트림 모델은 더 단순하고 유지보수하기 쉬워집니다. 이제 하나씩 살펴보겠습니다. ✅ 기본 원칙 가능한 한 가장 이른 단계에서 데이터를 클렌징 — 이상적으로는 스테이징 레이어에서 처리해야 합니다. ✅ 그 이유 1️⃣ 책임 분리의 명확성 스테이징 모델의 역할은 다음과 같습니다: 클렌징 표준화 (정규화) 타입 캐스팅 만약 더러운 데이터를 marts까지 (예: dim_customer, fct_transaction) 넘긴다면: 다운스트림 모델이 복잡해짐 중복 필터 발생 유지보수가 어려운 취약한 코드 생성 2️⃣ Marts 레이어는 비즈니스 로직만 담당해야 한다 Marts 레이어는 오직 다음에 집중해야 합니다: ...

6월 4, 2025

🐳 GitHub Pages용 Jekyll 블로그를 Docker로 세팅하기 — 삽질 끝에 완성한 깔끔한 구성

😩 문제 Jekyll을 Docker로 세팅하는 건 쉬워 보였지만, 실제로는 여러 문제를 겪었습니다: 플랫폼 이슈 (arm64 vs amd64) - Apple Silicon Macbook (M1)을 사용중 bundle install에서 발생하는 오류들 개인 GitHub Pages 사이트용으로 만들고 있었기 때문에, GitHub Pages에서 사용하는 gem 버전과 호환되면서도 로컬에서 개발하기 쉽게 유지해야 했습니다. 🛠 나의 깔끔한 해결책 결국 이 Docker 세팅을 만들게 되었습니다. 저에게는 잘 작동합니다. ✅ Key Features Apple Silicon (M1/M2)와 Intel 모두에서 동작 깔끔하게 다시 빌드 가능한 이미지 볼륨 마운트를 통한 안정적인 파일 동기화 GitHub Pages의 Jekyll 버전과 호환 (선택 사항) livereload 지원 🐳 The Dockerfile FROM ruby:3.2.3-slim RUN apt-get update -qq && \ apt-get install -y build-essential libpq-dev nodejs npm RUN gem install bundler -v 2.6.9 WORKDIR /srv/jekyll COPY Gemfile* ./ RUN bundle install COPY . . EXPOSE 4000 CMD ["bundle", "exec", "jekyll", "serve", "--host", "0.0.0.0", "--force_polling", "--livereload"] 🐳 docker-compose.yml services: site: image: my-jekyll platform: linux/arm64 command: bundle exec jekyll serve --host 0.0.0.0 --force_polling --livereload ports: - "4000:4000" - "35729:35729" volumes: - .:/srv/jekyll - ./vendor/bundle:/usr/local/bundle working_dir: /srv/jekyll environment: - JEKYLL_ENV=development 💎 Gemfile source "https://rubygems.org" gem "jekyll", "~> 4.3.3" gem "csv", "~> 3.3.5" gem "base64", "~> 0.2.0" gem "logger", "~> 1.6.0" group :jekyll_plugins do gem "jekyll-feed", "~> 0.17.0" gem "jekyll-seo-tag", "~> 2.8.0" end # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem # and associated library. platforms :mingw, :x64_mingw, :mswin, :jruby do gem "tzinfo", ">= 1", "< 3" gem "tzinfo-data" end # Performance-booster for watching directories on Windows gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem # do not have a Java counterpart. gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] # Add webrick as it's no longer bundled with Ruby gem "webrick", "~> 1.8" Gemfile.lock도 레포에 포함되어 있습니다. ...

6월 3, 2025

🔧 Airflow Docker 스타트 문제 해결

Airflow를 Docker로 운영하면서 자주 마주치는 이슈를 정리 ❗ 문제 1 — .env 파일이 Airflow 컨테이너 안에서 안 보인다 🔍 증상 요약 .env 파일은 프로젝트 루트에 존재. 하지만 Airflow 컨테이너 안에서는 load_dotenv()가 읽지 못함. 이유: Docker는 .env 파일을 환경변수로 변환해 넘기긴 하지만, 파일 자체를 컨테이너 내부로 복사하거나 mount하지 않는다. 결국 load_dotenv()가 찾을 파일이 없음. ✅ 해결법 1️⃣ docker-compose.yml에 volume mount 추가 services: airflow: ... volumes: - ./dags:/opt/airflow/dags - ./.env:/opt/airflow/dags/.env # ✅ 이 줄 추가 이렇게 하면 .env 파일이 Airflow 컨테이너 안의 /opt/airflow/dags/.env 경로로 복사된다. ...

5월 30, 2025