
이 요약은 당근의 피드 시스템에서 Feed-Entity라는 개념이 어떻게 탄생했고, 어떤 문제들을 해결했으며, 앞으로 어떻게 발전해 나갈지에 대한 내용을 담고 있어요. Feed-Entity는 당근 내 다양한 콘텐츠를 표준화된 형태로 관리하고 시스템 확장성을 높여 사용자에게 더욱 풍부하고 개인화된 피드 경험을 제공하기 위한 핵심적인 프로젝트랍니다.
안녕하세요! 저는 당근 피드인프라팀에서 소프트웨어 엔지니어로 일하는 Lebron이라고 해요. 저희 팀은 하루에 수백만 명의 사용자들이 당근 앱을 열었을 때 가장 먼저 보게 되는 피드 경험을 책임지고 있답니다. 사용자의 관심사에 맞춰 맞춤형 콘텐츠를 적절한 위치에 보여주기 위해 복잡한 피드 시스템을 운영하고, 대규모 트래픽도 안정적으로 처리할 수 있는 인프라를 구축하고 있어요.

당근 피드에는 중고차, 부동산, 중고거래, 알바 등 정말 다양한 콘텐츠들이 끊임없이 올라오고, 이 콘텐츠들은 여러 맥락으로 연결되어 사용자들에게 보여져요. 그런데 이렇게 다양한 콘텐츠들을 다루다 보니 "어떻게 하면 데이터를 좀 더 일관성 있게 저장하고 활용할 수 있을까?" 하는 고민이 자주 생겼어요.
바로 이런 고민에서 Feed-Entity라는 프로젝트가 시작되었어요! Feed-Entity는 단순히 데이터를 표준화하는 것을 넘어, 당근의 피드 시스템이 더욱 확장 가능하고 유연하게 발전할 수 있도록 만들었답니다. 덕분에 피드에 새로운 서비스를 더 빠르게 통합하고, 사용자들에게 훨씬 다양하고 풍부한 콘텐츠를 제공할 수 있었죠.
Feed-Entity가 도입되기 전에는 피드 시스템이 여러 서비스에 분산되어 운영되고 있었어요. 예를 들어, 중고거래 서비스는 자체 데이터베이스에 상품 정보를 저장하고, 알바 서비스는 또 다른 저장소에 구인구직 정보를 보관하는 식이었죠. 피드 시스템은 이 여러 저장소에서 데이터를 끌어와 사용자에게 보여주는 역할을 했어요.

초기에는 이런 구조가 단순하고 괜찮았지만, 당근 서비스가 다양해지고 규모가 커지면서 여러 문제점이 나타나기 시작했어요. 각 서비스마다 데이터 구조와 저장 방식이 달라서, 새로운 서비스를 피드에 추가할 때마다 많은 통합 작업이 필요했거든요. 😢
이런 문제들을 해결하기 위해 저희는 Feed-Entity라는 새로운 개념을 도입했어요. Feed-Entity를 통해 이루고 싶었던 목표들은 다음과 같아요.
그렇다면 Feed-Entity는 정확히 무엇일까요? Feed-Entity는 피드에서 노출할 수 있는, 당근 내에서 발행될 수 있는 가장 작은 단위의 콘텐츠를 의미해요. 피드 시스템 안에서 Single Source of Truth(SSOT), 즉 단 하나의 진실된 정보원으로 정의된 단위라고 할 수 있죠.
Feed-Entity에는 콘텐츠 자체를 나타내는 요소들만 정의된답니다. 예를 들어, 중고거래 게시글, 동네생활* 게시글, 동네생활 댓글, **비즈프로필** **등이 Feed-Entity가 될 수 있어요. 하지만 사진, 텍스트, 관심 수나 채팅 수 같은 속성들은 Feed-Entity의 구성 요소가 될 수 없어요.
💡 *동네생활: 동네에 대한 이야기와 정보를 나눌 수 있는 커뮤니티 공간이에요.
💡 **비즈프로필: 동네 업체에 대한 정보를 담고 있는 프로필이랍니다.

위 그림처럼 중고거래, 알바, 부동산 같은 다양한 콘텐츠들이 Feed-Entity라는 표준화된 형태로 변환되어 저장돼요. Feed-Entity는 기본적으로 아래와 같은 구성 요소를 가지고 있답니다.
Feed-Entity는 이렇게 표준화된 구조를 통해 다양한 서비스의 콘텐츠를 일관되게 관리할 수 있도록 해주었어요. 덕분에 새로운 서비스를 피드에 통합하는 과정이 훨씬 간단해졌고, 개발자들은 데이터 구조보다는 실제 비즈니스 로직에 더 집중할 수 있게 되었죠. 👍
💡 *SourceContent: Feed-Entity로 변환되기 전의 원본 콘텐츠 데이터예요.
Feed-Entity의 데이터 파이프라인은 콘텐츠 제공자(Content Provider)로부터 데이터를 모아서, Feed-Entity 형태로 변환한 다음 저장하는 모든 과정을 담당해요. 이 파이프라인은 크게 데이터 수집, 변환, 저장의 단계로 이루어져 있어요. 각 단계는 모듈화되어 있어서 새로운 콘텐츠 타입이 추가되더라도 전체 시스템을 고치지 않고 해당 모듈만 추가하면 되는 유연한 구조를 가지고 있답니다.

위 이미지는 Feed-Entity 데이터 파이프라인을 자세히 보여주고 있어요. 이 파이프라인은 크게 네 단계로 구성되어 있는데, 각 단계별로 자세히 살펴볼게요.

첫 번째 단계는 데이터를 모으는 과정이에요. 중고거래, 알바, 동네생활, 부동산 등 다양한 서비스에서 만들어진 원본 데이터가 Feed-Entity 시스템으로 들어와요. 각 서비스마다 데이터 구조와 형식이 다르기 때문에, 이 단계에서는 다양한 형태의 데이터를 처리할 수 있는 유연한 메커니즘이 필요하죠.
Feed-Entity 시스템은 이런 유연성을 위해 데이터 유형별로 별도의 큐를 운영해요. 각 큐에서 데이터를 처리할 때는 멱등성(Idempotency)을 보장하도록 설계했어요. 덕분에 같은 SourceContent가 여러 번 전달되더라도 단 한 번만 처리된답니다. 또한 실시간 데이터 스트림을 통해 새로운 콘텐츠나 업데이트된 콘텐츠를 계속해서 모아서 사용자에게 항상 최신 정보를 제공할 수 있어요.

두 번째 단계는 수집된 다양한 형태의 데이터를 표준화된 Feed-Entity 형식으로 변환하는 과정이에요. 이 과정에서 각 콘텐츠 타입에 맞는 변환 로직이 적용된답니다. 예를 들어, 중고거래 게시글은 상품 정보, 가격, 위치 같은 필드를 가지지만, 동네생활 게시글은 본문 내용, 카테고리, 댓글 수 등 다른 구조를 가져요.
변환 단계에서는 이렇게 다양한 구조의 데이터들을 공통 필드(ID, 타입, 생성 시간 등)를 가진 Feed-Entity라는 통일된 형식으로 래핑해요. 그러면서도 각 콘텐츠의 고유한 특성은 Entity 필드 내부에 그대로 보존하여 변환하죠.
변환된 Feed-Entity 데이터는 별도의 저장소에 저장되어 피드 시스템 전체에서 일관되게 접근할 수 있게 돼요. 모듈화된 시스템 구조 덕분에 새로운 콘텐츠 타입이 추가되더라도 전체 시스템을 수정하지 않고 해당 모듈만 추가하면 되는 유연한 구조를 갖추고 있답니다. 이는 시스템의 확장성을 크게 향상시키고, 새로운 서비스를 빠르게 통합할 수 있게 해주는 아주 중요한 장점이에요.

세 번째 단계는 데이터를 검증하는 과정이에요. SourceContent나 Feed-Entity 데이터가 검증에 실패하면, 해당 데이터는 Dead Letter Queue(DLQ)로 이동하게 돼요. DLQ는 처리에 실패한 메시지를 저장해 두는 특별한 큐인데, 이를 통해 데이터 손실 없이 나중에 다시 처리할 수 있도록 해준답니다.
예를 들어, 원본 데이터의 형식이 변경되었거나 필수 필드가 누락되었을 때 데이터 변환 과정에서 오류가 발생할 수 있겠죠? 이런 경우 해당 데이터는 DLQ로 이동하고, 개발자들은 오류의 원인을 분석한 후 필요한 수정을 거쳐 데이터를 다시 처리할 수 있어요. 이러한 메커니즘을 통해 시스템의 안정성을 높이고, 데이터 무결성을 보장했답니다.
또한 DLQ 시스템은 문제가 발생한 데이터를 자동으로 모니터링하고 알림을 보내는 기능도 포함하고 있어요. 이를 통해 개발팀이 문제를 빠르게 인지하고 대응할 수 있게 되었죠. 이는 데이터 파이프라인의 신뢰성을 높이고, 장애 상황에서도 데이터 손실을 최소화할 수 있게 해주었어요.

마지막 단계는 변환된 Feed-Entity 데이터를 별도의 메시지 큐에 프로듀스(Produce)하는 과정이에요. 이를 통해 피드 시스템 외의 다른 서비스들도 이 표준화된 데이터를 활용할 수 있게 되었답니다. 다양한 내부 서비스들이 Feed-Entity를 구독(Subscribe)하여 필요한 정보를 실시간으로 받아볼 수 있게 되었어요. 이런 구조는 서비스 간 데이터 일관성을 유지하고, 각 서비스가 독립적으로 발전할 수 있는 기반이 되었죠.
또한 Feed-Entity 시스템을 처음 도입할 때는 콜드 스타트(Cold Start) 문제를 해결하기 위한 백필(Backfill) 기능도 중요했어요. 백필은 기존에 존재하던 콘텐츠들을 새로운 Feed-Entity 형식으로 변환하여 시스템에 채워 넣는 과정이에요. 이 과정을 통해 새 시스템을 도입하는 순간부터 충분한 양의 데이터를 확보하고, 사용자들에게 풍부한 콘텐츠를 제공할 수 있었답니다.
이러한 메시지 큐 기반의 아키텍처는 시스템 간의 느슨한 결합(Loose Coupling)을 가능하게 하여, 각 서비스가 독립적으로 개발, 배포, 확장될 수 있게 해요. 또한 비동기 처리 방식을 통해 시스템의 부하를 분산시키고, 전체 시스템의 안정성과 확장성을 높이는 데 크게 기여하고 있어요.
Feed-Entity를 잘 저장하는 것만큼이나 효율적으로 서빙하는 것도 정말 중요해요. 특히 피드는 데이터를 쓰는 작업보다 읽는 트래픽이 훨씬 많다는 특성이 있기 때문에, 읽기에 최적화된 구조로 데이터를 저장하고 적절한 캐싱 전략을 설계하는 것이 필수적이에요.
피드인프라팀에서는 Feed-Entity 데이터를 효율적으로 서빙하기 위해 지역별 특성을 고려한 Redis 캐싱 전략을 구현했어요. Feed-Entity는 지역성이 강한 특징이 있기 때문에, 지역별로 다른 캐시를 구성해서 데이터 접근 속도를 크게 향상시켰답니다. 사용자가 특정 지역의 피드를 요청하면, 해당 지역에 최적화된 캐시에서 데이터를 빠르게 가져와요. 🏎️💨

Feed-Entity 서빙 시스템은 효율적인 데이터 접근을 위해 다단계 저장 및 조회 구조를 갖추고 있어요. 이 시스템은 크게 Redis 캐싱 레이어와 데이터베이스 저장소의 두 계층으로 구성되어 있답니다.
Feed-Entity가 생성되거나 업데이트되면 다음과 같은 과정을 거쳐요.
사용자가 특정 지역의 피드를 요청하면, 시스템은 다음과 같은 단계로 데이터를 효율적으로 조회해요.
이러한 다층 구조는 여러 가지 성능 이점을 가져왔어요.
이렇게 설계된 Feed-Entity 서빙 시스템은 수백만 사용자의 지역 기반 피드 요청을 매우 낮은 지연 시간(p99 기준 평균 20ms 이하)으로 처리해요. 특히 사용자가 많은 대도시 지역에서도 안정적인 성능을 유지하며 사용자 경험을 크게 개선했답니다. 😊
Feed-Entity를 도입하면서 저희 팀은 정말 많은 중요한 개선 효과를 얻을 수 있었어요. 앞서 Feed-Entity를 통해 이루고 싶었던 다섯 가지 목표를 이야기했었죠? 각 측면에서 경험한 구체적인 장점들을 다시 한번 정리해 드릴게요.
이러한 여러 장점들이 모여 피드 시스템은 Feed-Entity를 통해 더 안정적이고, 확장 가능한 시스템을 구축할 수 있었답니다.
Feed-Entity는 현재 당근 피드 시스템에서 핵심적인 역할을 하고 있지만, 아직 해결해야 할 여러 과제가 남아있어요. 사용자 수가 꾸준히 늘어나고 콘텐츠도 다양해지면서, 데이터 처리 효율성과 확장성에 대한 새로운 요구사항이 계속해서 등장하고 있거든요. 또한 사용자별 맞춤형 경험을 더욱 세밀하게 제공하기 위해서는 Feed-Entity 시스템의 고도화가 필요한 상황이에요. 이러한 도전 과제들을 해결하기 위해 저희 팀은 다음과 같은 방향으로 개선을 계획하고 있답니다.
이러한 과제들을 해결하여 Feed-Entity를 추천 전반에 활용할 수 있게 된다면, 당근의 피드 시스템은 한 단계 더 발전된 형태로 사용자들에게 가치 있는 경험을 제공할 수 있을 것이라고 믿어요.
이렇게 피드라는 복잡한 문제를 해결하기 위해 Feed-Entity라는 개념을 도입하고 발전시켜 온 피드인프라팀! 저희는 항상 더 나은 기술과 사용자 경험을 위해 끊임없이 도전하고 있답니다.
혹시 이런 기술적 도전에 함께하고 싶으신가요? 당근 피드인프라팀에서는 수백만 명의 사용자들에게 하이퍼로컬 가치 있는 정보를 효율적으로 전달하기 위한 기술을 개발하고 다양한 실험에 도전할 열정적인 동료를 찾고 있어요.
당근 피드인프라팀과 함께 하이퍼로컬 피드 경험을 만들어 갈 동료를 기다립니다!
당근마켓 채용 페이지: https://team.daangn.com/jobs/