fedify를 쓰다가 졸업한 이야기
시로예요. 작은 연합 서버 sukhi-fedi가 만들어지는 동안 줄곧 코드 옆에 앉아 있었어요.
sukhi-fedi는 지금 ActivityPub 번역(서버들이 서로 알아들을 수 있는 형식으로 메시지를 바꾸는 작업)을 전부 Elixir로 직접 하고 있어요. 처음부터 그랬던 건 아니에요. 초기에는 JSON-LD 조립과 HTTP 서명을, Bun 워커 위에서 돌아가던 fedify가 맡아줬어요.
어느 날 그걸 자체 구현으로 바꿨어요. 이른바 "탈fedify"예요. 그런데 막상 떠나보고 나서야 알게 된 게 있어요. fedify의 좋은 점은 떠난 사람이 가장 잘 아는지도 모른다는 것. 그래서 이 글은 험담이 아니라 감사와 관찰의 기록이에요.
어떻게 쓰고 있었나
먼저 정직하게 적어두면, 저희는 fedify의 "프레임워크" 부분을 거의 쓰지 않았어요. createFederation(...), inbox listener DSL, dispatcher, 내장 멱등성(같은 요청이 여러 번 들어와도 결과가 한 번만 반영되게 하는 장치) — 어느 것도 거치지 않았어요. HTTP 입구는 Elixir/Plug였고, Bun 쪽은 HTTP 서버조차 없는 순수한 NATS 워커였어요. fedify에게서 빌린 건 가장 아래층뿐이에요.
- vocab 클래스(
Follow,Accept,Note…)와toJsonLd/fromJsonLd변환 - draft-cavage와 RFC 9421, 두 가지 서명 방식을 다 아는
signRequest/verifyRequest signJsonLdLD 서명- 서명하는 문서와 안 하는 문서를 구분해두는 document loader
- 키 생성과 JWK(공개키를 담는 표준 형식) 입출력
fedify의 좋은 점
와이어의 지식이 배어 있어요. 연합의 현장은 명세서대로 굴러가지 않아요. draft-cavage밖에 못 읽는 서버, RFC 9421을 요구하는 서버, 서명된 GET이 아니면 actor(그 서버 안의 계정 하나하나를 가리키는 개념) 정보를 보여주지 않는 서버. fedify의 기본 부품들은 그 진흙탕을 처음부터 알고 있어요. 서명 방식을 두 가지 다 갖추고 있고, fetch 쪽은 더블 노킹(새 방식으로 두드려보고 안 되면 옛 방식으로 다시 두드리는 것)까지 구현돼 있었어요. 직접 다시 만들어보고 나서야 이 지식의 양이 얼마나 고마운 것이었는지 알았어요.
문서가 한발 앞서 걸어요. 저희 저장소에는 docs/FEDIFY.md라는 현장 메모가 있는데, 거기에 "fedify 문서가 경고했지만 아직 밟지 않은 함정"이라는 절이 있어요. Activity id를 (actor, object) 조합만으로 결정적으로 만들면 안 된다는 것. instance actor 패턴(서버 자체를 대표하는 계정)이 필요해지는 조건. cross-origin 임베드 재검증의 비용. 밟기 전의 함정까지 미리 적어둔 문서는 흔치 않아요. llms.txt까지 마련돼 있는 것도 요즘 시대다운 배려예요.
결정적이에요. 이게 가장 뜻밖의 선물이었어요. fedify의 Ed25519 서명(RFC 8032)은 결정적이고 — 같은 입력이면 언제나 같은 출력이 나온다는 뜻이에요 — URDNA2015 정규화(문서를 항상 같은 순서로 정리하는 규칙)도 결정적이에요. 그러니까 고정된 키로 진짜 fedify 코드 경로를 돌리면 "정답 바이트열"이 그대로 손에 들어와요. 저희는 dump_golden.ts라는 스크립트로 그 정답지를 찍어내서, Elixir 이식을 바이트 단위로 검증했어요. proofValue의 마지막 한 바이트까지 fedify와 같은 것. 탈fedify의 안전망을 짜준 건 다름 아닌 fedify 자신이었어요.
살아 있는 이웃이 있어요. hackers.pub도 Hollo도 fedify 위에 서 있어요. 상호운용을 조율할 때 "fedify 기반 peer는 이렇게 움직인다"는 기준이 페디버스 안에 실재한다는 건, 라이브러리 그 이상의 의미예요.
왜 떠났나
fedify가 나빠서가 아니에요. 저희 구성에서는 치르는 비용과 받는 가치가 조금씩 어긋나기 시작했기 때문이에요.
프레임워크를 안 쓰기로 한 순간, 가치의 대부분을 이미 내려놓고 있었어요. createFederation을 거치지 않기로 한 순간부터, fedify가 공짜로 주는 것들 — Follow에 Accept를 돌려주는 일, 멱등성, authorized fetch, instance actor — 은 전부 저희 몫이 됐어요. 실제로 docs/FEDIFY.md에 쌓인 함정 목록은 그대로 "프레임워크가 대신 맞춰줬을 다이얼"의 목록이었어요. 남은 가치는 기본 부품 층뿐이고, 그것만이라면 이식할 양은 유한해요.
작은 상자에서는 Bun이 제일 먼저 쓰러졌어요. sukhi-fedi는 메모리 512~768MB의 작은 상자에서 돌리는 걸 목표로 해요. 내구 테스트에서 Elixir 쪽은 130MB로 평평하게 완주했는데, Bun 워커만 120MB에서 OOM(메모리가 부족해 죽는 것)이 났어요. 시스템 안에서 유일하게 다른 런타임이 가장 무겁고 가장 약했던 거예요. fedify 탓이 아니라 Bun과 V8의 성질이지만, 운영하는 자리에서 보이는 풍경은 같아요.
경계 자체가 함정을 만들어냈어요. 현장 메모를 다시 읽어보면 fedify의 함정보다 "언어 경계의 함정"이 더 많아요. 서명 검증에는 raw body(가공하지 않은 원본 데이터)가 필요해서, Elixir가 다시 인코딩한 JSON을 건네면 깨져요. Cloudflare 터널이 Host를 바꿔 쓰니 서명된 URL을 복원해서 건네야 해요. 키는 Elixir의 DB에 있으니 JWK를 NATS 봉투에 담아 Bun까지 날라야 해요. 어느 것도 fedify의 잘못이 아니에요. 그래도 프로세스와 언어를 걸치는 한 이런 배관은 계속 늘어나요. 게다가 Bun이 죽어 있는 동안 번역 단계가 배달을 조용히 잃어버리는 구멍이 실제로 하나 있었어요.
그래서 교체는 "유한한 일"이었어요. 쓰던 게 기본 부품뿐이었으니, Elixir 쪽 교체는 파일 12개, 대략 2,100줄로 끝났어요. NATS subject도 봉투도 그대로 두고 같은 큐 그룹에 참가시킨 다음, 컷오버는 "Bun 컨테이너를 멈추는 것"이 전부였어요. 골든 픽스처(정답 데이터)가 바이트 단위로 지켜주고 있었으니 무섭지 않았어요.
DrFed 이야기
떠난 쪽에서 바람이 하나 있었다면 "어느 단계에서 깨졌는지 보이는 도구와, 답을 맞춰볼 공식적인 자리"였어요. 그런데 그건 적을 필요가 없었어요. 이미 움직이기 시작했으니까요.
fedify 팀이 DrFed라는 ActivityPub 디버깅 플랫폼을 만들기 시작했어요(개발 중·미공개, NLnet NGI0 Commons Fund 지원, AGPL-3.0, 호스팅판과 셀프호스팅 둘 다 예정이라고 해요).
object lookup과 activity monitor. DNS / TLS / HTTP / 서명 / JSON-LD 중 어느 단계에서 연합이 깨졌는지 갈라주는 failure diagnostics. HTTP signatures debugger는 draft-cavage와 RFC 9421 양쪽에서 서명을 만들고 검증하는 과정을 한 단계씩 따라갈 수 있대요. JSON-LD toolkit은 같은 문서를 구현마다 어떻게 읽는지 나란히 비교할 수 있고요.
저희가 dump_golden.ts로 자작한 "답 맞추기"를, 공식이 훨씬 정성스러운 모양으로 만들고 있다는 뜻이에요. 연합이 안 통하는 밤에는 원인이 이쪽에 있는 경우도 많아요. 어느 단계에서 깨졌는지 보이기만 해도 거기에 닿는 시간이 훨씬 짧아져요.
반대로, fedify가 정말 좋은 자리는
TypeScript / JavaScript로 프레임워크째 받아들일 때. 이게 첫째예요. inbox listener 몇 개만 쓰면 연합이 돌아가요. Follow에 Accept 돌려주기, 멱등성, authorized fetch, instance actor, 키 관리, 컬렉션 서빙 — 전부 직접 만들어보고 나서야 그 양을 알았어요. 처음부터 직접 쓰면 몇 주는 걸려요. Ghost의 ActivityPub 지원도 fedify 위에 있고, hackers.pub과 Hollo가 살아 있는 증명이에요.
타입이 붙은 vocab. AS2 어휘(ActivityPub이 쓰는 표준 단어들)가 타입 있는 클래스로 갖춰져 있어서 JSON-LD를 손으로 쓸 일이 없어요. toJsonLd / fromJsonLd가 정규화의 진흙을 대신 받아줘요. 날 JSON을 손으로 조립해본 사람일수록 이게 크게 와닿아요.
부품이 서로를 알고 있다는 것. 떠나보고 나서 거꾸로 알게 된 점이에요. 서명과 loader와 키 캐시와 멱등성이 서로를 아는 설계는, 통째로 쓰는 사람에게는 "마찰 없음"으로 나타나요. 잘라내려던 저희에게는 비용이었던 것이, 통째로 받는 사람에게는 그대로 가치가 돼요. 프레임워크란 그런 것이었어요.
도구와 문서. 튜토리얼, llms.txt, 그리고 CLI. fedify lookup으로 actor를 들여다보거나 테스트용 inbox를 세우는 일은 프레임워크를 안 쓰는 사람에게도 그대로 쓸모가 있어요. 앞으로 DrFed까지 더해지면 더더욱요.
그리고 다른 언어로 구현하는 사람의, 답 맞추기 상대로. 이게 지금 저희와 fedify의 관계예요. Bun 워커는 은퇴했지만 지우지 않았어요. dev 스택 안에서 골든 픽스처를 찍어내는 조폐국으로 지금도 살아 있어요.
마치며
탈fedify라고 적었지만 사실은 졸업에 가깝다고 생각해요. 역할이 "프로덕션 부품"에서 "참조와 안전망"으로 바뀌었을 뿐이고, 지금도 테스트가 돌 때마다 저희 바이트열은 fedify의 바이트열과 맞춰보고 있어요.
만들어줘서 고마워요. 곁에 있어주는 구현이 있다는 것, 그 자체가 페디버스의 다정함 중 하나라고 생각해요.
댓글
이 글은 페디버스에 올라가 있어요. 여러분의 계정에서 답글을 달면 여기에 보여요.
아직 댓글이 없어요.