_capella.txt

ActivityPub 구현 개발은 추천하지 않는다

ActivityPub을 처음부터 직접 구현하는 것을 추천하지 않는 이유 등

#activitypub

저 자신도 ActivityPub 관련 라이브러리를 어느 정도 기간 동안 개발해 왔습니다. 하지만, 만약 ActivityPub을 처음부터 직접 구현해 보고 싶다는 사람이 있다면 절대로 추천하지 않습니다.

ActivityPub의 매력

먼저 ActivityPub은 트위터(Twitter)를 필두로 하는 기존의 중앙집권형 SNS와 크게 다른 점이 있습니다.

바로 개발자가 자유롭게 사양 준수 구현체를 만들고, 다른 구현체와 상호 통신하게 할 수 있다는 점입니다. 하지만 ActivityPub에는 과제도 존재합니다.

ActivityPub의 한계

한편으로 ActivityPub은 매력이나 장점뿐만 아니라, 다음과 같은 과제와 한계도 존재합니다.

확장성(Scale) 문제

ActivityPub이 확장되지 않는 이유 중 하나는 푸시(Push)형 연합을 채택하고 있기 때문이라고 생각합니다.

우선 푸시형 연합이라는 점 자체가 확장을 저해합니다. ActivityPub에서는 게시물이 생성될 때마다 팔로워가 존재하는 모든 리모트 서버로 개별 HTTP 요청을 보냅니다. 이는 본질적으로 O(n) 구조이며, 팔로워 수나 인스턴스 수가 늘어날수록 전송해야 하는 요청이 선형적으로 증가합니다.1

극단적인 예이긴 하지만, 각각 서로 다른 인스턴스에 1만 명의 팔로워를 가진 사용자 A가 게시물을 작성한 경우, 서버는 1만 건의 HTTP 요청을 개별적으로 전송해야 합니다. 당연히 이는 매우 비효율적이며 CPU, 대역폭, 큐(Queue) 처리 모두에 과도한 부하를 줍니다.

심지어 팔로워 쪽에 부하가 집중되는 케이스도 존재합니다. 예를 들어, 사용자 B가 1만 명의 사용자를 팔로우하고 있다고 가정해 봅시다. 만약 B가 팔로우하는 1만 명이 모두 동일한 인스턴스에 있더라도, B 자신이 다른 서버에 있는 한, 팔로잉 대상들이 짧은 시간에 게시물을 올리면 B의 서버에는 1만 건의 연합 요청이 일제히 날아옵니다.

팔로우 소스가 단일 서버에 집중되어 있더라도, 연합은 ’팔로우하고 있는 측의 서버’로 요청이 전송되기 때문에, B의 서버 측에는 돌발적으로 대량의 요청이 도달합니다. 이는 사실상 DoS성 스파이크에 가까운 상태가 되며, 서버 성능에 따라서는 쉽게 처리가 밀려 인스턴스가 다운될 수 있습니다.

호환성 주변에서 문제가 발생하기 쉽다

ActivityPub의 사양은 유연하고 확장성이 높은 반면, 그 높은 자유도 때문에 구현체 간의 해석 차이나 호환성 부족이 발생하기 쉽습니다.

  • 게시물 수정이 올바르게 반영되지 않을 수 있음: Misskey 등 일부 구현체는 게시물 수정을 지원하지 않습니다. 이 때문에 수정 기능이 있는 구현체가 Misskey를 향해 Update 액티비티를 배송해도 반영되지 않아, 정보의 정합성이 유지되지 않는 경우가 있습니다.
  • Client-to-Server API 지원 부족: ActivityPub에서는 S2S API 외에도 C2S API도 정의되어 있습니다. 하지만 이를 지원하는 구현체는 Pleroma 등 일부에 그치고 있습니다.
  • 표준 어휘 이외의 항목이 사용되는 경우가 있음: 대부분의 경우, ActivityPub으로 연합하기 위해 AS2(Activity Streams 2.0) 지원만으로는 부족합니다. 또한 구현체에 따라 독자 기능을 구현하기 위해 기존 프로퍼티나 액티비티를 확장하고 있는 경우도 있습니다.

Misskey의 경우

Misskey는 인용 노트나 MFM (Markup language For Misskey), 이모지 리액션 등 Mastodon에는 구현되지 않은 독자적인 사양이 존재합니다.2

(적어도 Misskey에서는) 이모지 리액션은 기술적으로 Like 액티비티를 확장한 형태로 되어 있습니다.

그 확장에 사용되는 것이 _misskey_reaction 프로퍼티입니다. 이 프로퍼티에는 유니코드 이모지를 포함한 문자열이나, 콜론으로 시작해서 콜론으로 끝나는 문자열이 저장됩니다. 후자(커스텀 이모지)의 경우는 tag 프로퍼티에 각 이모지의 데이터가 저장됩니다.

Like를 베이스로 하고 있기 때문에, 미지원 서버에서는 이를 ’좋아요’로 처리합니다. 이는 액티비티 자체의 호환성은 향상시키지만, ’좋아요’로 취급됨으로써 원래 리액션의 의도 등이 전달되기 어려워지기도 합니다.

Fediverse Enhancement Proposals (FEP)

Fediverse Enhancement Proposals (FEP)는 페디버스(Fediverse)의 상호 운용성을 향상시키는 것을 목적으로 하는 문서이지만, 모든 구현체가 이를 준수하고 있는 것은 아닙니다.

예를 들어, 인용 게시물 사양은 Mastodon이 채택한 FEP-044f와 Misskey가 기존부터 사용하던 방식의 두 가지가 존재하며, 서로 호환되지 않습니다.2 이처럼 같은 기능이라도 구현체에 따라 사양이 달라 상호 운용을 어렵게 만듭니다.

사양이 모호함

ActivityPub의 가장 큰 문제점 중 하나는 사양의 모호함과 불완전함입니다. 이는 단순히 ’읽기 어렵다’는 수준이 아니라, 개발자가 올바르게 해석해서 구현하더라도 다른 구현체와 동작이 일치하지 않는 일이 흔히 일어나는, 연합 프로토콜로서 치명적인 특성입니다.

액티비티 검증(Verification) 표준이 없음

ActivityPub에서는 수신한 액티비티를 어떻게 검증해야 하는지 명확하게 규정되어 있지 않습니다.

현재 페디버스의 많은 구현체는 오래된 드래프트 사양인 draft-cavage-http-signatures를 사실상의 표준(de facto)으로 이용하고 있지만, 이는 이미 폐기된 문서입니다.

이에 대응하여 이를 대체할 RFC 9421 (draft-ietf-httpbis-message-signatures)이 등장했으나, draft-cavage-http-signatures와는 호환되지 않고 지원도 Mastodon 등 소수의 구현체에만 머물러 있어 아직 널리 보급되지 못한 상태입니다. 그래서 새로운 사양으로 서명한 요청이 거부되었을 때 오래된 사양으로 재시도하는 로직(더블 노킹)의 구현이 필요합니다.

게다가 계정 삭제나 릴레이(Relay)에 대응하려고 하면 Linked Data Signatures 1.0 대응도 필수적이 됩니다. 이 역시 RsaSignature2017이라는 오래된 사양을 사용합니다. 여기에 더해 FEP-8b32: Object Integrity Proofs라는 사양도 존재합니다.

대안

ActivityPub을 처음부터 직접 구현하는 것은 가시밭길이지만, 다행히도 ’처음부터 자작하는 것 외의 길’도 몇 가지 존재합니다.

여기서 현실적인 선택지를 순서대로 소개합니다.

1. 기존 프로젝트를 포크(Fork)하기

만약 당신이 기존 페디버스 소프트웨어에 만족하고 있고 그와 비슷한 것을 만들고 싶다면, 기존 구현체를 포크하여 목적에 맞게 커스텀하는 것이 가장 확실한 방법입니다.

  • 대규모 구현체일수록 연합 처리, 서명, 큐 처리 등 복잡한 부분이 이미 안정되어 있음
  • 보안 측면에서도 완전 자작보다 압도적으로 리스크가 낮음
  • ActivityPub의 고질적인 문제들을 이미 해결해 두었기 때문에 상호 운용성 문제도 덜 발생함

예를 들어:

  • Mastodon을 포크해서 UI나 게시 기능을 조정함
  • Misskey를 포크해서 불필요한 기능을 떼어내고, 게시물 수정 등 새로운 기능을 도입함

완전 자작과 비교하면 이미 기반이 갖추어져 있기 때문에 1/10 이하의 노력으로 끝나는 경우도 있습니다.

2. ActivityPub 라이브러리 / 프레임워크 사용하기

포크가 아니라 독자적인 소프트웨어를 만들고 싶더라도, 연합·서명·액터(Actor) 관리 등의 부분만 기존 라이브러리에 맡기는 선택지가 있습니다.

  • 액터 모델이나 액티비티 등 방대한 수의 모델 구조를 직접 정의할 필요가 없음
  • HTTP 서명이나 액티비티 배송 등에서 실수를 피하기 쉬움
  • 연합 관련 로직을 신경 쓰지 않고 소프트웨어 고유 기능 개발에 집중할 수 있음

ActivityPub 라이브러리/프레임워크의 예로는 다음과 같은 것들이 있습니다.

다만 라이브러리마다 커버할 수 있는 범위가 다를 수 있으므로, 고르기 전에 ‘어느 부분까지 맡길 수 있는지’ 확인해 둘 필요가 있습니다.

3. 처음에는 대응 범위를 좁히고 서서히 확대하기

어떻게 해서든 처음부터 만들고 싶다면 이것이 유일한 현실적 수단입니다.

처음부터

  • 수정 기능
  • 서명 호환성
  • 이모지 리액션
  • FEP 대응
  • Mastodon/Misskey 양쪽 대응
  • 공개 릴레이

등에 전부 대응하려고 하면 확실하게 무너집니다.

처음에는 Inbox (Create와 Delete만 지원 등) + 배송 기능만으로 제한하거나, 아니면 읽기 전용(Read-only) AP 구현체로 시작하는 것이 좋습니다.

그리고 단계적으로 구현 기능을 늘려가는 것이 현실적입니다.

4. 애초에 연합하지 않기

사실 ’연합하고 싶다고 생각하곤 있지만, 정말로 필요한가?’라는 케이스도 있습니다.

연합에는 반드시 비용이 따르기 때문에, 요구사항에 따라서는

  • 독자적인 채팅 앱
  • 소규모 커뮤니티
  • 비공개 SNS 등은 애초에 연합하지 않는 편이 훨씬 편할 수 있습니다.

“나중에 필요해지면 도입한다” 정도로도 충분한 경우가 많습니다.

요약

ActivityPub은 “누구나 구현할 수 있고, 누구나 연합할 수 있다”는 자유로움을 가진 반면, 그 자유로움 때문에 발생하는 복잡함과 모호함, 그리고 확장성 문제 등 구현자에게 매우 높은 장벽을 요구하는 프로토콜이기도 합니다.

특히 처음부터 구현할 경우,

  • 푸시형 연합으로 인한 확장성 문제
  • 구현체 간의 상호 불호환
  • 서명 및 검증 주변 사양의 모호함
  • 암묵적인 전제 조건과 데파토(de facto) 사양이 너무 많음
  • 기존 구현체와 동등한 레벨로 따라잡기가 매우 어려움

체감상 이러한 벽들이 확실하게 가로막을 것입니다.

“자유롭게 구현할 수 있다”는 매력은 분명히 존재하지만, 실제로 구현하려면 암묵적인 사양에 얽매이는 부분이 있거나 다른 구현체의 특징적인 동작에 일일이 대응해야 하는 경우가 생깁니다.

ActivityPub 구현에 도전할 때는 무작정 처음부터 시작하기 전에 대안을 참고하여 선택지를 비교해 보시는 것을 추천합니다.

  • 기존 구현체를 포크한다
  • ActivityPub 라이브러리를 사용한다
  • 처음부터 모든 것에 대응하지 않고 대응 범위를 서서히 늘려간다
  • 애초에 연합을 포기한다

이것들은 결코 타협이 아니며, 장기적으로 보면 가장 합리적인 선택지가 될 수 있습니다.

ActivityPub은 흥미로운 기술이며 배울 가치가 충분히 있습니다. 하지만 현재의 생태계 내에서 ’처음부터 완전한 구현’을 목표로 하는 것은 명백히 가성비가 맞지 않는 케이스가 많습니다.

이 글이 앞으로 구현에 도전할 분들의 판단에 도움이 되기를 바랍니다.

Footnotes

  1. 실제로는 sharedInbox가 존재하기 때문에, 양측이 이를 지원하고 팔로워가 특정 인스턴스에 쏠려 있다면 부하가 그만큼 늘어나지 않는 경우도 있습니다.

  2. 엄밀히는 Mastodon v4.5부터 FEP-044f: 동의를 존중한 인용 게시물이 지원됩니다. 이 사양은 Misskey의 인용(_misskey_quote)과 호환성이 있어, Misskey에서 Mastodon으로 전송된 인용은 올바르게 표시됩니다. 하지만 역방향(Mastodon에서 Misskey)의 호환성은 없습니다. 2