Post

Dify 소스코드 뜯어보기

LLMOps 플랫폼 오픈소스인 Dify를 확장성 있게 셀프 호스팅하기 위해 소스코드를 분석한다. 폴더 구조 파악, 로컬 실행, API 코드 분석 등의 과정을 거치며 Dify의 전체 아키텍처를 이해하고, 오픈소스 프로젝트를 효과적으로 파악하는 노하우를 공유한다.

Dify 소스코드 뜯어보기

Dify 메인 화면 Dify 공식 GitHub 저장소

Dify 를 셀프 호스팅하기 위해 낱낱이 파헤쳐야 할 일이 생겼다.

분석에 도움이 될 만한 문서들 확보하기

제일 먼저 파악에 도움이 될 수 있는 실행에 필요한 부분과 환경 변수들을 설명하는 다큐먼트를 찾아봤다.

빠른 파악에 도움이 될 만한 문서들

환경 변수와 그에 대한 설명

환경 변수는 뭐가 많아서 나중에 구조가 눈에 들어오면 다시 보기로 하고 스킵.

그래도 대충 코드 안에서 도메인 용어들이 등장하는지 알 수 있었다.

  • 콘솔 API, WEB: 여기서 API는 콘솔 요청 받는 친구
  • 서비스 API - Dify 에서 제공하는 API 서버. 이걸 확장성 있게 설계하는 것이 중요함.
  • 앱 API, WEB: 여기서 API는 웹앱 요청 받아주는 친구
  • front-end 가 각각 콘솔과 웹앱으로 나뉘는 것 같은데, 콘솔과 웹앱은 무슨 차이지?

폴더 구조 먼저 살펴보기

기본적으로 모노레포로 구성되어 있고, 각 컴포넌트마다 README 가 상세하게 적혀 있다. 하지만 지금 README 읽어봤자 100% 이해는 못한다. 그냥 감만 잡는다는 느낌으로 읽는다.

폴더 구조도 복잡하지 않아서 대략 파악한 바로는:

1
2
3
4
5
6
7
8
9
❯ tree -L 1 -d
.
├── api // Dify backend 담당. console, webapp, service API 들이 모여 있을 것으로 기대됨.
├── dev // 개발 편의를 위한 폴더인 듯. 안 중요.
├── docker // 도커 파일에 대한 설정 및 설명.
├── docker-legacy // 아마 안 중요..?
├── images // 안에 든 게 별로 없음. 안 중요.
├── sdks // Dify service API를 호출하기 위한 SDK 집합인 듯. 안 중요.
└── web // Dify frontend 담당. 아마 console, webapp 이 여기 같이 있으려나?

로컬에서 실행하기

가장 빠르게 파악할 수 있는 방법 중 하나는 직접 로컬에서 실행하는 거라고 생각한다. README에 적힌대로 docker 로 옮겨서 기본 설정 카피하고(.env) docker-compose 로 실행시켜 보면 컨테이너가 9개가 뜬다.

1
2
3
4
5
6
7
8
9
10
11
❯ docker compose up -d
[+] Running 9/0
 ✔ Container docker-web-1         Running  // 웹앱 <- 아까 파악한 친구
 ✔ Container docker-weaviate-1    Running  // 벡터 서치 엔진 (RAG 가져올 때 쓰이려나)
 ✔ Container docker-redis-1       Running  // 캐싱 및 redis pub sub 을 위해 설정된 듯. 
 ✔ Container docker-ssrf_proxy-1  Running  // 보안 미들웨어 정도로 추정
 ✔ Container docker-sandbox-1     Running  // ??
 ✔ Container docker-db-1          Running  // API에서 호출할 DB
 ✔ Container docker-api-1         Running  // API <- 아까 파악한 친구 = console, service, web app API 전부 포함.
 ✔ Container docker-worker-1      Running  // 분산 큐 작업을 위한 워커
 ✔ Container docker-nginx-1       Running  // nginx 서버 

각각이 어떤 서비스인지 docker 폴더 안을 헤집으면서 보고 있었는데, 아주 친절하게도 구조도까지 그려져 있는 걸 발견했다.

Dify Docker 구조도 Dify 공식 Docker Compose 구조도

이런 건 너무 고맙다. 없으면 내가 그려야 하기 때문이다. 구조도를 보고 알 수 있었던 건:

  • nginx 에서 80번 포트로 들어오는 트래픽을 각각 web, api 로 라우팅 해줌
  • api, worker 는 의존하는 부분이 똑같음, storage, redis, db
  • api 가 redis 로 pub 하면 worker 가 sub 해서 처리하는 구조 같다
  • weaviate 는 뭔가 했는데 벡터 저장소였음 연결선이 없이. 동떨어져있는 이유는 nginx를 타지 않기 때문인가? 싶었음.

그리고 들었던 의문점…

sandbox 는 뭘까? 이 친구가 뭘하는 건지 도무지 파악을 할 수 없었다. 구조도에도 sandbox는 없다.

docker-compose.yml 설정에서 CODE_EXECUTION_ENDPOINT 하는 환경 변수가 있는 걸 보니 코드 실행을 격리하기 위한 환경인 듯. 정확히는 뭔지 모르겠음.

나중에 찾아보니 dify 내에서 코드를 실행할 수 있다 보니 런타임을 보호하기 위한 설정이었다. Dify 내부에서 코드 실행을 열어주다보니 보안 문제가 발생할 수 있어서 코드 실행과 관련된 부분들은 격리된 환경을 만들어둔 것이었다.

실제 코드에서 사용되는 부분은 여기다.

api 파악

제일 중요하다고 생각했던 건 api 디렉토리다. Dify 의 핵심이기 때문이다. api/Dockerfile 을 들여다 보고 console, service API를 통합해서 제공하는 것을 알았고, controllers 를 열어보니 다음과 같이 나눠져 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ tree -L 1 -d
.
├── configs // .env 여기서 다 불러옴
├── constants // 상수들 정의된 곳. 별로 안중요
├── contexts // 뭔지 모르겠지만 일단 넘어감 ㅋㅋ
├── controllers // API 목록들을 여기서 찾을 수 있음
├── core // 핵심 로직 담겨 있음
├── docker // 워커를 실행시키기 위한 엔트리포인트
├── events // pub/sub 하기 위한 이벤트가 정의된 곳
├── extensions // storage 같은 외부 의존성들과 관련된 코드들이 있는 곳
├── factories // 팩토리들 있겠지 하고 넘어감.
├── fields // 뭔지 잘 모르겠는데 web의 필드들이 정의된 곳인 듯
├── libs // 공용 라이브러리 같은 건가 봄. passport 나 helper 같은 거 있음. 어노테이션도 있음.
├── migrations // 안중요해보여서 스킵.
├── models // 도메인 모델들 정의된 곳.
├── schedule // 셀러리 스케줄 테스크들 있음
├── services // 실제 어플리케이션 로직들 있는 곳. controllers -> services -> models 순으로 호출됨.
├── tasks // 셀러리 테스크들 정의된 곳.
├── templates // html 템플릿들 있음. 안중요.
└── tests // 테스트 코드는 걍 무시

코드를 들어가서 조금씩 살펴보다 보니 생각했던 것과 달리 service 와 console의 역할이 반대였다. dify 들어가면 바로 보이는 게 service 인 줄 알았는데, console이 그 역할을 하는 것이었다.

controllers에서 아무 API나 타고 들어가서 Database 와 만나는 부분을 찾아봤다. Database는 PostgreSql 로 model 에 정의되어 있었고, 접속해보고 싶어졌음. 그러려면 연결 정보를 찾아야 했다. api/models/engine.py 가 그 역할을 하는 것 같았고, 코드를 보니 SQLALCHEMY_DATABASE_URI 로 설정하고 있는 것 같아서 보니 api/configs 에 그 설정 정보들이 있었다.

DB 접속 주소 알고 싶어서 print 문 하나 걸고 실행시키니 $postgresql://postgres:@localhost:5432/dify 이렇게 나왔다. .env 에서 그대로 가져오는 거 확인하고 docker-compose 수정해서 호스트에서 DB 포트 오픈해주었다. 접속 잘 됨.

Dify DB 접속 화면 로컬에서 Dify DB에 접속한 모습

대략 어떤 식으로 굴러가는 지 감을 잡았고, celery 를 이용해 비동기 작업들을 redis pub/sub 으로 tasks 안에 적용해둔 것도 확인했다.

모델들은 api/core/model_runtime 에서 관리되고 있다. 그리고 여기에 model_providers 부터 시작해서 Dify 토폴로지를 돌리는 어마어마한 핵심 코드들이 있다.

더 이상 세세하게 볼 필요는 없는 것 같아서 빠져나옴. 나중에 버그 직접 고칠 때나 더 들여다 볼 것 같다.

나중에 docker 폴더 들여다 보다가 알게 된 거지만, worker 랑 api 는 같은 코드로 돌린다.

.env 살펴보기

api 를 대략적으로 훑어 보니 위 구조도에 대한 이해가 좀 더 된 것 같아서, .env 를 각각 살펴보고 어떻게 연관되는지 살펴보기로 했다.

  • SECRET_KEY: 엔터프라이즈 레벨에서 보안을 위해 헤더에 전달하는 키다. 쿠키 세션 사이닝할 때 쓰임.
  • CONSOLE_API_URL=http://127.0.0.1:5001 // 콘솔(dify 들어가자마자 보이는 화면) API URL
  • CONSOLE_WEB_URL=http://127.0.0.1:3000 // 콘솔 웹
  • SERVICE_API_URL=http://127.0.0.1:5001 // 콘솔에서 API 를 열었을 때 나오는 URL
  • APP_WEB_URL=http://127.0.0.1:3000 // 파일 미리보기 & 멀티 모달
  • FILES_URL=http://127.0.0.1:5001 // 파일 미리보기 or 다운로드 URL prefix
  • FILES_ACCESS_TIMEOUT=300 // 파일 엑세스 타임아웃
  • ACCESS_TOKEN_EXPIRE_MINUTES=60 // 콘솔 액세스 토큰 만료 시간
  • CELERY_BROKER_URL=redis://:difyai123456@localhost:6379/1 // 셀러리 브로커 URL 유저 파일 업로드 같은 비동기 작업 처리용
  • redis 설정들, redis sentinel, redis cluster
  • DB 설정 - 콘솔 앱은 기본적으로 postgresql 을 사용하고 있음.
  • 파일 업로드할 스토리지 설정, OPENDAL_SCHEME=fs 이런 식으로 설정하면 file system 쓰는 듯. private key나 유저가 업로드한 파일 저장하는 데 쓰임
  • s3 설정들. 언제 쓰이나 봤더니 tts 파일 저장하는 데 쓰인다
  • 기타 등등…. 뭐 많은데 대충 이 정도면 된 거 같다.

본래 목적

여기까지 개략적인 구조를 파악해보았다.

확장성 있는 구조로 셀프 호스팅을 하는 것이 목적이었기 때문에 구조도에 대한 이해가 가장 중요했다.

나머지는 docker-compose 파일 찢어서 직접 각 컴포넌트 셀프 호스팅하면서 더 자세히 알게 될 것 같다.

README 부터 보려고 마음 먹어도 코드를 보지 않고는 이해가 안되는 부분도 있고, 코드를 보고 이해가 안가는 부분은 이해가 안됐던 README 보고 다시 이해가 가기도 하고 그랬던 것 같다.

만약 처음부터 다시 분석을 한다면 중요해보이는 file tree 구조를 먼저 잡고, 각 README 를 좀 더 꼼꼼하게 읽어 봤을 것 같다. 사실 나중에 보니 docker 폴더가 핵심이었는데, 구조가 머릿속에 안잡히다 보니 api 코드까지 들여다 보게 된 듯 하다.

This post is licensed under CC BY 4.0 by the author.