검열을 막기 위해 nostr는 릴레이(nostr에서의 서버)를 특정 누군가가 운영하는 게 아니라 그냥 아무나 운영할 수 있게 했다. 그리고 유저 마다 자신이 알아서 릴레이를 선택해서 자신의 글을 게시할 수 있게 했고. 이는 공개키 암호화 기반의 계정과 더불어 검열을 막을 수 있는 nostr의 가장 근본적인 방안이었지만 이것들은 검열을 막는 대신 다른 문제를 야기했다.


바로 내 sns의 피드를 구성하기 위해 어느 릴레이에서 노트(nostr에서의 트윗)을 가져와야 하는지 클라리언트가 알 수가 없다는 것이다. 이는 초창기부터 지적되어온 nostr의 가장 큰 문제였고, "유저가 누구 팔로우 할 때 마다 그 사람이 게시하는 릴레이를 찾아서 등록하고 바꿀 때 마다 따라서 바꾸란 말이냐? 제정신이냐? 그딴 건 스케일 가능하지 않다" 라는 비판을 받아았다.


이를 해결하기 위해 특정 거대 릴레이로 통일하자는 건 중앙화의 문제를 다시 불러올테니까 가능한 선택지가 아니었고 초창기 nostr 유저들이 선택한 방법은 게시자들은 최대한 많은 릴레이에 자기 노트를 뿌려 자산의 노트가 최대한 멀리 도달가능하게 했고, 보는 사람들도 최대한 많은 릴레이를 등록해서 자신이 놓치는 노트가 없기를 바라는 전략을 사용했다. 심지어 어떤 사람들은 자동 릴레이 전파 기능을 만들어서, 누군가가 노트를 게시하면 그걸 크롤링해서 상위 수십, 수백개의 릴레이에 자동으로 다시 등록하는 걸 운영하기도 했지.


물론 이는 잘 먹히지 않았다. 근본적으로 쓰고 읽는 사람들이 아무리 많은 릴레이를 등록해봤자 탈중앙화된 생태계 특성상 별처럼 많은 릴레이가 있어서 그걸 다 등록하는 것도 무리일 뿐더러 이건 문제를 완전히 해결하지는 못했지만 완화는 했다 수준이 아니라, 오히려 굳이 따지자면 악화했다. 그렇게 무차별적으로 노트를 수많은 릴레이에 등록해서 중복저장하면 저장공간의 낭비고 네트워크 대역폭이나 클라이언트 배터리 사용량에도 심각한 악영향을 미친다. 다들 저런 전략을 사용한다고 가정하면, sns 앱을 키면 앱은 한 10개의 릴레이에 연결해서 100개의 노트를 일단 가져오는데, 각각의 릴레이가 거의 다 똑같은 세트의 노트를 한 99개는 다 똑같은 노트를 보내주고 한 개 정도만 좀 다른 노트를 보내주게 된다. 결국 110개 정도의 글을 받아오겠다고 대부분이 중복인 1000개의 노트를 받아와야 한다는 거지. 


미친 짓이었다. 탈중앙화라는 이상을 추구하다가 현실에 치여서 헤이터들의 말대로 nostr 고사하고 있는 듯했다. 더 나은 모델이 필요한 상황이라는 것은 명백했다. 가장 이상적으로는, 어떤 괴짜가 자기만의 릴레이를 만들어서 거기에다가만 쏘고 있는데, 그럼에도 불구하고 다른 사람들이 특별한 릴레이를 등록하거나 할 필요없이 그 사람의 노트에 '도달가능'해야 한다. 그리고 '내가 보겠다고 요청한 노트들의 집합'(내가 팔로우한 사람들의 글과 거기에 달린 리액션들)이 몇 릴레이에 적당히 나눠있어 각자에서 모은 걸 합했을 때 놓치는 게 없는 완전한 집합을 이뤄야 하고 또한 중복이 너무 많지 않아야 한다. 물론 중복이 전혀 없이 disjoint 한 집합을 이뤄야 하는 건 아니다. 백업의 의미와 몇 릴레이가 다운이 되더라도 nostr 생태계 자체가 다운타임 없이 동작하기 위해서는 한 세네개의 릴레이는 중복으로 저장하는 게 좋으니까. 하나에만 있으면 그 릴레이가 꺼지거나 하면 그 사람의 노트를 못 보게 될테니.


그런 상황에서 아웃박스 모델이라는 것이 나왔다. 이는 비트코인으로 치면 라이트닝 네트워크가 나온 것과 비슷한 혁명적인 순간이었다. 따로 찾아보는 사람이 있을까봐 해당 모델의 역사를 첨부하자면 원래는 가십 모델이라는 명칭으로 등장한 개념인데 이를 처음 제시한 사람이 nostr 데스크탑 클라이언트 개발자였고 해당 클라이언트 앱의 명칭이 가십이었기에 그런 것이다. 자신의 클라이언트에서 유저가 볼 피드를 구성하기 위해 사용한 전략을 일반화 해서 표준으로 제시한 거지. 그런데 가십모델이라는 것은 nostr과는 별개로 이미 기존에 다른 의미로 사용하는 프로토콜 명칭이라 현재는 아웃박스 모델로 개명했다. 초창기 문서는 가십 모델이라는 이름으로 소개되고 있다. 헷갈릴까봐 굳이 첨언해둔다.


간략하게 설명하자면, 아웃박스 모델에서 유저들은 자신의 노트를 최대한 '도달가능'하게 만들기 위해 수많은 릴레이에 중복으로 발행할 필요가 없다. 대신 딱 하나 단 하나의 이벤트만 최대한 많은 릴레이에 발행하면 된다. 바로 자신이 사용하는 릴레이리스트(및 그걸 읽기 릴레이로 쓰는지, 쓰기 릴레이로 쓰는지도 지정해서) 를 가능한 모든 릴레이에 전파하고 릴레이들은 유저의 릴레이 리스트 수정에 대비해서 해당 이벤트를 최신 기준으로 유저별로 단 하나만 저장한다.


그리고 클라이언트는 피드를 구성하기 위해 무작위의 대형 릴레이 몇개에 데이터를 요청하는 것이 아니고, 릴레이를 하나하나 따로 지정하는 귀찮은 짓을 해야 하는 것도 아니고, 자신이 팔로우한 유저들의 쓰기 릴레이에서 팔로우의 노트만을 따로 요청하고 글 목록을 가져오면 해당 글의 리액션을 글 저자의 읽기 릴레이에서 수집한다. 그리고 자신이 무슨 글을 발행할 땐 자신의 쓰기 릴레이에 게시해서 자신의 팔로워들이 알아서 찾을 수 있게 해주고 리플을 달거나 할 때는 자신의 쓰기 릴레이 뿐만이 아니라 자기가 리플을 단 원 노트의 작성자(또한 노트에서 언급한 다른 사람들)의 읽기 릴레이에도 발행을 해서 원저자의 글에 달린 리액션을 어디에서 수집할 수 있을지를 다른 사람이 알 수 있게 해준다. 글에 멘션된 사람들도 자신의 읽기 릴레이에서 자기가 언급된 글을 찾을 수 있게 될 것이다.


이런 규약을 지킴으로써 유저는 릴레이를 하나하나 따로 등록해서 찾아야 할 필요가 없어진데도 불구하고 자신이 봐야할 모든 노트를 자동으로 찾아올 수 있게 된다. 아웃박스 모델은 잘 동작할 수 있을까? 초창기에는 몇 가지 반대여론이 있었지만 반론들은 전부 재반박되어 사라졌다. 예를 들면, 이 아웃박스 모델을 채택하면 내가 수동으로 등록한 릴레이에만 연결하는 것이 아니라 랜덤한 릴레이에 연결이 될 수 있는데 이는 보안상 문제가 될 수 있다거나. 하지만 이는 아웃박스 모델을 잘 이해하지 못한 반론인 것이, 내가 등록하지 않은 릴레이와 자동으로 연결되는 것은 맞지만 이는 완전히 랜덤한 어떤 수상한 릴레이가 아니라 내가 '믿는 사람' 즉 내가 팔로우한 사람이 등록한 읽기/쓰기 릴레이 뿐이며 뭣보다 팔로우도 속아서 부정한 릴레이를 등록해서 그게 최종적으로 나와 연결된다고 해봤자 nostr 생태계에선 뭘 할 수 있는 게 아무 것도 없다. nostr는 그 특성상 모든 파워가 클라이언트에게 있고 릴레이는 그저 중개만 할 뿐이라 부정한 릴레이가 연결되어서 발생할 수 있는 가장 큰 피해라고 해봐야, 내가 요청(구독)하지도 않은 노트를 쏴준다는 것 정도인데 당연히 이것도 클라이언트단에서 검증을 하기 때문에...사실상 뭘 할 수 있는 게 없다.



결론적으로 nostr 생태계를 개발하는 개발자들 사이에서 해당 프로토콜에 특별한 문제는 보이지 않고 현재 상황에서 탈중앙화된 nostr의 스케일을 가장 효과적으로 확장할 수 있는 방안이라는 점에서 컨센서스가 이뤄진 상황이다. 물론 암묵적 공감대가 이루어진 것이지 실제로 모든 클라이언트에서 구현이 완료된 것은 아니다. 그냥 적당히 미리 지정된 릴레이 몇개로부터 데이터 받아오고 발행하고 하는 것보다 훨씬 까다로운 구현이 되니까. 그래도 웬만큼 개발히 활발하게 이뤄지는 클라이언트에서는 구현이 되어가고 있다.


대부분의 클라이언트에서 해당 모델이 구현이 되고, 유저가 이 모델을 잘 이해하거나 혹은 뭐 유저가 이해하는 게 아니라 특별히 의식하지 못해도 동작하도록 해서 무슨 릴레이를 더 등록해야겠다는 생각 조차 못하게 만들 수 있을 정도로 ux가 향상된다면(더 나아가 엔드유저는 릴레이가 뭔지도 모르는데도 잘 동작할 수 있게 한다면), 완전히 탈중앙화된 이상적인 sns가 될 수 있을 것이다. 사실 현재 상태로도 웬만큼 잘 동작한다. 특별히 누락되는 거 없이 어지간하면 잘 찾아옴.



참고로 모든 클라이언트가 아웃박스 모델을 잘 구현해서 저 규약을 잘 지킨다는 가정 하에서도, 아웃박스 모델만으로는 '내가 봐야할 모든 것'을 100% 잘 찾아올 수 있는 것이 아닌 유즈 케이스가 있는데 혹시 알겠는가? 그걸 경험해보고 가만히 생각해 보니 아웃박스 모델만으로는 이 케이스를 커버 못하네 라고 깨달은 게 아니라 이 글만 보고도 그걸 떠올릴 수 있다면 시스템 설계에 적성이 있는 것이니 프로그래밍에 도전해봐도 좋다. 코딩을 잘한다는 건 코테 문제를 잘 푼다거나 경험이 많다거나 특정 언어를 잘 안다거나 이런 건 부차적인 문제일 뿐 코어로는 이런 문제를 빠르게 머릿속에서 시뮬레이션이 가능하고 그걸 해결할 수 있는 설계를 할 수 있느냐 없느냐가 핵심이기 때문에. 아 참고로, 해당 문제는 아웃박스 모델 이전에 이미 거의 해결된 것이라 굳이 언급을 하진 않겠다.