통합 데이터 허브로 가는 길 - Part 3

기능 구현과 운영전략
Gray Yoon's avatar
Nov 28, 2025
통합 데이터 허브로 가는 길 - Part 3

JupyterHub 전환에서 우리가 가장 중요하게 본 것은 Zeppelin 사용자들의 ‘소프트 랜딩’이었습니다.

분석가들이 이미 익숙하게 쓰던 %sql, %spark 같은 인터페이스를 그대로 유지하면서도,

더 강력하고 안정적인 실행 방식을 제공하는 방향으로 설계했습니다.

즉, 새로운 툴로 갈아타는 전환이 아니라,

익숙한 사용 경험 위에 안정성과 확장성을 더한 진화였습니다.


기능 구현

1. %%sql — 익숙한 문법, 더 안전한 구조

Zeppelin 에서 SQL 사용
Jupyterhub에서 SQL 사용

가장 먼저 구현한 기능은 %%sql 매직이었습니다.

내부적으로는 PyAthena를 사용했지만, Zeppelin의 %sql 문법을 그대로 지원하도록 설계했습니다.

초기에는 대용량 쿼리 결과를 한 번에 불러오면서 메모리(OOM) 문제가 자주 발생했습니다.

이를 해결하기 위해 결과를 LIMIT 1000 단위로 스트리밍해서 순차적으로 읽어오는 방식으로 개선했습니다.

이 변경만으로 대용량 쿼리에서도 세션이 안정적으로 유지되었고,

“한 번만 더 실행하다 죽는” 현상이 사라졌습니다.

1.1 파라미터 패널 — 반복 쿼리의 자동화

{start_date}, {end_date}처럼 변수를 쿼리 안에 직접 넣는 대신,

UI 입력으로 값을 치환할 수 있는 Custom Parameter Panel을 구현했습니다.

이 기능 덕분에 주간/월간 리포트를 매번 수정하지 않고,

비개발자도 클릭 몇 번으로 반복 쿼리를 실행할 수 있게 되었습니다.

1.2 Scan Monitoring — 비용을 ‘보게’ 만든 기능

Athena는 스캔 데이터량에 따라 비용이 발생합니다.

문제는 대부분의 사용자가 자신의 쿼리가 얼마짜리인지 몰랐다는 점이었죠.

그래서 쿼리 실행 전 예상 스캔 용량을 계산해 보여주고,

Slack으로 “이 쿼리는 약 2.3GB 스캔, 예상 비용 0.01 USD” 같은 알림을 보냈습니다.

사용자들은 자연스럽게 비용 구조를 인식하게 되었고,

불필요한 풀 스캔이나 과도한 조인을 줄이는 건전한 쿼리 습관이 자리 잡았습니다.

1.3 결과 저장·공유 — 한 번의 실행으로 끝내다

Zeppelin에서 자주 쓰이던 %sql.download, %sql.googledrive 기능은

JupyterHub의 %%sql 내부 옵션으로 통합했습니다.

이제 쿼리 실행 한 번으로

결과 생성 → CSV 저장 → Google Drive 업로드까지 자동 처리됩니다.

결과 링크는 Slack으로 공유되어,

데이터 추출과 공유 과정이 완전히 자동화되었습니다.

즉, %%sql은 익숙한 문법 위에서

대용량 안정성(스트리밍), 반복 작업 자동화(파라미터),

비용 인식(Scan Monitoring), 결과 공유까지 하나로 묶은 완성형 인터페이스가 되었습니다.


2. %%spark — Zeppelin %spark의 자연스러운 계승

Spark 기반 분석 역시 Zeppelin의 %spark 문법을 그대로 계승했습니다.

다만, JupyterHub에서는 Livy REST API를 통해 클러스터에 코드를 전송하고 실행하도록 구조를 바꿨습니다.

이제 사용자는 익숙한 문법으로 Spark 코드를 작성하되,

실행 로그를 실시간으로 스트리밍 형태로 확인할 수 있습니다.

세션이 중간에 종료되더라도 자동 복구되고,

Spark UI를 통해 실행 상태를 바로 추적할 수 있습니다.

즉, 사용자는 예전과 똑같은 사용감으로 작업하지만,

운영자는 훨씬 안정적이고 관리 가능한 환경을 갖게 된 셈입니다.

Livy를 통한 Pyspark session 사용

3. 지능형 SQL 작성과 스키마 탐색 — 익숙함에 직관을 더하다

SQL을 작성할 때 가장 큰 장벽은 “무엇을 써야 할지 모른다”는 것이었습니다.

컬럼 이름을 외워야 하고, 테이블 구조를 일일이 찾아야 했죠.

Zeppelin 시절에는 스키마 문서를 찾아보거나 SHOW COLUMNS를 반복 실행하는 일이 흔했습니다.

이 문제를 해결하기 위해,

우리는 Athena 메타데이터 기반의 자동완성과 시각적 탐색 기능을 JupyterHub에 통합했습니다.

3.1 Athena 메타데이터 기반 LSP (Language Server Protocol)

SQL 문맥을 분석해 컬럼과 테이블을 자동 제안하는 LSP(Language Server Protocol)를 구현했습니다.

Glue Catalog의 스키마 정보를 주기적으로 동기화해 로컬 캐시로 유지하고,

SELECT 문맥에서는 해당 테이블의 컬럼을 자동 완성하도록 했습니다.

  • Glue Catalog 스키마 주기 동기화 및 로컬 캐시 유지

  • 동일 컬럼명은 table.column 형식으로 구분

  • 스키마 변경 시 자동 갱신

이제 사용자는 컬럼명을 외울 필요가 없습니다.

타이핑 몇 글자만 입력하면, 필요한 컬럼이 자동으로 완성됩니다.

익숙한 SQL 환경이 지능형 IDE 수준으로 확장된 셈입니다.

3.2 Athena 전용 스키마 패널 — 클릭 한 번으로 탐색

자동완성만으로는 충분하지 않았습니다.

특히 초심자들은 “Athena에 어떤 테이블이 있는지조차 모르겠다”고 했죠.

그래서 오픈소스 jupyterlab_sql_explorer를 참고하되,

Athena 드라이버와 S3 결과 구조의 한계를 보완한 전용 Athena Explorer 패널을 직접 구현했습니다.

Glue Catalog의 메타데이터를 동기화해

DB → 테이블 → 컬럼 구조를 트리 형태로 시각화하고,

컬럼 타입과 설명까지 함께 표시합니다.

클릭 한 번이면 쿼리 에디터에 자동 삽입되기 때문에,

SQL 명령어 없이도 스키마를 탐색할 수 있습니다.

이 기능 하나로 초심자의 진입장벽이 크게 낮아졌고,

데이터 탐색의 효율이 눈에 띄게 높아졌습니다.


4. GitHub Enterprise — 자동 브랜치, PR, 그리고 Slack 알림까지

우리 조직은 보안 정책상 모든 사용자에게 GitHub 계정을 개별 발급할 수 없었습니다.

그렇다고 협업 기능을 포기할 수도 없었기에,

JupyterHub 내부에서 OAuth 인증 정보를 이용해 GitHub 브랜치를 자동 생성하는 구조를 만들었습니다.

사용자가 노트북에서 코드를 수정하고 “commit and push”를 실행하면, 다음 과정이 자동으로 이어집니다.

  1. JupyterHub가 OAuth로부터 사용자의 이메일을 식별

  2. 해당 이메일 주소 기반으로 브랜치 자동 생성 (feature/<user_email_prefix> 형식)

  3. 변경 내용 자동 커밋 및 푸시

  4. GitHub API를 통해 Pull Request 자동 생성

  5. 리뷰 담당자가 코멘트를 남기면

  6. Slack Webhook이 트리거되어, PR 요청자를 멘션하며 리뷰 알림 발송

    Git자동화 Flow

이 과정 덕분에 Git 계정이 없어도 협업 가능한 개발 플로우가 완성되었습니다.

사용자는 git 명령어를 몰라도, 노트북에서 클릭 한 번으로 PR을 생성할 수 있습니다.

모든 변경 이력은 GitHub Enterprise에 기록되어

운영 감사(Audit)코드 추적성(Traceability)을 완벽히 보장합니다.

이 구조 덕분에 JupyterHub는 단순한 분석 플랫폼이 아니라

엔터프라이즈 개발·협업 허브로 확장되었습니다.

데이터 엔지니어나 분석가는 브랜치를 직접 관리할 필요가 없습니다.

코드를 작성하고 저장만 하면,

백그라운드에서 자동으로 PR이 생성되고 리뷰가 진행됩니다.

협업의 복잡함은 사라지고, 생산성만 남았습니다.


결론 — 익숙함은 유지하고, 불편함만 바꿨다

Zeppelin에서 JupyterHub로의 전환은 단순한 도구 교체가 아니었습니다.

익숙한 인터페이스는 그대로 두고, 그 아래 실행 구조를 개선산 새로운 도구 였습니다.

커스텀 매직(%%sql, %%spark), LSP 자동완성, Github PR 그리고 Athena 전용 패널은

모두 사용자의 습관을 존중하면서 불편함만 제거하는 방향으로 발전했습니다.

그 결과, JupyterHub는 “새로운 플랫폼”이 아니라

“익숙하지만 훨씬 강력한 데이터 허브”로 자리 잡았습니다.


운영

JupyterHub는 한 번 구축하고 끝나는 시스템이 아닙니다.

사용자마다 다른 쿼리를 실행하고, 각자 노트북에서 코드를 수정하며,

매일 새로운 기능 요청이 들어오는 살아 있는 플랫폼입니다.

그래서 운영의 핵심은 “사용자는 계속 일하고, 운영자는 부담 없이 바꿀 수 있는 구조”였습니다.

업데이트나 환경 변경이 생겨도 사용자의 세션을 내리지 않고,

큰 구조 변화가 아니라면 바로 반영할 수 있도록 설계했습니다.


1. 무중단 운영 — 컨테이너를 내리지 않고도 변경 가능

일반적으로 JupyterHub의 설정을 바꾸거나 이미지를 업데이트하면

모든 사용자 컨테이너를 재기동해야 합니다.

하지만 우리는 Hub와 사용자 컨테이너를 완전히 분리한 구조로 설계했습니다.

공통 설정(hub_config.py, 공용 volume 등)은 Hub 단에서만 반영되고,

사용자 컨테이너는 그대로 유지됩니다.

변경된 내용은 사용자가 다음 로그인할 때만 새로 반영됩니다.

이 구조 덕분에 대부분의 설정 변경은 운영 중에도 바로 반영이 가능하고,

사용자의 세션이 끊기지 않습니다.

즉, 업데이트가 있어도 “업무는 그대로 이어지는” 환경이 만들어졌습니다.


2. Jenkins 배포와 Git 버저닝

배포는 Jenkins Pipeline을 통해 자동화했습니다.

GitHub에 코드가 병합되면 Jenkins가 자동으로 빌드를 수행하고,

새로운 이미지를 만들어 JupyterHub에 반영합니다.

모든 배포 버전은 Git 태그(v1.0.0, v1.1.2 등)로 관리됩니다.

만약 문제가 발생하면 이전 버전으로 즉시 롤백할 수 있습니다.

덕분에 새로운 기능을 배포하더라도 “실패”가 두렵지 않았습니다.


3. Portainer — 컨테이너 문제 해결의 눈

사용자별로 컨테이너가 개별 실행되기 때문에,

특정 사용자 환경에서만 문제가 발생하는 경우가 종종 있었습니다.

이를 빠르게 파악하기 위해 Portainer를 도입했습니다.

운영자는 웹 UI에서 각 사용자 컨테이너의 로그와 상태를 즉시 확인할 수 있고,

CPU, 메모리, 네트워크 사용량을 한눈에 볼 수 있습니다.

Portainer 덕분에 “누구의 세션이 멈췄는지”,

“어떤 코드가 원인이었는지”를 빠르게 찾아 대응할 수 있게 되었습니다.


4. 셀 실행 로그 — 누가 무엇을 실행했는가

운영 투명성과 추적성을 위해 모든 셀 실행 로그를 기록하고 있습니다.

사용자가 셀을 실행할 때마다 실행 시간, 사용자 이메일, 코드 내용이 로그로 남습니다.

이 기록은 단순한 감사용이 아니라,

문제가 발생했을 때 “어떤 셀이, 어떤 코드로, 언제 실행되었는지”

즉시 확인할 수 있는 근거가 됩니다.

또한 주기적으로 이 로그를 분석해

사용자별 쿼리 패턴과 리소스 사용량도 파악할 수 있습니다.


5. 지역별 접근 제어 — 정책을 시스템으로 지키다

JupyterHub는 한국과 인도 리전에서 사용됩니다.

각 지역의 보안 정책이나 데이터 접근 규칙이 다르기 때문에

IP 대역 기반 접근 제어를 추가했습니다.

  • 한국/인도 IP 대역 구분

  • 정책 위반 가능성이 있는 구간은 접근 제한

  • 외부 API 호출, 파일 다운로드 등은 지역별 제한 가능

이 구조 덕분에 법적·보안 정책을 시스템 차원에서 자동으로 준수할 수 있게 됐습니다.


6. 로컬 테스트 환경 — Makefile로 운영 환경을 그대로

운영 환경을 직접 건드리며 테스트하는 건 위험했습니다.

그래서 로컬에서도 완전히 동일한 구조를 재현할 수 있도록

Makefile 기반의 테스트 환경을 만들었습니다.

로컬에서 make run 한 번이면

Docker Compose를 통해 Hub, Spawner, Auth 구성까지 동일하게 띄워집니다.

운영과 동일한 베이스 이미지, 포트, 네트워크 구성을 그대로 사용하기 때문에

새로운 기능을 자유롭게 검증할 수 있습니다.

테스트가 끝나면 make clean으로 환경을 깔끔하게 초기화할 수 있습니다.

운영자는 실험을 두려워하지 않고,

운영 변경 전 어떤 결과가 나올지 미리 확인할 수 있습니다.


7. 우리가 추구한 운영의 형태

JupyterHub 운영의 목표는 거창한 자동화 시스템이 아니라,

안정성과 실용성의 균형이었습니다.

  • 사용자 컨테이너를 내리지 않는 실시간 변경

  • Jenkins + Git 버저닝으로 안전한 배포와 롤백

  • Portainer를 통한 컨테이너 단위 진단

  • 셀 로그 기반 감사 추적

  • 지역별 정책 제어

  • Makefile로 로컬 테스트 환경 구축

이 구조를 통해 우리는

운영자의 개입은 최소화하면서도,

플랫폼 전체의 신뢰성과 가시성을 유지할 수 있었습니다.

마무리하며…

세 편에 걸쳐 JupyterHub 도입 여정을 정리했습니다.

Part 1 에서는 왜 JupyterHub를 선택했는지,즉 우리 조직이 겪었던 비효율을 어떻게 바꾸려 했는지를 이야기했습니다.

Part 2 에서는 그 목표를 현실로 만들기 위해 인증, 격리, 보안, 베이스 이미지 설계 등 플랫폼의 아키텍처를 어떻게 설계하고 구축했는지를 다뤘습니다.

Part 3 에서는 그 위에 구현된 기능과 실제 운영 체계를 정리했습니다. Zeppelin 사용자의 익숙함을 유지하면서 확장한 매직 명령어,

지능형 SQL 환경, GitHub 자동화, 그리고 무중단 운영 구조와 관리 방식까지

“설계된 플랫폼이 어떻게 실제로 굴러가는가”

를 보여드렸습니다.

JupyterHub는 단순히 분석 툴을 교체한 프로젝트가 아니었습니다.

팀의 협업 방식과 운영 문화를 시스템으로 옮긴 여정이었습니다.

이제 우리는 도구를 ‘운영’하는 게 아니라,

운영이 자연스럽게 녹아든 지속 가능한 데이터 허브를 갖게 되었습니다.

그리고 이 여정은 아직 끝나지 않았습니다.

다음 단계는 Jupyter AI입니다.

LLM 기반의 Text-To-SQL, 자연어 쿼리 지원,

사용자별 프롬프트 환경 등으로

데이터 허브를 ‘스마트 허브’로 확장할 준비를 하고 있습니다.

JupyterHub가 “사람이 데이터를 다루는 공간”이었다면,

Jupyter AI는 “데이터가 사람과 대화하는 공간”이 될 것입니다.

Share article