회사의 데이터는 여러 서비스에 분산되어 있습니다.
고객 정보는 업무 DB, 계약 및 청구는 Stripe, 인증은 WorkOS, 행동 로그는 PostHog 등입니다. 모두 같은 고객과 관련된 데이터임에도 불구하고, 서비스별로 ID와 구조가 다릅니다.
이러한 데이터를 연결하여 공통의 업무 모델로서 탐색할 수 있게 해주는 OSS가 Helix Foundry입니다.
실제로 로컬에서 실행하고 샘플 데이터를 넣어보았더니, '온톨로지를 만든다'라는 막연했던 작업이 이해하기 쉬운 조작으로 바뀌어 있었습니다.
한편으로는 실제 서비스의 접근 정보나 키를 입력하는 것이 두려워지는 느낌도 들었기에 소개합니다.
Helix Foundry란 무엇인가
Helix Foundry는 자신의 컴퓨터에서 구동하는 데이터 워크스페이스입니다.
여러 데이터 소스를 가져와 고객이나 사용자, 계약 등 업무상의 개념과 그 관계를 한데 모아 다룰 수 있습니다.
주요 기능은 다음과 같습니다:
- DB, SaaS, REST API, S3, 파일로부터의 데이터 가져오기
- 데이터셋의 스키마(schema), 품질, 이력 확인
- 데이터를 기반으로 한 온톨로지(Ontology) 제안
- 레코드와 관계를 그래프나 표로 탐색
- SQL을 실행한 결과에 기반한 AI 질문
- 지표 표시, 파이프라인 및 정의 변경 제안
연결처로는 PostgreSQL, MySQL, Stripe, WorkOS, PostHog 등이 준비되어 있습니다. CSV, JSON, JSONL, Parquet도 가져올 수 있어, 우선 파일부터 시도해 볼 수도 있습니다.
온톨로지를 화면으로 이해하다
'온톨로지(Ontology)'라는 단어만 들으면 어려워 보이지만, 이번 화면에서는 업무상의 대상과 관계가 눈에 보이는 형태로 나타났습니다.
예를 들어 다음과 같은 구조입니다.
고객에게 사용자가 소속되어 있고, 고객에는 계약이 있으며, 그 계약에 청구서가 연결됩니다. 문의나 행동 이벤트에서도 사용자를 추적할 수 있습니다.
이렇게 되면 '테이블이 11개 있습니다'라고 말하는 것보다, 데이터로 무엇을 조사해 볼 수 있을지 상상하기 쉬워집니다.
이번 샘플에서는 PlanetScale의 accounts, WorkOS의 organizations, Stripe의 customers를 Account로 통합할 것을 제안하는 화면이 표시되었습니다. 사용자(User)에 대해서도 PlanetScale과 WorkOS의 데이터를 User로 묶었습니다.
서비스별 데이터 구조가 업무상의 관점으로 바뀐다는 점이 이 앱의 흥미로운 부분이라고 느꼈습니다.
아래 내용을 그대로 복사할 수 있습니다. 이미지 2장은 Zenn에 업로드하고, /images/helix-foundry/...를 발행된 이미지 URL로 대체해 주세요. GitHub 연동이라면, 지난번 ZIP 내부의 이미지 배치를 그대로 사용할 수 있습니다.
실제로 구동한 앱의 온톨로지 제안 화면입니다.
Helix Foundryのオントロジー提案画面
샘플 가져오기 후 실제 화면. 업무상의 대상과 관계, 통합 원본 서비스, 확인할 데이터가 표시됨.
고객을 나타내는 Account를 중심으로 User, Subscription, Invoice, Campaign 등이 연결되어 있습니다.
- User는 Account에 소속된다
- Subscription은 Account에 청구된다
- Invoice는 Subscription에 대응한다
- Support ticket은 User로부터 도착한다
- Event는 User의 행동을 나타낸다
이렇게 보니, 온톨로지는 '업무상의 대상과 그 관계를 정의한 것'이라고 이해하기 쉽습니다.
테이블 목록만 보는 것보다 '고객부터 계약이나 청구, 문의까지 추적할 수 있겠구나'라며 무엇이 가능한지 상상하기 쉬운 화면이었습니다.
다른 서비스의 데이터를 같은 업무 개념으로 통합하다
특히 흥미로웠던 점은, 다른 서비스에 존재하는 동일한 대상을 하나의 개념으로 묶어 제안하는 것이었습니다.
이번 샘플에서는 다음 세 가지를 Account로 통합할 것을 제안했습니다.
| 원본 데이터 | 서비스 |
|---|
| accounts | PlanetScale |
| ... | |
| 사용자(User)에 대해서도 PlanetScale과 WorkOS의 데이터를 User로 묶었습니다. | |
서비스별 구조를 그대로 나열하는 것에서 '우리 업무에서는 무엇을 나타내는 데이터인가'라는 관점으로 바뀌는 것이 좋은 부분입니다.
초기 설정은 AI가 Data, Ontology까지 안내하다
세팅은 3단계입니다.
⚠️ [IMG:N] 형식 토큰은 이미지 placeholder 입니다. 번역하지 말고 원래 위치에 그대로 유지하세요.
AI: 사용할 AI를 선택 -
Data: 데이터를 연결할지, 샘플을 가져올지 -
Ontology: 제안된 구조를 확인합니다
AI는 로컬 Ollama, Claude, OpenAI 중에서 선택할 수 있습니다. 이번에는 Docker Desktop과 이미 작동하던 네이티브 Ollama를 사용했습니다.
실제 서비스에 연결하지 않고, 제공되는 샘플을 가져왔습니다. 가상의 B2B SaaS 기업인 'Loopwell'의 고객, 계약, 청구, 행동 로그, 문의 등 총 11개의 데이터셋과 합계 5,100행입니다.
가져오기가 완료되면 이전에 보여준 제안 화면이 나타납니다. 이름을 변경하거나 대상에서 제외하는 등의 확인 과정을 거친 후 "Looks right — build my workspace" 버튼을 누르는 흐름입니다.
이번에는 제안을 확인하는 단계까지만 시도했습니다. 확정 버튼은 누르지 않았기 때문에, 공개 후 Home이나 Analyst의 조작은 이 기사에서의 실제 경험에는 포함되어 있지 않습니다.
초기 온톨로지는 어떻게 만들어지는가
처음에는 고정된 온톨로지에 서비스 접근 정보나 키를 넣으면 데이터가 채워지는 방식이 아닐까 생각했습니다.
하지만 리포지토리를 확인해보니, 초기 구조는 가져온 데이터의 유일성, 열 이름, 외부 키, 값 일치율을 사용하여 코드로 제안하는 설계였습니다.
핵심은 apps/api/src/discovery.ts의 suggestOntology()입니다.
1. 유일하고 NULL이 없는 열 찾기
가져올 때 DuckDB를 사용해 각 열의 NULL 개수와 다른 값의 개수를 계산합니다.
services/executor/src/engine.ts에는 다음 처리가 있습니다.
primaryKey:
rows > 0 &&
Number(p.null_count) === 0 &&
...
즉, 비어 있지 않은 테이블에서 NULL이 없고 모든 행의 값이 다른 열을 키 후보로 삼습니다.
여기서 primaryKey는 원본 DB의 PRIMARY KEY 제약 그 자체는 아닙니다. 가져온 데이터로부터 판단한 유일성 플래그입니다.
예를 들어 다음 데이터라면, user_id와 email은 유일하지만, account_id는 중복됩니다.
유일한 열이 여러 개 있는 경우, ownKey()가 열 이름 등을 사용하여 해당 테이블 자체의 키를 선택합니다.
우선순위는 id나 users에 대한 user_id 같은 열입니다. 그 외의 ID, 코드 등의 자연 키, 그리고 다른 유일한 열들이 이어집니다.
이는 유일한 이름이나 날짜/시간을 발견했다고 해서 안이하게 식별자로 사용하는 것을 방지하기 위한 규칙입니다.
2. 열 이름뿐만 아니라 실제 값 비교하기
다음으로 ID, 코드, 이메일, 다른 테이블을 가리킬 것 같은 열 등을 비교 대상으로 삼습니다. DB 연결 시 얻은 선언적 외부 키도 사용합니다.
그 위에 overlap()이 DuckDB의 SQL로 값의 중첩을 조사합니다.
완전 일치와 lower(trim(v))를 이용한 대소문자/앞뒤 공백 정규화된 일치를 각각 계산합니다.
후보 채택 판정의 일부는 다음과 같습니다.
if (!(isDeclared
? m > 0
...
선언적 외부 키가 아니면 다음 중 하나를 충족해야 합니다.
- 다른 값의 80% 이상이 일치함
- 비교 대상 행의 90% 이상이 일치함
선언적 외부 키인 경우에도 실제 데이터의 일치가 필요합니다.
이는 채택 조건의 일부이며, 참조하는 곳의 유일성이나 열 이름, 타입 등의 체크도 있습니다. 단순히 열 이름이 비슷하다고 연결하는 처리가 아닙니다.
3. 같은 대상을 통합하고, 대상 간 관계를 구별하기
다른 서비스의 같은 대상으로 통합할 수 있는지는 identity라는 플래그로 판정합니다.
const identity =
!A.field &&
A.unique &&
...
주요 조건은 참조하는 쪽의 열이 유일하고, 참조되는 쪽도 유일하며 NULL이 없고, 다른 시스템의 데이터이며, 완전 일치 비율이 임계값을 충족하는 것입니다.
예를 들어, 이번 샘플에서는 다음과 같은 대응이 있습니다.
PlanetScale accounts.workos_organization_id
↔ WorkOS organizations.organization_id
PlanetScale accounts.stripe_customer_id
...
이러한 1대1 대응을 바탕으로, 여러 데이터셋을 Account에 통합하는 방안을 만듭니다.
통합 그룹 구축에는 Union-Find를 사용하여, 동일 시스템의 데이터셋이 하나의 그룹에 중복하여 들어가지 않도록 제어합니다. 정수 순차 ID가 우연히 일치했다는 이유만으로 통합하지 않기 위한 추가 검사도 있습니다.
반면, 다음 대응은 User와 Account의 통합이 아닙니다.
users.account_id → accounts.account_id
여러 사용자가 동일한 account_id를 가지므로, 이는 '사용자가 고객에 소속된다'는 관계입니다.
User ── belongs to ──> Account
동일 대상의 다른 표현인지, 아니면 서로 다른 대상 간의 참조인지를 구별함으로써 화면 구조를 만들고 있습니다.
4. AI가 완성된 구조 설명을 보완합니다
초기 안이 만들어지면, 필요에 따라 describe_ontology라는 AI 처리가 실행됩니다.
apps/api/src/assistant.ts에서 받는 출력 형식은 다음과 같습니다.
z.object({
summary: z.string(),
entities: z.array([
...
AI가 반환하는 것은 전체 요약, 엔티티 설명, 링크 표시명입니다.
저장 처리 역시 기존 엔티티와 링크를 따라가면서 텍스트만 업데이트합니다.
entities: current.entities.map((e: any) => ({
...e,
description: descriptions.get(e.id) || e.description,
...
이 단계에서는 구조를 고정합니다.
즉, '고정된 온톨로지를 처음부터 제공한다'는 의미가 아니라, 데이터로부터 계산한 구조를 AI 설명 처리에서 변경하지 않는다는 의미입니다.
사용자가 확인한 후에는 confirmOntology()로 명칭 변경이나 제외 사항을 반영하고, ontologyBundle()로 파이프라인, 타입, 관계 등의 실행 정의를 조립합니다.
연결되지 않은 데이터도 보여줍니다
화면 하단에는 통합 결과와 'Worth checking'이 표시됩니다.
이번 샘플에서는 다음과 같은 결과가 나왔습니다.
| 통합 대상 | 일치 건수 |
|---|
| WorkOS organization → Account | 60 / 62 |
| ... | |
그 외에도, 사용자에게 일치하지 않는 이벤트 106건이나 문의 5건이 표시되었습니다. 대소문자를 무시하면 일치하는 이메일 주소나 캠페인 코드도 있습니다.
그래프가 만들어졌다고 해서 전부 맞다는 것은 아닙니다.
어디가 연결되고 어디에 차이가 있는지 화면에서 확인할 수 있기 때문에, 리뷰를 시작하기 쉽다고 느꼈습니다.
이메일 주소가 일치하면 같은 사용자인지, 청구지와 이용 기업을 같은 Account로 해도 되는지 등 업무상의 판단은 남아 있습니다.
스마트폰 폭에서는 이런 표시입니다
スマートフォン幅で表示したオントロジー提案画面
구체적인 데이터를 보면서 통합이나 불일치 항목을 확인할 수 있다는 점이 이 UI의 가치라고 생각합니다.
내용은 HelixDB와 DuckDB의 조합
그래프와 표 형식 분석으로 역할을 분리했습니다.
| 구성 요소 | 주요 역할 |
|---|
| React UI와 API | 화면, 연결 설정, 리뷰, 공개, 레코드 조작 |
| ... | |
이번 Docker 구성에서는 App, Worker, Executor, HelixDB가 작동하며, AI는 호스트 측의 Ollama를 이용합니다.
DuckDB의 SQL 실행은 별 프로세스로 분리되며, 선언된 입력에 대한 단일 SELECT로 제한됩니다. 실행 시간이나 메모리에 제한도 있습니다.
그래프로 관계를 추적하고, 수치는 스냅샷을 SQL로 분석하는 조합입니다.
실제 서비스의 접근 정보나 키를 넘겨주는 두려움
화면이 이해하기 쉬워도, 실제 DB 연결 정보나 API 키를 입력하는 단계에서는 신중해집니다.
무엇에 접근하는지, 가져온 데이터는 어디에 남는지, AI에게 무엇이 전송되는지. 이 부분들은 이해한 후에 사용하고 싶습니다.
Helix Foundry는 사용자가 자신의 컴퓨터에서 사용할 것을 전제로 합니다. 로그인 기능은 없으며, Docker 구성 시 애플리케이션은 127.0.0.1에만 공개됩니다. 외부로 공개하는 운영은 예상되지 않습니다.
연결 정보나 AI 자격 증명(credentials)은 암호화하여 저장하지만, 복호화에 필요한 키는 .env 파일에 있습니다. 이 파일을 분실하면 저장된 자격 증명을 복원할 수 없으므로, 설정 및 백업 관리가 중요합니다.
또한, 로컬 AI와 Claude 또는 OpenAI를 사용할 경우 데이터 전송지가 달라집니다. 호스팅되는(Hosted) AI로 보내는 샘플은 민감 정보라고 판단한 열을 기본적으로 마스킹하지만, 그 탐지 과정은 휴리스틱(heuristic)입니다.
'로컬에서 작동한다'는 말만으로 판단하지 말고, 연결 권한과 AI 설정을 확인한 후에 사용하고 싶다고 생각했습니다.
온톨로지를 쉽게 만들기
온톨로지를 만들려면 업무 이해와 개념을 도출하고, 유형(type)과 관계를 설계하며, 데이터 매핑이라는 이미지를 갖는 경우가 많다고 알고 있지만,
Helix Foundry에서는 기존 서비스의 접근 정보를 입력하는 것만으로 데이터 생성 과정과 실제 대조 결과를 확인하면서 온톨로지를 만들 수 있습니다.
기존 서비스를 자신이 사용하는 것으로 대체하여 온톨로지 추출 및 활용 부분은 재사용할 수 있을 것 같다고 생각했습니다.
참고
토론(Discussion)