• Evolution of SoundCloud’s Architecture

  • SoundCloud 아키텍처의 진화

  • This is a story of how we adapted our architecture over time to accomodate growth.
  • SoundCloud의 성장에 대응하기 위한 우리의 지속적인 설계 변경에 대해 얘기하고자 한다.
  • Scaling is a luxury problem and surprisingly has more to do with organization than implementation. For each change we addressed the next order of magnitude of users we needed to support, starting in the thousands and now we’re designing for the hundreds of millions.  We identify our bottlenecks and addressed them as simply as possible by introducing clear integration points in our infrastructure to divide and conquer each problem individually.
  • 스케일링, 확장성을 갖추는 것은 정말 어려운 문제이며, 직접적인 구현 외에도 할 일이 놀라울 정도로 많다. 설계를 변경할 때 마다 다음에 지원해야 하는 사용자의 수를 예측해야했고, 수 천명으로 시작하여, 현재는 수 백만을 대상으로 하고 있다. 병목지점을 파악하고, 각각의 문제를 분할 정복하기 위해 인프라 스트럭쳐의 통합 포인트를 명백하게 함으로써, 한계점을 가늠하는 일이 단순해짐을 알 수 있다.
  • By identifying and extracting points of scale into smaller problems and having well defined integration points when the time arrived, we are able to grow organically.
  • 스케일 포인트를 파악하여 더 작은 문제로 추출해내고, 적절한 시점에서 통합 포인트를 정의함으로써 유기적으로 성장할 수 있었다.
  • Product conception

  • 제품 컨셉

  • From day one, we had the simple need of getting each idea out of our heads and in front of eyeballs as quickly as possible. During this phase, we used a very simple setup:
  • 처음엔 가능한 빨리 머릿속의 아이디어들을 구현해야 겠다는 단순한 요구사항만 있었던 까닭에, 굉장히 단순한 구조를 사용했다.
Image of 1652 article
  • Apache was serving our image/style/behavior resources, and Rails backed by MySQL provided an environment where almost all of our product could be modeled, routed and rendered quickly. Most of our team understood this model and could work well together, delivering a product that is very similar to what we have today.
  • 아파치 웹 서버(이하 아파치) 가 이미지와 스타일, 행동(behavior) 등의 리소스를 전달하고, MySQL 기반의 Rails를 통해 우리 제품 대부분을 모델링하고, 라우팅하고 빠르게 렌더링했다. (역주: Rails 프레임워크를 통해 웹 서비스를 수행했음을 의미) 팀원 대부분이 이 모델을 이해하고 있어, 괜찮은 협업이 가능했고, 오늘날과 매우 유사한 형태의 서비스를 했었다.
  • We consciously chose not to implement high availability at this point, knowing what it would take when that time hopefully arrived. At this point we left our private beta, revealing SoundCloud to the public.
  • 고가용성은 적절한 시점에 구현하면 됨을 알고 있었기에, 굳이 미리 작업하지 않았다. 이 즈음에 제한적인 베타 서비스를 종료하고 SoundCloud를 대중에게 공개하였다.
  • Our primary cost optimization was for opportunity, and anything that got in the way of us developing the concepts behind SoundCloud were avoided. For example, when a new comment was posted, we blocked until all followers were notified knowing that we could make that asynchronous later.
  • 우리의 최우선 비용 최적화는 '기회'에 대한 것이어서, SoundCloud의 컨셉트를 구현하는것에 방해하는 것들은 무시했다. 예를 들어, 비동기 처리는 나중에 할 수 있으니까, 새로운 댓글이 등록되면 모든 팔로워들에게 알림을 보낼 때까지 대기하는 방식(block)으로 처리했다.
  • note icon
    "Our primary cost optimization was for opportunity" 이 애매합니다.
  • In the early stages we were conscious to ensure we were not only building a product, but also a platform. Our Public API was developed alongside our website from the very beginning. We’re now driving the website with the same API we were offering to 3rd party integrations.
  • 우리는 초기 단계부터 우리가 만들고 있는 것이 단순한 '제품'이 아니라 '플랫폼'이라는 것을 확실히 의식했다. 그래서 공개 API를 웹사이트의 처음부터 같이 개발해왔고, 이제는 우리의 웹사이트도 서드파티에 제공되는 것과 동일한 API를 통해 돌아가고 있다.
  • Growing out of Apache

  • 아파치를 넘어서

  • Apache served us well, but we were running Rails app servers on multiple hosts, and the routing and virtual host configuration in Apache was cumbersome to keep in sync between development and production.
  • 아파치 웹 서버는 괜찮았다. 헌데, Rails 애플리케이션을 여러 대의 호스트에서 동작시키게 되면서, 아파치의 라우팅과 가상 호스트 설정이 개발 환경과 실제 환경 사이를 맞추기 어렵게 만드는 골칫거리가 되버렸다.
  • The Web tier’s primary responsibility is to manage and dispatch incoming web requests, as well as buffering outbound responses so to free up an application server for the next request as quickly as possible. This meant the better connection pooling and content based routing configuration we had, the stronger this tier would be.
  • 웹 서버의 일차적 역할은 요청을 받아 관리하고 할당하는 일과 함께 응답을 최대한 빠르게 전송해, 애플리케이션 서버가 다음 요청을 받아들일 수 있도록 하는 것이다. 향상된 커넥션 풀링과 콘텐츠 기반 라우팅 설정이 가능하다면, 더 강력한 웹 서버가 될 수 있다는 것이다.
  • At this point we replaced Apache with Nginx and reduced our web tier’s configuration complexity, but our architecture didn’t change.
  • 그래서 아파치 웹 서버를 Nginx로 변경했고, 아키텍처의 변경 없이 웹 서버 설정의 복잡도를 줄일 수 있었다.
Image of 1652 article
  • Load distribution and a little queue theory

  • 부하 분산과 자그마한 큐 이론

  • Nginx worked great, but as we were growing, we found that some workloads took significantly more time compared to others (in the order of hundreds of milliseconds).
  • Nginx는 잘 동작해주었지만, 서비스가 계속 성장함에 따라, 다른 작업들에 비해 수백 밀리초 정도로 오래 걸리는 몇몇 작업이 있다는 것을 찾아냈다.
  • When you’re working on a slow request when a fast request arrives, the fast request will have to wait until the slow request finishes, called “head of the line blocking problem”. When we had multiple applications servers each with its own listen socket backlog, analogous to a grocery store, where you inevitably stand at one register and watch all the other registers move faster than your own.
  • 이렇게 느린 요청에 대한 처리를 하고 있으면, 빠르게 처리될 수 있는 요청도 느린 요청이 끝날 때까지 기다려야 하는 문제가 있는데, 이를 "head of the line blocking problem" 이라고 한다. 각자 연결해야하는 소켓 뭉치가 있는 애플리케이션 서버가 여러 개 있고, 이를 식료품 가게에 빗대어 보면, 다른 줄은 빨리 지나가는데, 자신이 서있는 줄만 멈춰 있는 것과 같다.
  • Around 2008 when we first developed the architecture, concurrent request processing in Rails and ActiveRecord was fairly immature. Even though we felt confident that we could audit and prepare our code for concurrent request processing, we did not want to invest the time to audit our dependencies. So we stuck with the model of a single concurrency per application server process and ran multiple processes per host.
  • 첫번째 아키텍처를 구현한 2008년에는 Rails와 ActiveRecord에 구현된 동시 요청 처리 수준이 미숙한 편이었다. 상황이 이러함에도, 동시 요청 처리에 대해 우리는 잘 할 수 있다는 확신이 들어, Rails와 ActiveRecord에 대한 검증(audit)을 따로 하진 않았다. 결국 애플리케이션 서버 프로세스당 단일 처리 모델로는 한계에 느끼고, 호스트마다 여러 프로세스를 운용하도록 하였다.
  • In Kendall’s notation once we’ve sent a request from the web server to the application server, the request processing can be modeled by a M/M/1 queue. The response time of such a queue depends on all prior requests, so if we drastically increase the average work time of one request the average response time also drastically increases.
  • Kendall의 표기법에 따르면, 일단 웹 서버에서 애플리케이션 서버로의 요청을 보내는 처리 과정은 M/M/1 큐로 모델링될 수 있다. 이러한 큐에서의 응답 시간은 요청의 우선 순위와 무관하게 결정되기에, 한 요청의 평균 작업 시간이 크게 늘어나면, 전체 요청의 평균 처리 시간도 마찬가지로 크게 늘어나게 된다.
  • note icon
  • Of course, the right thing to do is to make sure our work times are consistently low for any web request, but we were still in the period of optimizing for opportunity, so we decided to continue with product development and solve this problem with better request dispatching.
  • 모든 웹 요청에 대해 일관된 짧은 작업 시간을 갖도록 하는 것이 좋겠지만, 우리는 여전히 '기회 비용 최적화' 중이었기 때문에, 일단 개발을 계속하고 이 문제를 요청 분배를 개선하는 것으로 해결하고자 했다.
  • We looked at the Phusion passenger approach of using multiple child processes per host but felt that we could easily fill each child with long-running requests. This is like having many queues with a few workers on each queue, simulating concurrent request processing on a single listen socket.
  • 호스트 당 여러 자식 프로세스를 사용하는 Phusion passenger(역주:Rails 애플리케이션의 실행을 담당)의 접근 방법을 면밀히 살펴보았고, 오래 걸리는 요청들로 자식 프로세스가 금새 가득 차는 것을 알 수 있었다. 이는 각자의 워커를 가진 많은 큐가 있음에도, 하나의 리슨 소켓(역주: 클라이언트로부터의 접속을 수행하는 소켓)으로 동시 요청 처리를 시뮬레이션 하는 것과 같다.
  • This changed the queue model from M/M/1 to M/M/c where c is the number of child processes for every dispatched request. This is like the queue system found in a post office, or a “take a number, the next available worker will help you” kind of queue. This model reduces the response time by a factor of c for any job that was waiting in the queue which is better, but assuming we had 5 children, we would just be able to accept an average of 5 times as many slow requests. We were already seeing a factor of 10 growth in the upcoming months, and had limited capacity per host, so adding only 5 to 10 workers was not enough address the head of the line blocking problem.
  • 그래서 M/M/1 에서 M/M/c 형태로 큐 모델을 변경하게 되었다. 여기서 c는 전달된 요청을 위한 자식 프로세스의 수를 의미한다. 이는 "번호표를 뽑고 기다리시면, 빈 창구에서 당신을 도와드리겠습니다"의 우체국 큐 시스템과 유사하다. 이 모델은 큐에서 대기하고 있는 모든 작업에 대해 c의 값에 따라 응답 시간을 줄일 수 있다. 하지만 5개의 자식 프로세스가 있다고 가정할 때, 그저 느린 요청을 5배 정도 받아들이는 수준에 그칠 것이다. 앞으로의 성장에 대비해 10 개의 프로세스를 추가했지만, 각 호스트마다 한계 용량이 있기 때문에, 5개에서 10개 정도의 프로세스 추가만으론 'head of the line blocking' 문제에 충분히 대응할 수 없다.
  • We wanted a system that never queued, but if it did queue, the wait time in the queue was minimal. Taking the M/M/c model to the extreme, we asked ourselves “how can we make c as large as possible?”
  • 절대로 큐가 발생하지 않는 시스템을 원했지만, 어쨌든 큐에 요청이들어가면 대기 시간을 최소화 하고 싶었다. M/M/c 모델을 극한으로 몰아붙이다보니, "도대체 c 가 얼마나 커야 충분한가" 라는 의문이 생겼다.
  • To do this, we needed to make sure that a single Rails application server never received more than one request at a time. This ruled out TCP load balancing because TCP has no notion of an HTTP request/response. We also needed to make sure that if all application servers were busy, the request would be queued for the next available application server. This meant we must maintain complete statelessness between our servers. We had the latter, but didn’t have former.
  • 이를 위해 하나의 Rails 애플리케이션 서버가 반드시 한 번에 하나의 요청만 받도록 해야 했다. 이는 HTTP 요청/응답에 대한 개념이 없는 TCP의 부하 분산 규칙에 어긋난다. 또한, 모든 애플리케이션 서버가 바쁠 경우에, 여유가 생길때까지 요청이 대기 큐에 들어갈 수 있도록 해야 했다. 이는 서버 사이에 완전한 무상태(statelessness)를 유지해야 하는 것을 의미한다. 후자는 가지고 있었지만, 전자는 없었다.
  • note icon
    전자는 '한 번에 하나의 요청만 받도록 하는 것', 후자는 '대기 큐에 들어가도록 하는 것'을 뜻함.
  • We added HAProxy into our infrastructure, configuring each backend with a maximum connection count of 1 and added our backend processes across all hosts, to get that wonderful M/M/c reduction in resident wait time by queuing the HTTP request until any backend process on any host becomes available. HAProxy entered as our queuing load balancer that would buffer any temporary back-pressure by queuing requests from the application or dependent backend services so we could defer designing sophisticated queuing in other components in our request pipeline.
  • HAProxy를 시스템에 추가하고, 각 백엔드 연결의 최대값을 1로 설정한 후, 백엔드 프로세스를 모든 호스트에 추가하여, 모든 호스트의 모든 프로세스가 바쁜 경우에 HTTP 요청이 큐에 들어감으로 발생하는 상시 대기 시간을 줄이는 멋진 M/M/c 효과를 얻었다. HAProxy를 부하 분산을 위해 사용하게 되면서, 애플리케이션(역주:soundcloud)이나 의존적인 백엔드 서비스들로부터의 요청에 대한 임시 배압(back-pressure) 버퍼로도 활용하여, 요청 파이프라인에 있는 다른 콤포넌트들을 위한 정교한 큐 설계 작업을 미룰 수 있었다.
  • note icon
Image of 1652 article
  • Going asynchronous

  • 비동기 처리로 가자!

  • One class of request that took a long time was the fan-out of notifications from social activity. For example, when you upload a sound to SoundCloud, everyone that follows you will be notified. For people with many followers, if we were to do this synchronously, the request times would exceed the tens of seconds. We needed to queue a job that would be handled later.
  • 소셜 네트워크 활동의 팬-아웃(fan-out) 알림은 처리에 긴 시간이 걸리는 작업이다. SoundCloud에 음악을 올린 경우를 예로 들면, 당신을 팔로우(follow)하고 있는 모두에게 알려야 한다. 많은 팔로워가 있는데 이 작업을 동기적(synchronously)으로 처리해야 한다면 아마 수십초 이상이 걸리는 작업이 될 것이다. 우린 이런 작업을 나중에 처리할 수 있는 큐가 필요했다.
  • Around the same time we were considering how to manage our storage growth for sounds and images, and had chosen to offload storage to Amazon S3 keeping transcoding compute in Amazon EC2.
  • 같은 시기에 늘어만 가는 음악과 이미지를 어떻게 관리할지 고민하고 있었는데, Amazon S3를 저장소로, Amazon EC2를 변환 연산 처리를 위해 선택하였다.
  • Coordinating these subsystems, we needed some middleware that would reliably queue, acknowledge and re-deliver job tickets on failure. We went through a few systems, but in the end settled on AMQP because of having a programmable topology, implemented by RabbitMQ.
  • 이런 하위 시스템을 조정하기 위해, 안정적으로 큐잉하고, 실패한 작업을 알아채고 다시 전달할 수 있는 신뢰도 높은 미들웨어가 필요해졌다. 몇몇 시스템을 테스트한 후에 최종적으로 AMQP에 정착했는데, RabbitMQ를 통해 구현된 프로그램 가능한 토폴로지를 가지고 있기 때문이다.
  • To keep the same domain logic that we had in the website, we loaded up the Rails environment and built a lightweight dispatcher class with one queue per concern.  The queues had a namespace that describes estimated work times. This created a priority system in our asynchronous workers without requiring adding the complexity of message priorities to the broker by starting one dispatcher process for each class of work that bound to multiple queues in that work class. Most of our queues for asynchronous work performed by the application are namespaced with either “interactive” (under 250ms work time) or “batch” (any work time). Other namespaces were used specific to each application.
  • 웹 사이트와 같은 도메인 로직을 유지하기 위해, Rails 환경을 올리고 중요 지점마다 큐를 가진 경량의 디스패쳐 클래스를 제작하였다. 각각의 큐는 예상되는 작업시간을 의미하는 네임스페이스를 가진다. This created a priority system in our asynchronous workers without requiring adding the complexity of message priorities to the broker by starting one dispatcher process for each class of work that bound to multiple queues in that work class. 애플리케이션에 의해 비동기적으로 동작하는 큐의 이름은 250ms이하의 작업 시간을 가지는 "interactive" 또는 작업 시간에 제한이 없는 "batch" 등으로 지었고, 나머지 네임스페이스는 애플리케이션 별로 정해서 사용하는 식이었다.
Image of 1652 article
  • Caching

  • 캐싱

  • When we approached the hundreds of thousands user mark, we saw we were burning too much CPU in the application tier, mostly spent in the rendering engine and Ruby runtime.
  • 사용자가 수십만명에 수준에 도달하자, 애플리케이션 레벨의 Ruby 실행기와 렌더링 엔진이 CPU를 너무 많이 쓰고 있는 것을 알게 되었다.
  • Instead of introducing Memcached to alleviate IO contention in the database like most applications, we aggressively cached partial DOM fragments and full pages. This turned into an invalidation problem which we solved by maintaining the reverse index of cache keys that also needed invalidation on model changes in memcached.
  • Memcached를 대부분의 애플리케이션에서와 같이 데이터베이스로부터 콘텐츠를 끌어오기 위해 사용하는 I/O를 줄이는데 썼을 뿐 아니라, 전체 페이지 및 DOM 조각을 캐시하는데도 적극적으로 사용했다. 이는 페이지 갱신과 관련된 문제를 일으켰지만, memcached의 모델이 변경되는 경우에 사용하기 위한 캐시 키의 역인덱스를 유지함으로써 해결했다.
  • Our highest volume request was one specific endpoint that was delivering data for the widget. We created a special route for that endpoint in nginx and added proxy caching to that stack, but wanted to generalize caching to the point where any end point could produce proper HTTP/1.1 cache control headers and would be treated well by an intermediary we control. Now our widget content is served entirely from our public API.
  • 요청의 대부분이 위젯을 위한 데이터를 전달하는 것이었기에, 이를 위한 특별한 루트를 nginx에 생성하고 프락시 캐시를 스택에 추가하였다. but wanted to generalize caching to the point where any end point could produce proper HTTP/1.1 cache control headers and would be treated well by an intermediary we control. 현재의 위젯 콘텐츠는 모두 public API를 통해 전달된다.
  • We added Memcached and much later Varnish to our stack to handle backend partially rendered template caching and mostly read-only API responses.
  • Memcached 뿐만 아니라 후에 Varnish를 추가해 백엔드에서 부분적으로 렌더링한 템플릿과 읽기 전용 API의 응답을 캐싱하는데 사용하였다.
Image of 1652 article
  • Generalization

  • 일반화

  • Our worker pools grew, handling more asynchronous tasks. The programming model was similar for all of them: take a domain model and schedule a continuation with that model state to be processed at a later state.
  • 워커 풀(worker pool)이 커지면서 더 많은 비동기 요청을 처리할 수 있게 되었다. 프로그래밍 모델도 이와 유사해졌는데, 도메인 모델을 도입하고 모델의 상태에 따라 다음 상태로 지속적으로 처리 가능하도록 스케쥴링하게 되었다.
  • Generalizing this pattern, we leveraged the after-save hooks in ActiveRecord models in a way we call ModelBroadcast. The principle is that when the business domain changes, events are dropped on the AMQP bus with that change for any asynchronous client that is interested in that class of change. This technique of decoupling the write path from the readers enables the next evolution of growth by accommodating integrations we hadn’t foreseen.
  • 이 패턴을 일반화 하여, ActiveRecord의 after-save를 후킹하여 레버리지 하는 동작을 ModelBroadcast라 부르고 있다. The principle is that when the business domain changes, events are dropped on the AMQP bus with that change for any asynchronous client that is interested in that class of change. This technique of decoupling the write path from the readers enables the next evolution of growth by accommodating integrations we hadn’t foreseen.
  • after_create do |r| broker.publish("models", "create.#{r.class.name}", r.attributes.to_json) end after_save do |r| broker.publish("models", "save.#{r.class.name}", r.changes.to_json) end after_destroy do |r| broker.publish("models", "destroy.#{r.class.name}", r.attributes.to_json) end
  • - 코드 부분이므로 원본 참고 -
  • This isn’t perfect, but it added a much needed non-disruptive, generalized, out-of-app integration point in the course of a day.
  • 완벽하진 않지만, 하루만에 장애 없이, 일반화된 앱 외적(out-of-app)인 통합 포인트를 더할 수 있었다.
  • Dashboard

  • 대시보드

  • Our most rapid data growth was the result of our Dashboard. The Dashboard is a personalized materialized index of activities inside of your social graph and the primary place to personalize your incoming sounds from the people you follow.
  • Dashboard 때문에 데이터가 급격히 늘어나게 되었다. DashBoard는 소셜 그래프에 포함된 활동을 보여주는 목차이며, 팔로우하고 있는 사람들의 소리(메세지)를 개인화하는 주된 장소이다.
  • We have always had a storage and access problem with this component. Looking at the read and write paths separately, the read path needs to be optimized for sequential access per user over a time range. The write path needs to be optimized for random access where one event may affect millions of users’ indexes.
  • 이 부분은 늘 저장과 접근에 대한 문제가 있었는데, 읽기와 쓰기 작업을 따로 놓고 본다면, 읽기 작업은 사용자당 시간의 흐름에 따라 순차적으로 읽는 행위 위주로 최적화되어야 한다. 반면에 쓰기 작업은 하나의 이벤트가 수백만 사용자의 인덱스에 영향을 미치기에 무작위 접근에 최적화되어야 하는 경향이 있다.
  • The solution required a system that could reorder writes from random to sequential and store in sequential format for read that could be grown to multiple hosts. Sorted string tables are a perfect fit for the persistence format, and add the promise of free partitioning and scaling in the mix, we chose Cassandra as the storage system for the Dashboard index.
  • 이 솔루션은 여러 호스트에서 순차적인 형태로 읽어갈 수 있도록 무작위 쓰기 작업을 순차적으로 정렬한 후, 저장하는 시스템을 필요로 한다. 지속 가능한 형태로 정렬된 문자열 테이블이 적합하며, 자유롭게 파티셔닝되고 스케일링할 수 있어야 한다. 그래서 Cassandra를 Dashboard 인덱스의 저장소로 선택했다.
  • The intermediary steps started with the model broadcast and used RabbitMQ as a queue for staged processing, in three major steps: fan-out, personalization, and serialization of foreign key references to our domain models.
  • 중간 과정은 팬아웃(fan-out), 개인화(personalization), 그리고 도메인 모델에 대한 외부키 참조 직렬화(serialization) 의 3단계로 구성되어 있으며, 각각의 단계는 model broadcast로 시작하여 RabbitMQ를 큐로 사용해 처리한다.
  • - Fan-out finds the areas of the social graph where an activity should propagate.
  • - 팬아웃 과정에서 이 액티비티가 전달되어야 하는 소셜 그래프의 범위를 찾아낸다.
  • - Personalization looks at the relationship between the originator and destination users as well as other signals to annotate or filter the index entry.
  • - 개인화는 창작자와 대상자(팔로워) 사이의 관계 뿐 아니라 색인 항목에 주석을 달거나 필터링하는 신호도 살펴본다.
  • - Serialization persists the index entry in Cassandra for later lookup and joining against our domain models for display or API representations.
  • - 직렬화(serialization)는 색인 항목을 카산드라에 보관하여 이를 나중에 찾아보거나, 도메인 모델에 결합(join)해서 화면에 보여주거나 API 로 표현할 때 사용한다.
  • Search

  • 검색

  • Our search is conceptually a back-end service that exposes a subset of data store operations over an HTTP interface for queries. Updating of the index is handled similarly to the dashboard via ModelBroadcast with some enhancement from database replicas with index storage managed by Elastic Search.
  • 우리의 검색 서비스는 데이터 저장소의 기능 중 일부를 쿼리를 위한 HTTP 인터페이스를 통해 노출하는 개념이다. 인덱스를 갱신하는 일은 대쉬보드와 유사하게 이뤄지는데, Elastic Search 로 관리되는 인덱스 저장소를 가진 데이터베이스 복제본에서 갱신사항을 얻어 ModelBroadcast 한다.
Image of 1652 article
  • Notifications and Stats

  • 알림과 상태

  • To make sure users are properly notified when their dashboard updates, whether this is over iOS/Android push notifications, email or other social networks we simply added another stage in the Dashboard workflow that receives messages when a dashboard index is updated. Agents can get that completion event routed to their own AMQP queues via the message bus to initiate their own logic. Reliable messages at the completion of persistence is part of the eventual consistency we work with throughout our system.
  • Our statistics offered to logged in users at http://soundcloud.com/you/stats also integrates via the broker, but instead of using ModelBroadcast, we emit special domain events that are queued up in a log then rolled up into a separate database cluster for fast access across the various time ranges.
Image of 1652 article
  • What’s next

  • 다음엔 뭐가 있지?

  • We have established some clear integration points in the broker for asynchronous write paths and in the application for synchronous read and write paths to backend services.
  • 브로커의 비동기적 쓰기 작업과 애플리케이션(역주:유저 대상 웹 서비스를 의미)내에서의 백엔드 서비스로의 동기적인 읽기/쓰기 작업 등에서의 명백한 통합 지점을 설정해왔다.
  • Over time, the application server’s codebase has collected both integration and functional responsibilities. As the product development settles, we have much more confidence now to decouple the function from the integration to be moved into backend services that can be consumed à la carte by not only the application but by other backend services, each with a private namespace in the persistence layer.
Image of 1652 article
  • The way we develop SoundCloud is to identify the points of scale then isolate and optimize the read and write paths individually, in anticipation of the next magnitude of growth.
  • 차기 성장을 고려하여 스케일 가능한 포인트를 파악한 후 분리하고, 읽기와 쓰기 방법을 독립적으로 최적화하는 방식으로 SoundCloud를 개발해왔다.
  • At the beginning of the product, our read and write scaling limitations were consumer eyeballs and developer hours. Today, we’re engineering for the realities of limited IO, network and CPU. We have the integration points set up in our architecture, all ready for the continued evolution of SoundCloud!
  • 서비스의 초기에는 읽기/쓰기 작업 스케일링의 한계가 고객의 관심과 개발자의 시간에 달려있었지만, 이제는 제한된 I/O, 네트워크, CPU 등의 현실적인 문제에 대한 엔지니어링을 하고 있다. 아키텍처의 통합 지점을 알고 있는 이상, SoundCloud의 지속적인 발전은 계속될 것이다!
2 Comments
isyoon

isyoon • Oct 14th, 2012

멋쪄용 ㅎㅎ
  • lqez • Oct 16th, 2012

    아직 제대로 다 번역하지도 못했는데... 감사합니다.