Computer >> 컴퓨터 >  >> 프로그램 작성 >> Ruby

사이드키크 러브스토리, 카프카와 루비

계속 성장하는 올인원 APM으로서 우리는 AppSignal이 트래픽 증가에 대처할 수 있도록 하는 데 많은 시간을 할애합니다. 일반적으로 우리는 어떻게 하는지에 대해 이야기하지 않습니다. 우리 블로그는 Ruby를 사용하여 멋진 일을 하거나 Elixir로 미친 짓을 하는 것에 대한 기사로 가득 차 있지만 AppSignal이 요동치는 이유에 대한 기사는 없습니다.

그러나 이번에는 지난 몇 년 동안 스택의 더 큰 변경 사항 중 일부를 공유하여 매월 전송되는 두 자릿수 십억 개의 요청을 (쉽게) 처리할 수 있습니다. 실시간. 그래서 오늘은 확장 경험을 활용하여 자체 스택에 대해 논의하고 도움을 드립니다.

표준 레일 설정에서 더 많은 맞춤형 부품으로

AppSignal은 꽤 표준적인 Rails 설정으로 시작되었습니다. 백그라운드에서 처리할 Sidekiq 작업을 생성하는 API 엔드포인트를 통해 데이터를 수집하는 Rails 앱을 사용했습니다.

잠시 후 우리는 약간의 속도를 얻기 위해 Rails API를 Rack 미들웨어로 교체했고 나중에 이것은 Sidekiq 호환 작업을 Redis로 푸시하는 Go 웹 서버로 교체되었습니다.

앱 상태 및 증분/업데이트

이 설정은 오랫동안 잘 작동했지만 데이터베이스가 데이터베이스에 대해 실행되는 쿼리의 양을 따라잡을 수 없는 문제에 부딪히기 시작했습니다. 이 시점에서 우리는 이미 수백억 개의 요청을 처리하고 있었습니다. 그 주된 이유는 올바른 카운터를 증가시키고 올바른 문서를 업데이트하기 위해 각 Sidekiq 프로세스가 데이터베이스에서 전체 앱의 상태를 가져와야 했기 때문입니다.

데이터의 로컬 캐싱으로 이 문제를 어느 정도 완화할 수 있지만 설정의 라운드 로빈 특성으로 인해 각 서버에 모든 데이터의 전체 캐시가 있어야 함을 의미했습니다. 끝날 것입니다. 우리는 데이터가 증가함에 따라 이러한 설정이 앞으로 불가능해질 것임을 깨달았습니다.

카프카 입력

데이터를 처리하는 더 나은 방법을 찾기 위해 Kafka를 데이터 처리 파이프라인으로 사용하기로 결정했습니다. 데이터베이스에서 메트릭을 집계하는 대신 이제 Kafka 프로세서에서 메트릭을 집계합니다. . 우리의 목표는 집계된 데이터가 플러시될 때까지 Kafka 파이프라인이 데이터베이스를 쿼리하지 않는 것입니다. 이렇게 하면 페이로드당 쿼리 수가 최대 10개의 읽기 및 쓰기에서 파이프라인 끝에서 단 하나의 쓰기로 줄어듭니다.

우리는 각 Kafka 메시지에 대해 키를 지정하고 Kafka는 동일한 키가 동일한 서버에서 사용되는 동일한 파티션에서 종료되도록 보장합니다. 우리는 앱의 ID를 메시지의 키로 사용합니다. 즉, 서버의 모든 고객에 대한 캐시를 갖는 대신 모든 앱이 아니라 서버가 Kafka에서 수신하는 앱에 대한 데이터만 캐시하면 됩니다.

Kafka는 훌륭한 시스템이며 우리는 지난 2년 동안 마이그레이션했습니다. 현재 거의 모든 처리가 Rust에서 Kafka를 통해 이루어지지만 알림 보내기 및 기타 데이터베이스가 많은 작업과 같이 Ruby에서 더 쉽게 수행할 수 있는 작업이 여전히 있습니다. 즉, Kafka에서 Rails 스택으로 데이터를 가져올 방법이 필요했습니다.

Kafka와 Ruby/Rails 연결

이 전환을 시작했을 때 Kafka Ruby gem이 몇 개 있었지만 Kafka의 최신(당시 0.10.x) 릴리스에서는 작동하지 않았으며 대부분은 유지 관리되지 않았습니다.

우리는 우리 자신의 gem을 작성하는 것을 보았습니다(결국 그렇게 했습니다). 우리는 다른 기사에서 그것에 대해 더 쓸 것입니다. 그러나 좋은 운전자를 갖는 것은 요구 사항의 일부일 뿐입니다. 또한 Ruby에서 데이터를 사용하고 작업을 실행하고 이전 작업자가 충돌할 때 새 작업자를 생성하는 시스템이 필요했습니다.

결국 우리는 다른 해결책을 생각해 냈습니다. Kafka 스택은 Rust로 구축되었으며 sidekiq_out을 사용하는 작은 바이너리를 작성했습니다. Redis에서 Sidekiq 호환 작업을 생성합니다. 이렇게 하면 이 바이너리를 작업자 컴퓨터에 배포할 수 있으며 Rails 자체 내에서와 마찬가지로 Sidekiq에 새 작업을 제공할 수 있습니다.

바이너리에는 임계값이 지워질 때까지 Kafka 주제 소비를 중지하기 위해 Redis의 데이터 양을 제한하는 것과 같은 몇 가지 옵션이 있습니다. 이렇게 하면 백로그가 있는 경우 Kafka의 모든 데이터가 작업자의 Redis 메모리에 저장되지 않습니다.

Ruby의 관점에서는 Rails에서 생성된 작업과 Kafka에서 생성된 작업 간에 차이가 전혀 없습니다. 이를 통해 Kafka에 대해 전혀 몰라도 알림을 보내고 데이터베이스를 업데이트하기 위해 Kafka에서 데이터를 가져와 Rails에서 처리하는 새 작업자의 프로토타입을 만들 수 있습니다.

새로운 Ruby 코드를 배포할 필요 없이 Kafka로 전환했다가 다시 돌아올 수 있으므로 Kafka로의 마이그레이션이 더 쉬워졌습니다. 또한 전체 Kafka 스택을 로컬로 설정하지 않고도 Ruby에서 사용할 테스트 제품군에서 작업을 쉽게 생성할 수 있어 테스트가 매우 간편해졌습니다.

우리는 Protobuf를 사용하여 모든 (내부) 메시지를 정의합니다. 이렇게 하면 테스트가 통과하면 작업자가 Kafka의 작업을 올바르게 처리할 것이라고 확신할 수 있습니다.

결국 이 솔루션은 많은 시간과 에너지를 절약하고 Ruby 팀의 삶을 훨씬 더 단순하게 만들었습니다.

장점

모든 것과 마찬가지로 이 설정에는 몇 가지 장단점이 있습니다.

장점:

  • Ruby를 변경할 필요 없음, API 호환
  • 쉽게 배포하고 되돌릴 수 있음
  • Kafka와 Ruby 사이를 쉽게 전환할 수 있습니다.
  • 리미터를 사용할 때 Redis는 메시지에 과부하가 걸리지 않고 서버의 메모리를 절약하고 대신 Kafka에 메시지를 보관합니다.
  • 수평 확장은 키 메시지로 인해 각 서버에서 더 작은 캐시로 이어집니다.

단점:

  • 각 Sidekiq 스레드가 서버가 사용하는 파티션에서 앱에 대한 모든 데이터의 캐시에 액세스해야 한다는 문제가 여전히 있습니다. (예:Memcache).
  • 서버에서 별도의 프로세스 실행
  • rust 프로세서는 메시지가 Redis로 플러시될 때 메시지 오프셋을 커밋합니다. 즉, Redis에 있다는 것은 보장되지만 Ruby에서 메시지를 처리한다는 보장은 없습니다. Redis에 있었지만 처리되지 않은 일부 메시지가 처리되지 않았을 가능성이 있습니다.

Sidekiq 및 Kafka

Sidekiq를 사용하면 처리 파이프라인을 Kafka로 마이그레이션하는 동안 엄청난 도움이 되었습니다. 우리는 이제 Sidekiq에서 거의 완전히 벗어나 Kafka 드라이버를 통해 직접 모든 것을 처리하지만 다른 기사를 위한 것입니다.

이 해피엔딩으로 러브스토리를 마무리합니다. 성능 및 확장성에 대한 이 관점과 AppSignal 확장 경험을 즐겼기를 바랍니다. 스택과 관련하여 내린 결정에 대한 이 이야기가 여러분에게 도움이 되기를 바랍니다.

블로그의 나머지 부분을 확인하거나 팔로우하여 Kafka 설정에 대한 다음 에피소드가 게시될 때를 지켜봐 주십시오. 그리고 진정으로 개발자가 개발자를 위한 올인원 APM을 찾고 계시다면 저희를 찾아주세요.