TRENDSHIFT DAILY #23 · 2026-07-25 · INVIDIOUS · 프라이버시 YouTube 프런트엔드

iv-org/invidious 딥다이브
광고·추적·자바스크립트 없이 YouTube를 보는 오픈소스 대체 프런트엔드

Invidious는 YouTube를 광고·추적·구글 계정 없이 볼 수 있게 해 주는 오픈소스 대체 프런트엔드(alternative front-end)다. 당신의 브라우저는 구글이 아니라 Invidious 서버에만 접속하고, 서버가 대신 YouTube에서 데이터를 긁어와 깨끗하게 재조립해서 보여 준다. 그 사이에 구글의 광고·쿠키·추적 스크립트는 한 톨도 끼지 못한다.

기술적으로도 독특하다. 세상에서 제일 큰 Crystal 언어 프로젝트(README 표현: "Crystal 자체보다 크다")이고, 공식 YouTube API를 전혀 안 쓰는 대신 YouTube 앱이 내부적으로 쓰는 InnerTube 클라이언트를 흉내 내서 데이터를 얻는다. 최근에는 영상 스트림을 가져오는 부분이 별도 컴패니언 서비스로 분리되는 큰 구조 변화가 일어났다.

(저장소 iv-org/invidious · 언어 Crystal · 웹 프레임워크 Kemal · DB PostgreSQL 14 · 렌더링 서버사이드 ECR · 라이선스 AGPLv3 · 버전 2.20260723.0(2026-07-23) · 분석 시점 2026-07-25 · shallow clone 소스 직접 분석)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목받는가 — '대신 봐 주는' 프라이버시 프런트엔드
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"YouTube를 대신 봐 주는 대리인" — 구글과 당신 사이에 서는 서버

Invidious는 한 문장으로 "YouTube 앞에 세우는 프라이버시 방패막이 웹서버"다. 보통 YouTube를 보면 당신의 브라우저가 구글 서버에 직접 접속하고, 그 순간 광고·쿠키·추적 스크립트·시청 로그가 모두 구글로 흘러 들어간다. Invidious는 그 사이에 중간 서버 하나를 세운다. 당신은 Invidious 인스턴스에만 접속하고, Invidious가 당신 대신 YouTube에서 영상 정보·댓글·자막을 긁어와 광고와 추적을 싹 걷어낸 깨끗한 페이지로 재조립해 준다.

핵심 비유

"직접 백화점에 가는 것" vs "심부름꾼에게 대신 사 오게 하는 것"

YouTube를 직접 보는 건 백화점에 몸소 가는 것이다. 입장하는 순간 CCTV(추적)에 찍히고, 곳곳의 광고판(광고)을 지나야 하고, 회원카드(구글 로그인)로 구매 내역이 다 기록된다.

Invidious는 심부름꾼이다. 당신은 집(Invidious 서버)에서 "이 영상 가져와" 하고 시키기만 한다. 심부름꾼이 백화점(YouTube)에 대신 가서 물건만 받아 오므로, CCTV에 찍히는 건 당신이 아니라 심부름꾼이고, 광고판도 심부름꾼이 무시하고, 회원카드도 필요 없다. 게다가 이 심부름꾼은 자바스크립트를 한 줄도 안 요구해서, 아주 낡은 기기·브라우저에서도 가볍게 돈다.

중요한 구분이 하나 있다. Invidious는 영상을 저장·호스팅하지 않는다. 유튜브에서 실시간으로 긁어와 중계할 뿐이다. 그래서 "YouTube 다운로더"나 "불법 스트리밍 사이트"가 아니라, YouTube를 더 사생활 친화적으로 '보는 방법'이다. 실제로 README에도 "우리 도구의 사용 책임은 사용자에게 있으며, 각국의 정식 규정을 준수하라"는 면책 조항이 명시돼 있다.

용어 풀이
대체 프런트엔드 (alternative front-end)
원본 서비스(YouTube)의 백엔드는 그대로 두고, 사용자가 보는 화면(프런트엔드)만 제3자가 새로 만든 것. 원본의 광고·추적·불필요한 기능을 걷어내고 프라이버시·경량화·오픈소스를 지향한다. Invidious(YouTube), Nitter(트위터), Libreddit/Redlib(레딧), Bibliogram(인스타그램)이 같은 계열이다. 원본 API를 안 쓰고 '긁어오기(scraping)'로 데이터를 얻는 게 공통 특징이자 약점(YouTube가 방식을 바꾸면 깨진다)이다.

2왜 주목받는가 — '대신 봐 주는' 프라이버시 프런트엔드

TrendShift Daily 상위권(#23)에 다시 오른 이유

Invidious는 새 프로젝트가 아니다(TrendShift 저장소 번호 6069로, 트렌딩 역사가 오래됐다). 그런데도 2026년 7월 다시 상위권에 오른 데는 이유가 있다. YouTube가 봇 차단을 강화할 때마다 Invidious 같은 프런트엔드가 "이번엔 살아남을까"라는 관심을 받고, 실제로 이 팀은 한 달에 여러 번 릴리스하며 끈질기게 대응하고 있다. 핵심 매력을 네 가지로 정리한다.

① 프라이버시 — 구글에 흔적을 남기지 않는다

README가 내세우는 사용자 기능이 곧 매력의 핵심이다: 광고 없음, 추적 없음, 자바스크립트 불필요, 구글과 무관한 독립 구독, 모든 구독 채널 알림, 오디오 전용 모드(모바일 백그라운드 재생), Reddit 댓글 지원. 구글 계정 없이 구독·시청 기록을 관리하고, 그 데이터를 NewPipe·FreeTube로 내보내거나 가져올 수 있다.

② 극단적 경량 — 낡은 기기에서도 돈다

Invidious는 페이지를 서버에서 완성해서(server-side rendering) 보낸다. 요즘 웹앱처럼 브라우저에서 자바스크립트로 화면을 그리는 게 아니라, 옛날 방식처럼 완성된 HTML을 보낸다. 그래서 자바스크립트를 꺼도 대부분 작동하고, 10년 된 노트북이나 텍스트 브라우저에서도 열린다. 배포물도 단일 정적 바이너리(static musl binary) 하나라 실행이 가볍다.

③ 셀프호스팅 — 내 서버를 내가 통제한다

공개 인스턴스 목록(instances.invidious.io)에서 남의 서버를 골라 써도 되고, docker-compose내 서버에 직접 띄워 완전히 통제해도 된다. 라이선스가 AGPLv3이고 기여자 계약(CLA)이 없다는 점도 개발자 커뮤니티가 좋아하는 이유다 — 코드가 특정 회사에 종속되지 않는다는 신호이기 때문이다.

④ 살아 있는 '고양이와 쥐' 싸움 — 엔지니어링 드라마

개발자에게 진짜 재미있는 건 YouTube의 방어 vs Invidious의 우회라는 끝없는 술래잡기다. 최근 릴리스 노트만 봐도 "YouTube 백엔드 변경으로 깨진 재생을 핫픽스", "YouTube User-Agent 업데이트" 같은 항목이 반복된다. 이 대응 과정에서 영상 스트림 획득이 아예 별도 서비스로 분리(§4)되는 큰 아키텍처 진화가 일어났다. "대규모 스크래핑을 지속가능하게 운영하는 법"의 살아 있는 교과서인 셈이다.

비교InvidiousNewPipeFreeTube
형태셀프호스팅 웹서버안드로이드 앱데스크톱 앱(Electron)
접근브라우저로 어디서든폰에 설치PC에 설치
여러 명 공유가능(한 서버, 다수 사용자)1인용1인용
언어/스택Crystal + PostgreSQLJava/KotlinJS/Vue/Electron
데이터 호환서로 구독/기록을 import·export로 주고받음(같은 생태계)
한눈에

NewPipe·FreeTube가 "내 기기에 까는 개인용 앱"이라면, Invidious는 "서버에 세워 두고 URL로 나눠 쓰는 웹서비스"다. 그래서 Invidious는 혼자 쓸 수도, 친구·가족·커뮤니티에 인스턴스를 열어 줄 수도 있다. 셋은 경쟁이라기보다 구독 데이터를 주고받는 한 생태계다.

3기술 스택 전체 지도

Crystal이라는 '아는 사람만 아는' 언어 위에 세운 웹서버

Invidious의 스택에서 가장 눈에 띄는 건 언어 선택이다. 대부분의 웹 프로젝트가 Python·JS·Go를 쓰는데, Invidious는 Crystal을 쓴다. Crystal은 "Ruby처럼 쓰기 쉬운데 C처럼 빠른(컴파일·정적 타입)" 언어를 목표로 한 마이너 언어다. 그 덕에 Invidious는 세상에서 제일 큰 Crystal 프로젝트가 됐고, 단일 정적 바이너리로 컴파일되어 배포가 가볍다.

용어 풀이
Crystal
Ruby와 문법이 거의 같지만 컴파일 언어이자 정적 타입인 언어. "Ruby의 생산성 + C의 성능"이 목표. 실행 시 인터프리터가 필요 없는 기계어 바이너리 하나로 떨어진다. 동시성은 OS 스레드가 아니라 가벼운 파이버(fiber)로 처리한다. Invidious가 이 언어의 최대 실사용 사례라, Crystal을 배우려는 사람에게 살아 있는 대형 예제다.

백엔드 (핵심)

구성요소버전역할
Crystal>=1.10 (Docker는 1.20.3)전체 언어. 정적 바이너리로 컴파일
Kemal1.6.0웹 프레임워크. Ruby의 Sinatra 같은 경량 라우팅/미들웨어
pg (crystal-pg)0.28.0PostgreSQL 드라이버(주 DB 연결)
crystal-db0.13.1DB 추상화 계층(드라이버 공통 인터페이스)
sqlite30.21.0SQLite 드라이버(데이터 import/export 처리)
protodec0.1.5iv-org 자체 제작. YouTube continuation 토큰(protobuf) 인코딩/디코딩
athena-negotiation0.1.1HTTP 콘텐츠 협상(Accept 헤더 처리)
http_proxy0.10.3아웃바운드 HTTP 프록시 지원
radix0.4.1Kemal이 쓰는 radix-tree 라우터

프런트엔드 (의외로 옛날 방식)

요즘 유행하는 React·Vue 같은 SPA 프레임워크가 전혀 없다. 화면은 ECR(Embedded Crystal) 템플릿으로 서버에서 HTML을 완성해 보낸다(PHP·ERB와 같은 옛 방식). 이게 "자바스크립트 불필요"라는 기능의 정체다. 플레이어만 video.js를 쓰고, 스타일은 순수 CSS다. 다국어는 59개 로케일 JSON(ar, de, en-US, es, fr, ja, ko, zh-* …)으로 지원하며 Weblate로 번역을 크라우드소싱한다.

인프라 / 배포

Docker 멀티스테이지 빌드로 만든다. 특이하게도 빌드 과정에서 OpenSSL 3.6.3을 소스에서 직접 컴파일하는데, "Crystal이 OpenSSL과 상호작용하는 방식에 메모리 누수가 있어서"라는 주석이 붙어 있다(이슈 #1438). 런타임 이미지는 alpine:3.24, DB는 postgres:14 컨테이너를 함께 띄운다. systemd 유닛(invidious.service)과 쿠버네티스 매니페스트도 저장소에 들어 있다.

용어 풀이
정적 바이너리 (static musl binary)
프로그램 실행에 필요한 라이브러리를 전부 실행파일 하나에 넣어 컴파일한 것. musl은 Alpine 리눅스가 쓰는 가벼운 C 표준 라이브러리다. 장점은 "이 파일 하나만 복사하면 어디서든 돈다" — 별도 설치·의존성 걱정이 없다. Invidious가 컨테이너에서 가볍게 뜨는 비결이다.

4아키텍처 심화 분석

"공식 API를 안 쓰고 YouTube 앱을 흉내 낸다" — 그리고 스트림은 분가했다

Invidious의 심장은 두 가지 설계 결정이다. 첫째, 공식 YouTube API를 안 쓴다. 대신 YouTube 앱이 내부적으로 서버와 대화할 때 쓰는 비공개 InnerTube 프로토콜을 흉내 낸다. 둘째, 영상 스트림 획득이 별도 서비스(invidious-companion)로 분리됐다. 아래 그림이 전체 흐름이다.

┌──────────┐ ①요청 ┌───────────────────────────────────────────┐ │ 사용자 │ ─────────▶ │ INVIDIOUS (Crystal/Kemal) │ │ 브라우저 │ ◀───────── │ ┌──────────────────────────────────────┐ │ └──────────┘ ⑥완성 HTML │ │ Kemal 미들웨어 체인 │ │ (JS 불필요) │ │ 압축 → API핸들러 → 인증 → 프레임차단 │ │ │ └──────────────────────────────────────┘ │ │ routing.cr가 라우트 등록 │ │ ┌────────────┬───────────┬────────────┐ │ │ ▼ ▼ ▼ │ │ │ YT_POOL COMPANION_POOL PG_DB │ │ └───┼────────────┼───────────┼────────────┘ │ ②메타데이터 ④스트림 요청 ⑤캐시 읽기/쓰기 ▼ ▼ ▼ ┌────────────────┐ ┌──────────┐ ┌────────────┐ │ youtube.com │ │invidious-│ │PostgreSQL │ │ /youtubei/v1/* │ │companion │ │ (videos는 │ │ (InnerTube 흉내)│ │(Deno/TS) │ │ UNLOGGED) │ └────────────────┘ └────┬─────┘ └────────────┘ ③제목·댓글·자막 │ ③'서명/PoToken 풀어 스트림 URL 획득 ▼ youtube 영상 서버

핵심 패턴 ① — InnerTube 흉내 내기 (공식 API 거부)

YouTube 데이터를 가져오는 코드의 심장은 src/invidious/yt_backend/youtube_api.cr다. Invidious는 https://www.youtube.com/youtubei/v1/*YouTube 앱인 척 POST를 보낸다. ClientType이라는 열거형에 흉내 낼 클라이언트 종류가 나열돼 있다: Web, Android, IOS, TvHtml5 등. 각각 진짜 앱의 이름·버전·User-Agent를 하드코딩해 둔다.

# 진짜 안드로이드 유튜브 앱 버전을 그대로 흉내
ANDROID_APP_VERSION = "21.29.366"
ANDROID_USER_AGENT  = "com.google.android.youtube/#{ANDROID_APP_VERSION} ..."
IOS_APP_VERSION     = "20.11.6"
ClientType::Web => { name: "WEB", version: "2.20260722.01.00", ... }

주요 엔드포인트 래퍼는 browse(채널·홈), next(연관영상·댓글), search, resolve_url, get_transcript다. 응답으로 온 원시 JSONvideos/parser.cr, search/processors.cr 등이 해석한다. 페이지 넘김에 쓰는 continuation 토큰은 protobuf라서 자체 라이브러리 protodec로 만든다.

용어 풀이
InnerTube
YouTube 앱·웹이 자기네 서버와 대화할 때 쓰는 비공개 내부 API. 공식 개발자용 "YouTube Data API"와 다르며 할당량·키가 없다. Invidious·NewPipe 등은 진짜 앱의 요청을 흉내 내서 이 내부 API를 호출한다. 강력하지만 YouTube가 클라이언트 버전·서명 규칙을 바꾸면 즉시 깨지므로, "고양이와 쥐" 대응이 상시 필요하다.

핵심 패턴 ② — 스트림의 분가: invidious-companion

가장 중요한 최신 변화다. 영상의 실제 재생 URL을 얻는 player() 함수는 이제 YouTube를 직접 때리지 않고, 별도 서비스에 위임한다.

def player(video_id : String)
  data = { "videoId" => video_id }
  if CONFIG.invidious_companion.present?
    # 컴패니언에게 "스트림 URL 좀 뽑아 줘" 위임
    return self._post_invidious_companion("/youtubei/v1/player", data)
  else
    return nil   # 컴패니언 없으면 재생 데이터가 없음!
  end
end

컴패니언을 붙이지 않으면 영상이 아예 재생되지 않는다. 왜 이렇게 나눴을까? YouTube가 스트림 URL에 서명(signature)·PoToken·nsig 같은 봇 차단 장치를 겹겹이 씌우는데, 이 "암호 풀기"가 워낙 자주 바뀌고 무거워서 본체에서 떼어내 독립 서비스(Deno/TypeScript)로 격리한 것이다. 본체는 COMPANION_POOL을 통해 컴패니언과 통신하고, Authorization: Bearer 토큰(16자 키)으로 인증한다.

설계 교훈
자주 깨지는 부분을 '갈아끼울 수 있게' 격리한다

가장 불안정한 조각(서명/토큰 해독)을 본체에서 분리하면, YouTube가 규칙을 바꿔도 컴패니언만 업데이트하면 된다. 본체(안정적인 웹·DB·렌더링)는 건드릴 필요가 없다. "변동성 높은 의존성을 경계 뒤로 밀어내는" 마이크로서비스 분리의 교과서적 사례다.

핵심 패턴 ③ — 부팅 시퀀스와 컴파일타임 버전 매크로

진입점 src/invidious.cr는 순서대로: 설정 로드(Config.load) → DB 연결(PG_DB, 실패 시 종료) → git 정보로 버전 계산 → 커넥션 풀 3개 생성(YT_POOL·GGPHT_POOL·COMPANION_POOL) → 테이블 무결성 검사 → 백그라운드 잡 시작 → 라우트 등록 → Kemal.run. 여기서 재밌는 건 버전 번호를 컴파일할 때 git 로그에서 뽑아 코드에 박는다는 점이다. 그래서 Dockerfile이 굳이 .git/ 폴더까지 복사한다.

핵심 패턴 ④ — 백그라운드 잡(워커)

src/invidious/jobs/의 워커들이 주기적으로 돈다. 설정에 따라 켜고 끈다.

하는 일
RefreshChannelsJob구독 채널의 새 업로드를 크롤링(channel_threads>0)
RefreshFeedsJob사용자 RSS 피드 갱신(feed_threads>0)
PullPopularVideosJob인기 영상 목록을 메모리에 캐시
NotificationJobPostgres LISTEN/NOTIFY로 새 영상 알림 전달
SubscribeToFeedsJobPubSubHubbub 구독(domain+hmac_key 필요)
ClearExpiredItemsJob만료된 nonce·세션 등 DB 청소
StatisticsRefreshJob/api/v1/stats용 통계 갱신
InstanceListRefreshJob공개 인스턴스 목록 갱신
용어 풀이
UNLOGGED 테이블 (캐시로서의 DB)
PostgreSQL에서 videos 테이블은 CREATE UNLOGGED TABLE로 선언돼 있다. UNLOGGED는 WAL(장애복구 로그)을 안 남기는 테이블이라 쓰기가 훨씬 빠르지만, 서버가 크래시하면 내용이 날아간다. Invidious는 영상 메타데이터를 "날아가도 다시 긁어오면 그만인 캐시"로 취급하기에, 안전성을 버리고 속도를 택했다. 목적에 맞는 영리한 트레이드오프다.

5디렉토리 구조 해부

142개 .cr 파일이 사는 곳 — 어디를 열면 뭐가 있나
invidious/ ├── src/ │ ├── invidious.cr # 진입점: 설정·DB·풀·잡·라우팅 부팅 │ └── invidious/ │ ├── routing.cr # 라우트 등록 매크로 + register_all │ ├── routes/ # HTTP 컨트롤러(watch·channels·search·login·companion…) │ │ └── api/v1/ # 개발자 API(videos·channels·search·feeds…) │ ├── yt_backend/ # ★YouTube와 대화하는 스크래퍼 코어(youtube_api.cr) │ ├── database/ # 테이블별 접근 계층 + migrations/(10개) │ ├── jobs/ # 백그라운드 워커 │ ├── videos/ # 영상 파싱(parser·formats·caption·transcript…) │ ├── comments/ # youtube.cr + reddit.cr (댓글 두 소스) │ ├── channels/ # 채널(about·community·playlists·videos) │ ├── search/ # 검색(query·filters·ctoken·processors) │ ├── user/ # 계정·환경설정·쿠키·캡차·import/export │ ├── frontend/ # 서버사이드 HTML 빌더(watch_page·channel_page…) │ ├── jsonify/api_v1/ # API용 JSON 직렬화기 │ ├── helpers/ # i18n·logger·tokens·utils·handlers │ └── views/ # .ecr 템플릿(watch·channel·playlist·embed…) ├── config/ │ ├── config.example.yml # 27KB 주석 달린 설정 표본 │ └── sql/ # 테이블 스키마(.sql) ├── assets/ # css·js·fonts·videojs(플레이어) ├── locales/ # 59개 번역 JSON(Weblate) ├── docker/ # Dockerfile + init-invidious-db.sh ├── kubernetes/ # K8s 매니페스트 ├── spec/ # Spectator 테스트 ├── mocks/ # 서브모듈: 녹화된 YouTube 응답(테스트용) ├── shard.yml / shard.lock # 의존성 매니페스트(package.json 격) └── docker-compose.yml # invidious + postgres:14

구조를 관통하는 규칙이 있다. "YouTube에서 긁어온다 → 해석한다 → 저장/렌더한다"의 3단계가 폴더로 나뉜다. yt_backend/가 긁어오고, videos/·channels/·search/·comments/가 원시 JSON을 우리 데이터로 파싱하고, database/가 캐시하고, frontend/·views/HTML로 렌더한다. 같은 데이터를 API로 내보낼 땐 jsonify/가 담당한다. 이 "수집·파싱·저장·표현" 분리가 대형 스크래퍼를 유지보수 가능하게 만드는 뼈대다.

6학습 포인트

이 저장소에서 실제로 배울 수 있는 것들

Invidious는 "웹앱 하나"가 아니라 대규모 스크래핑·서버사이드 렌더링·마이너 언어·셀프호스팅이 한데 얽힌 종합 예제다. 기술별로 배울 거리를 뽑아 본다.

① Crystal + Kemal — "Ruby 감성의 컴파일 웹서버"

Crystal을 배우고 싶다면 이보다 큰 실사용 예제가 없다. Kemal의 라우팅·미들웨어 체인(압축 → API → 인증 → 프레임차단)이 웹 프레임워크의 뼈대를 아주 명료하게 보여 준다. Ruby/Sinatra를 알면 술술 읽히고, 정적 타입·파이버 동시성이라는 Crystal의 강점을 실전에서 관찰할 수 있다.

② 웹 스크래핑의 정석 — InnerTube 흉내와 파싱 분리

"공식 API가 없거나 부족할 때 어떻게 데이터를 얻는가"의 모범 답안이다. 클라이언트를 흉내 내고(hardcoded client) → 원시 JSON을 받아 → 별도 파서로 해석하는 3단 구조를 그대로 배울 수 있다. protobuf 토큰(protodec) 처리, User-Agent 위장, 커넥션 풀링까지 실전 스크래핑의 정수가 담겨 있다.

주의 / 윤리
스크래핑은 '기술'이자 '책임'이다

InnerTube 흉내 기법은 강력하지만, 대상 서비스의 약관·부하·법적 경계를 항상 의식해야 한다. Invidious도 README에 면책 조항을 두고 "각국 규정을 준수하라"고 명시한다. 학습 목적으로 원리를 이해하되, 무단 대량 요청으로 남의 서비스에 부담을 주지 않는 선을 지키자.

③ 서버사이드 렌더링(SSR)의 부활 — JS 없이도 웹은 된다

React에 익숙한 사람에게 Invidious의 .ecr 템플릿은 신선한 역발상이다. "화면을 서버에서 완성해 보낸다"는 옛 방식이 경량·접근성·프라이버시(JS 불필요) 측면에서 왜 여전히 강력한지 체감할 수 있다. 최신 웹이 다시 SSR로 회귀하는 흐름(Astro·HTMX 등)의 원형을 여기서 본다.

④ 캐시로서의 DB & 컴파일타임 메타프로그래밍

UNLOGGED 테이블로 "안전성 대신 속도"를 택한 결정, 인기 영상을 메모리에 캐시하는 잡, git 로그로 버전을 컴파일할 때 코드에 박는 매크로 — 이 세 가지는 각각 "캐시 설계", "백그라운드 워커", "메타프로그래밍"의 좋은 미니 사례다.

⑤ 다중 서비스 아키텍처 — 불안정한 부분의 격리

본체 + 컴패니언 분리는 "자주 바뀌는 의존성을 경계 뒤로 밀어내는" 실전 설계다. 왜 굳이 스트림만 떼어냈는지, 그 경계에서 인증(Bearer 토큰)과 통신(커넥션 풀)이 어떻게 이뤄지는지 따라가 보면, 마이크로서비스 분리 판단의 감을 익힐 수 있다.

실습 아이디어

작게 따라 만들어 보기

· 미니 프런트엔드: 아무 공개 API(예: Hacker News)를 골라, 서버에서 JSON을 받아 순수 HTML로 렌더하는 초경량 페이지를 만들어 본다(JS 0줄 목표).

· 클라이언트 흉내: 어떤 앱의 네트워크 요청을 개발자도구로 관찰해 User-Agent·헤더를 재현하고, 같은 응답을 받아 본다(합법적 공개 API로).

· UNLOGGED 실험: PostgreSQL에서 일반 테이블 vs UNLOGGED 테이블의 대량 INSERT 속도를 직접 재 본다.

7하드웨어 / 시스템 요구사항

"런타임은 가볍고, 빌드는 무겁다"

저장소 자체에는 구체적 사양(RAM/CPU 수치)이 없다(공식 사양은 docs.invidious.io에 있다). 하지만 코드에서 성격을 읽을 수 있다.

구분성격근거
런타임(운영)가벼움단일 정적 바이너리 + alpine:3.24, 비루트(uid 1000) 실행. README도 "Lightweight"를 첫 기능으로 강조
필수 동반 서비스PostgreSQL 14영속 볼륨(postgresdata)이 붙는 DB 컨테이너 필요
필수 동반 서비스 2invidious-companion없으면 영상 재생 불가(§4). 별도 컨테이너·리소스 필요
빌드(컴파일)무거움플레이어 의존성 fetch "20분 이상", OpenSSL 소스 컴파일, crystal spec 전체 실행
동시성단일 프로세스멀티스레드(MT) 기본 꺼짐("아직 불안정"). Crystal 파이버로 동시성 처리
쉽게 말하면

Invidious는 "만드는 데는 손이 많이 가지만, 완성품은 깃털처럼 가벼운" 부류다. 처음 도커 이미지를 빌드할 땐 시간·CPU가 꽤 들지만(플레이어 의존성 + OpenSSL + 테스트), 일단 뜨고 나면 낡은 VPS에서도 잘 돈다. 다만 혼자 두면 재생이 안 되고, PostgreSQL과 컴패니언을 반드시 곁에 둬야 한다는 점을 기억하자.

8직접 해볼 수 있는 실습 과제

난이도별로 손을 움직여 보는 5단계
LEVEL 1 · 입문난이도 ★☆☆☆☆

공개 인스턴스 써 보고, 구조 관찰하기

instances.invidious.io에서 인스턴스 하나를 골라 아무 영상을 연다. 브라우저 자바스크립트를 끄고도 페이지가 뜨는지 확인하고, 개발자도구 Network 탭에서 요청이 구글이 아니라 인스턴스로만 가는지 관찰한다.

LEVEL 2 · 초급난이도 ★★☆☆☆

docker-compose로 내 인스턴스 띄우기

저장소를 클론해 docker-compose up으로 invidious + postgres를 띄운다. config.example.yml을 열어 hmac_keypwgen 20 1로 새로 만들고, 재생을 위해 invidious-companion을 붙여 본다. "컴패니언 없으면 재생 안 됨"을 직접 확인.

LEVEL 3 · 중급난이도 ★★★☆☆

개발자 API로 JSON 뜯어보기

/api/v1/videos/{id}, /api/v1/search?q=...를 직접 호출해 응답 JSON 구조를 분석한다. 그 다음 코드에서 routes/api/v1/videos.crjsonify/api_v1/로 이어지는 직렬화 경로를 따라가 본다. 설정에서 disable_abusable_api: true로 이 API를 꺼 보고 동작 변화도 확인.

LEVEL 4 · 고급난이도 ★★★★☆

InnerTube 요청을 코드에서 추적하기

yt_backend/youtube_api.cr에서 ClientType·HARDCODED_CLIENTS를 읽고, 검색 한 번이 search_post_jsonyoutubei/v1/search로 어떻게 흘러가는지 추적한다. User-Agent를 바꾸면 응답이 어떻게 달라지는지 실험(합법적 범위 내에서). continuation 토큰이 protodec로 어떻게 만들어지는지도 살펴본다.

LEVEL 5 · 심화난이도 ★★★★★

Crystal 코드에 직접 손대기

새 로케일을 locales/에 추가하거나, routes/에 아주 간단한 새 라우트(예: /hello)를 만들어 routing.cr에 등록하고 crystal build로 컴파일해 본다. 나아가 videos 테이블을 LOGGED로 바꿔 성능·복구 동작 차이를 직접 측정해 UNLOGGED 설계의 의미를 체감한다.

9관련 기술 심화 학습 로드맵

Invidious를 축으로 5주간 넓혀 가는 코스
주차주제구체적으로
1주차Crystal + Kemal 기초Crystal 문법(Ruby 유사), 파이버 동시성, Kemal로 초미니 웹서버 만들기. Invidious의 routing.cr 읽기
2주차웹 스크래핑 & 비공개 APIInnerTube 개념, 클라이언트 흉내, User-Agent/헤더, protobuf 토큰. yt_backend/ 정독
3주차PostgreSQL & 캐시 설계crystal-db, UNLOGGED 테이블, LISTEN/NOTIFY, 배열 컬럼(subscriptions/watched). database/ + config/sql/
4주차Docker 멀티스테이지 & 셀프호스팅정적 musl 빌드, 멀티스테이지, docker-compose, 리버스 프록시(external_port), 컴패니언 연동
5주차프라이버시 프런트엔드 생태계NewPipe·FreeTube·Piped 비교, import/export 호환, AGPLv3 함의, "고양이-쥐" 대응 전략 이해
학습 순서 팁

관심사에 따라 두 갈래로 파면 좋다. "웹 개발·SSR"이 궁금하면 Crystal→Kemal→ECR 템플릿→라우팅 축으로, "데이터 수집·인프라"가 궁금하면 InnerTube→파싱→PostgreSQL 캐시→Docker/컴패니언 축으로 파면 된다. 둘 다 config.example.yml(27KB 주석)과 CHANGELOG.md가 최고의 안내서다 — 설정 하나하나에 "왜 이게 필요한지"가 적혀 있고, 체인지로그엔 "무엇이 왜 깨졌고 어떻게 고쳤는지"가 쌓여 있다.

10핵심 키워드 사전

이 문서에 나온 용어 빠른 정리
용어
InvidiousYouTube의 오픈소스 대체 프런트엔드. 광고·추적·JS 없이 서버가 대신 유튜브를 긁어와 보여 줌
대체 프런트엔드원본 서비스의 화면만 제3자가 새로 만든 것. 프라이버시·경량·오픈소스 지향(Nitter·Redlib 등)
CrystalRuby 문법 + C 성능을 노린 컴파일·정적타입 언어. Invidious가 세계 최대 실사용 예제
KemalCrystal용 경량 웹 프레임워크. Ruby의 Sinatra 격. radix-tree 라우팅 + 미들웨어 체인
InnerTubeYouTube 앱·웹이 쓰는 비공개 내부 API. 공식 Data API와 다름. Invidious가 이걸 흉내 냄
ClientType흉내 낼 클라이언트 종류 열거형(Web·Android·IOS·TvHtml5…). 각기 앱 버전·UA 하드코딩
invidious-companion영상 스트림 URL을 뽑아 오는 별도 서비스(Deno/TS). 없으면 재생 불가. 서명/PoToken 해독 격리
서명 / PoToken / nsigYouTube가 스트림 URL에 씌우는 봇 차단 장치. 자주 바뀌어 컴패니언으로 분리 대응
protodeciv-org 자체 Crystal 라이브러리. continuation 토큰(protobuf) 인코딩/디코딩
continuation 토큰"다음 페이지"를 가리키는 YouTube의 protobuf 토큰(무한 스크롤·페이지네이션)
ECREmbedded Crystal 템플릿. 서버에서 HTML을 완성해 보냄(ERB·PHP 방식). "JS 불필요"의 정체
SSRServer-Side Rendering. 화면을 서버가 그려서 완성 HTML로 전송. 경량·접근성·프라이버시 이점
UNLOGGED 테이블WAL을 안 남겨 빠르지만 크래시 시 소실되는 Postgres 테이블. videos를 '캐시'로 취급
커넥션 풀재사용 가능한 HTTP 연결 묶음. YT_POOL·GGPHT_POOL·COMPANION_POOL 3종
HMAC 키CSRF 토큰·쿠키·PubSub 검증에 쓰는 필수 비밀키. pwgen으로 무작위 생성
PubSubHubbub새 영상을 웹훅으로 즉시 통지받는 구독 프로토콜(SubscribeToFeedsJob)
정적 musl 바이너리의존성을 다 넣어 컴파일한 단일 실행파일. "복사만 하면 어디서든 실행"
Weblate번역 크라우드소싱 플랫폼. Invidious의 59개 로케일이 여기서 관리됨
AGPLv3네트워크로 제공해도 소스 공개 의무가 있는 강한 카피레프트 라이선스. 수정 시 소스 URL 공개 필요
날짜 기반 버전semver 대신 2.YYYYMMDD.micro 형식(예: 2.20260723.0). 롤링 릴리스

11참고 링크

원문으로 더 파고들기