Braze가 Ruby를 대규모로 활용하는 방법

게시 됨: 2022-08-18

Hacker News, Developer Twitter 또는 기타 유사한 정보 소스를 읽는 엔지니어라면 "Speed ​​of Rust vs C", "What Makes Node.js"와 같은 제목의 수천 개의 기사를 접했을 것입니다. js가 Java보다 빠름?" 또는 "Golang을 사용해야 하는 이유와 시작하는 방법"을 참조하세요. 이러한 기사는 일반적으로 확장성 또는 속도를 위한 명백한 선택인 이 특정 언어가 있으며 이를 수용하기만 하면 된다는 사실을 보여줍니다.

대학에 다니고 엔지니어로 일한 1, 2년 동안 저는 이 기사를 읽고 즉시 새로운 언어나 프레임워크를 배우기 위해 애완 동물 프로젝트를 시작했습니다. 결국, "전 세계적으로" 그리고 "지금까지 본 어떤 것보다 빠르게" 작동하는 것이 보장되었으며, 누가 이를 거부할 수 있겠습니까? 결국 나는 대부분의 프로젝트에서 이러한 매우 특정한 것들이 실제로 필요하지 않다는 것을 깨달았습니다. 그리고 경력이 발전하면서 언어나 프레임워크를 선택해도 실제로 이러한 것들을 무료로 제공할 수 없다는 것을 깨달았습니다.

그 대신, 언어나 프레임워크가 아니라 시스템을 확장할 때 실제로 가장 큰 레버는 아키텍처라는 것을 알게 되었습니다.

여기 Braze에서 우리는 엄청난 글로벌 규모로 운영됩니다. 그리고 네, 우리는 Ruby와 Rails를 두 가지 주요 도구로 사용합니다. 그러나 이 모든 것을 가능하게 하는 "global_scale = true" 구성 값은 없습니다. 이는 응용 프로그램 내에서 배포 토폴로지에 이르기까지 깊이 있는 아키텍처의 결과입니다. Braze의 엔지니어는 확장 병목 현상을 지속적으로 조사하고 시스템을 더 빠르게 만드는 방법을 모색하고 있으며 일반적으로 "Ruby에서 멀어지는 것"이 ​​답이 아닙니다. 거의 확실하게 아키텍처가 변경될 것입니다.

이제 Braze가 사려 깊은 아키텍처를 활용하여 실제로 속도와 대규모 글로벌 규모를 해결하는 방법과 Ruby와 Rails가 적합하고 적합하지 않은 경우를 살펴보겠습니다!

동급 최고의 아키텍처의 힘

간단한 웹 요청

우리가 운영하는 규모 때문에 우리는 고객의 사용자 기반과 관련된 장치가 일부 Braze 웹 서버에서 제공해야 하는 수십억 개의 웹 요청을 매일 생성할 것임을 알고 있습니다. 그리고 가장 단순한 웹사이트에서도 클라이언트에서 서버로 그리고 그 반대로 요청과 관련된 비교적 복잡한 흐름을 갖게 될 것입니다.

  1. 클라이언트의 DNS 확인자(일반적으로 ISP)가 웹사이트 URL의 도메인을 기반으로 이동할 IP 주소를 파악하는 것으로 시작합니다.

  2. 클라이언트가 IP 주소를 갖게 되면 게이트웨이 라우터로 요청을 보내고 요청이 대상 IP 주소로 전달될 때까지 "다음 홉" 라우터(여러 번 발생할 수 있음)로 요청을 보냅니다.

  3. 거기에서 요청을 수신하는 서버의 운영 체제는 네트워킹 세부 정보를 처리하고 웹 서버의 대기 프로세스에 수신 요청이 수신 대기 중인 소켓/포트에서 수신되었음을 알립니다.

  4. 웹 서버는 해당 소켓에 응답(요청된 리소스, 아마도 index.html)을 기록하고 라우터를 통해 클라이언트로 다시 이동합니다.

간단한 웹사이트에는 꽤 복잡한 것들이죠? 운 좋게도 이러한 많은 것들이 우리를 위해 처리됩니다(자세한 내용은 잠시 후). 그러나 우리 시스템에는 여전히 처리해야 할 데이터 저장소, 백그라운드 작업, 동시성 문제 등이 있습니다! 어떤 모습인지 살펴보겠습니다.

규모를 지원하는 최초의 시스템

DNS 및 이름 서버는 일반적으로 대부분의 경우 많은 관심을 필요로 하지 않습니다. 최상위 도메인 이름 서버에는 "yourwebsite.com"을 도메인 이름 서버에 매핑하는 몇 가지 항목이 있을 수 있으며 Amazon Route 53 또는 Azure DNS와 같은 서비스를 사용하는 경우 이름을 처리합니다. 도메인용 서버(예: A, CNAME 또는 기타 유형의 레코드 관리). 일반적으로 이 부분을 확장하는 것에 대해 생각할 필요가 없습니다. 사용 중인 시스템에서 자동으로 처리되기 때문입니다.

그러나 흐름의 라우팅 부분은 흥미로울 수 있습니다. Open Shortest Path First 또는 Routing Information Protocol과 같은 몇 가지 다른 라우팅 알고리즘이 있으며 모두 클라이언트에서 서버로의 가장 빠른/최단 경로를 찾도록 설계되었습니다. 인터넷은 사실상 거대한 연결된 그래프(또는 플로우 네트워크)이기 때문에 활용할 수 있는 여러 경로가 있을 수 있으며, 각각에 해당하는 더 높거나 낮은 비용이 있습니다. 절대적인 가장 빠른 경로를 찾는 작업을 수행하는 것은 금지되어 있으므로 대부분의 알고리즘은 허용 가능한 경로를 얻기 위해 합리적인 휴리스틱을 사용합니다. 컴퓨터와 네트워크가 항상 신뢰할 수 있는 것은 아니므로 Fastly를 사용하여 클라이언트가 서버로 더 빠르게 라우팅할 수 있는 기능을 향상시킵니다.

전 세계의 POP(Point-of-Presence) 간에 매우 빠르고 안정적인 연결을 제공하여 빠르게 작동합니다. 인터넷의 주간 고속도로로 생각하십시오. 우리 도메인의 A 및 CNAME 레코드는 Fastly를 가리키므로 고객의 요청이 고속도로로 직접 이동합니다. 거기에서 Fastly는 올바른 위치로 라우팅할 수 있습니다.

브레이징 정문

자, 우리 고객의 요청이 Fastly 고속도로를 따라 Braze 플랫폼의 정문 바로 앞에 있습니다. 다음은 어떻게 될까요?

간단한 경우에 해당 정문은 요청을 수락하는 단일 서버입니다. 상상할 수 있듯이 확장성이 좋지 않으므로 실제로 Fastly는 로드 밸런서 세트를 가리킵니다. 로드 밸런서가 사용할 수 있는 모든 종류의 전략이 있지만 이 시나리오에서 Fastly가 로드 밸런서 풀에 요청을 균등하게 라운드 로빈한다고 상상해 보십시오. 이러한 로드 밸런서는 요청을 대기열에 넣은 다음 해당 요청을 웹 서버에 배포합니다. 웹 서버는 라운드 로빈 방식으로 클라이언트 요청을 처리한다고 상상할 수도 있습니다. (실제로 특정 종류의 친화력에 이점이 있을 수 있지만 이는 다른 시간에 다룰 주제입니다.)

이를 통해 수신하는 요청 처리량과 처리할 수 있는 요청 처리량에 따라 로드 밸런서 수와 웹 서버 수를 확장할 수 있습니다. 지금까지 우리는 땀을 흘리지 않고 엄청난 양의 요청을 처리할 수 있는 아키텍처를 구축했습니다! 로드 밸런서의 요청 대기열의 탄력성을 통해 버스트 트래픽 패턴을 처리할 수도 있습니다.

웹 서버

마지막으로 흥미로운 (Ruby) 부분인 웹 서버에 도달합니다. 우리는 Ruby on Rails를 사용하지만 이는 웹 프레임워크일 뿐입니다. 실제 웹 서버는 Unicorn입니다. Unicorn은 각 작업자 프로세스가 작업을 위해 OS 소켓에서 수신 대기하는 시스템에서 여러 작업자 프로세스를 시작하여 작동합니다. 그것은 우리를 위해 프로세스 관리를 처리하고 OS 자체에 대한 요청의 로드 밸런싱을 연기합니다. 요청을 최대한 빨리 처리하려면 Ruby 코드만 있으면 됩니다. 다른 모든 것은 Ruby 외부에서 효과적으로 최적화됩니다.

SDK가 고객 애플리케이션 내부에서 또는 REST API를 통해 만든 요청의 대부분이 비동기식이기 때문에(즉, 클라이언트에게 특정 응답을 반환하기 위해 작업이 완료될 때까지 기다릴 필요가 없음), API 서버는 매우 간단합니다. 요청 구조, API 키 제약 조건을 확인한 다음 Redis 대기열에 요청을 던지고 모든 것이 확인되면 클라이언트에 200 응답을 반환합니다.

이 요청/응답 주기는 Ruby 코드가 처리하는 데 약 10밀리초가 소요되며 그 중 일부는 Memcached 및 Redis를 기다리는 데 사용됩니다. 우리가 이 모든 것을 다른 언어로 다시 작성하더라도, 이것에서 훨씬 더 많은 성능을 짜내는 것은 실제로 불가능합니다. 그리고 궁극적으로, 우리가 이 데이터 수집 프로세스를 확장하여 고객의 계속 증가하는 요구 사항을 충족할 수 있도록 하는 것은 지금까지 읽은 모든 것의 아키텍처입니다.

작업 대기열

이것은 우리가 과거에 탐구한 주제이므로 이 측면에 대해 자세히 다루지 않겠습니다. 작업 대기열 시스템에 대해 자세히 알아보려면 대기열을 사용하여 복원력 달성에 대한 내 게시물을 확인하십시오. 높은 수준에서 우리가 하는 일은 작업 대기열 역할을 하는 수많은 Redis 인스턴스를 활용하여 수행해야 하는 작업을 추가로 버퍼링하는 것입니다. 당사의 웹 서버와 마찬가지로 이러한 인스턴스는 특정 가용 영역에서 문제가 발생할 경우 더 높은 가용성을 제공하기 위해 가용 영역에 분할되며 중복성을 위해 Redis Sentinel을 사용하여 기본/보조 쌍으로 제공됩니다. 또한 수평 및 수직으로 확장하여 용량과 처리량 모두를 최적화할 수 있습니다.

노동자

이것은 확실히 가장 흥미로운 부분입니다. 작업자를 확장하려면 어떻게 해야 할까요?

무엇보다도 직원과 대기열은 고객, 작업 유형, 필요한 데이터 저장소 등 다양한 차원으로 분류됩니다. 이를 통해 고가용성을 확보할 수 있습니다. 예를 들어 특정 데이터 저장소에 문제가 있는 경우 다른 기능은 계속 완벽하게 작동합니다. 또한 이러한 차원에 따라 작업자 유형을 독립적으로 자동 확장할 수 있습니다. 우리는 수평적으로 확장 가능한 방식으로 작업자 용량을 관리할 수 있게 되었습니다. 즉, 특정 유형의 작업이 더 많으면 더 많은 작업자를 확장할 수 있습니다.

여기에서 언어 또는 프레임워크 선택의 문제를 보기 시작할 수 있습니다. 궁극적으로 더 효율적인 작업자는 더 많은 작업을 더 빨리 수행할 수 있습니다. C 또는 Rust와 같은 컴파일된 언어는 Ruby와 같은 해석된 언어보다 계산 작업에서 훨씬 빠른 경향이 있으며, 이는 일부 워크로드에 대해 더 효율적인 작업자로 이어질 수 있습니다. 그러나 나는 트레이스를 보는 데 많은 시간을 할애하고 Braze의 큰 그림에서 원시 CPU 처리는 놀라울 정도로 적은 양입니다. 처리 시간의 대부분은 데이터 저장소나 외부 요청의 응답을 기다리는 데 사용되며 숫자를 계산하는 것이 아닙니다. 이를 위해 크게 최적화된 C 코드가 필요하지 않습니다.

데이터 저장소

지금까지 우리가 다룬 모든 것은 상당히 확장 가능합니다. 그럼 잠시 시간을 내어 직원들이 가장 많은 시간을 보내는 곳인 데이터 저장소에 대해 이야기해 보겠습니다.

SQL 데이터베이스를 사용하는 웹 서버나 비동기식 작업자를 확장한 적이 있는 사람은 아마도 특정 확장 문제인 트랜잭션에 직면했을 것입니다. 두 개의 FulfillmentRequests와 하나의 PaymentReceipt를 생성하는 주문 완료를 처리하는 엔드포인트가 있을 수 있습니다. 이 모든 것이 트랜잭션에서 발생하지 않으면 일관성 없는 데이터로 끝날 수 있습니다. 단일 데이터베이스에서 동시에 수많은 트랜잭션을 실행하면 잠금 또는 교착 상태에 많은 시간이 소요될 수 있습니다. Braze에서 우리는 객체 독립성과 최종 일관성을 통해 데이터 모델 자체와 함께 스케일링 문제를 정면으로 해결합니다. 이러한 원칙을 통해 데이터 저장소에서 많은 성능을 끌어낼 수 있습니다.

독립 데이터 개체

우리는 아주 좋은 이유로 Braze에서 MongoDB를 많이 활용합니다. 즉, MongoDB 샤드를 상당히 수평으로 확장하고 스토리지 및 성능에서 거의 선형으로 증가할 수 있습니다. 이것은 사용자 프로필이 서로 독립적이기 때문에 매우 잘 작동합니다. 사용자 프로필 간에 유지해야 할 JOIN 문이나 제약 조건이 없습니다. 각 고객이 성장하거나 새로운 고객이 추가되면(또는 둘 다) 기존 데이터베이스에 새 데이터베이스와 새 샤드를 추가하여 용량을 늘릴 수 있습니다. 우리는 이러한 수준의 확장성을 유지하기 위해 다중 문서 트랜잭션과 같은 기능을 명시적으로 피합니다.

MongoDB 외에도 Redis를 분석 정보 버퍼링과 같은 임시 데이터 저장소로 활용하는 경우가 많습니다. 이러한 많은 분석에 대한 진실의 소스는 일정 기간 동안 MongoDB에 독립적인 문서로 존재하기 때문에 수평으로 확장 가능한 Redis 인스턴스 풀을 유지 관리하여 버퍼 역할을 합니다. 이 접근 방식에서는 해시된 문서 ID를 키 기반 샤딩 방식으로 사용하여 독립성으로 인한 부하를 고르게 분산합니다. 주기적 작업은 수평으로 확장된 한 데이터 저장소에서 다른 수평으로 확장된 데이터 저장소로 해당 버퍼를 플러시합니다. 스케일 달성!

또한 위에서 언급한 작업 대기열과 마찬가지로 이러한 인스턴스에 대해 Redis Sentinel을 활용합니다. 또한 다양한 목적을 위해 이러한 Redis 클러스터의 수많은 "유형"을 배포하여 제어된 오류 흐름을 제공합니다(즉, 특정 유형의 Redis 클러스터에 문제가 있는 경우 관련 없는 기능이 동시에 실패하기 시작하는 것을 볼 수 없음).

최종 일관성

Braze는 또한 대부분의 읽기 작업에 대한 원칙으로 최종 일관성을 활용합니다. 이를 통해 대부분의 경우 MongoDB 복제본 세트의 기본 및 보조 구성원 모두에서 읽기를 활용할 수 있으므로 아키텍처를 보다 효율적으로 만들 수 있습니다. 데이터 모델의 이 원칙을 통해 스택 전체에서 캐싱을 많이 활용할 수 있습니다.

우리는 Memcached를 사용하여 다계층 접근 방식을 사용합니다. 기본적으로 데이터베이스에서 문서를 요청할 때 TTL(Time to Live)이 매우 짧은 시스템 로컬 Memcached 프로세스를 확인한 다음 원격 Memcached 인스턴스( 데이터베이스에 직접 요청하기 전에 더 높은 TTL). 이를 통해 고객 설정 또는 캠페인 세부 정보와 같은 일반적인 문서에 대한 데이터베이스 읽기를 크게 줄일 수 있습니다. "결국"이 무섭게 들릴지 모르지만 실제로는 몇 초에 불과하며 이 접근 방식을 사용하면 진실 소스에서 오는 엄청난 양의 트래픽을 줄일 수 있습니다. 컴퓨터 아키텍처 수업을 들어본 적이 있다면 이 접근 방식이 CPU L1, L2 및 L3 캐시 시스템이 작동하는 방식과 얼마나 유사한지 알 수 있습니다!

이러한 트릭을 사용하면 아키텍처에서 가장 느린 부분에서 많은 성능을 끌어낼 수 있으며 처리량이나 용량 요구 사항이 증가할 때 적절하게 수평 확장할 수 있습니다.

Ruby와 Rails가 적합한 위치

문제는 다음과 같습니다. 각 계층이 수평으로 잘 확장되는 전체적 아키텍처를 구축하는 데 많은 노력을 기울이면 언어 또는 런타임의 속도가 생각하는 것보다 훨씬 덜 중요하다는 것이 밝혀졌습니다. 이는 언어, 프레임워크 및 런타임의 선택이 완전히 다른 요구 사항 및 제약 조건으로 이루어짐을 의미합니다.

Ruby와 Rails는 2011년에 Braze가 시작되었을 때 팀이 빠르게 반복할 수 있도록 지원한 입증된 실적을 가지고 있으며, 계속해서 이를 가능하게 하기 때문에 GitHub, Shopify 및 기타 주요 브랜드에서 여전히 사용하고 있습니다. 이들은 각각 Ruby 및 Rails 커뮤니티에서 계속해서 활발하게 개발되고 있으며 둘 다 다양한 요구 사항에 사용할 수 있는 훌륭한 오픈 소스 라이브러리 세트를 여전히 보유하고 있습니다. 이 쌍은 엄청난 유연성을 가지고 있고 일반적인 사용 사례에 대해 상당한 양의 단순성을 유지하므로 빠른 반복을 위한 훌륭한 선택입니다. 우리는 그것을 매일 사용하는 것이 압도적으로 사실임을 알게 됩니다.

이것은 Ruby on Rails가 모든 사람에게 잘 작동하는 완벽한 솔루션이라는 의미가 아닙니다. 그러나 Braze에서 우리는 데이터 수집 파이프라인, 메시지 전송 파이프라인 및 고객 대면 대시보드의 많은 부분을 구동하는 데 매우 잘 작동한다는 것을 발견했습니다. 이 모두는 빠른 반복이 필요하고 Braze 성공의 핵심입니다 플랫폼 전체.

루비를 사용하지 않을 때

하지만 기다려! Braze에서 하는 모든 작업이 Ruby에 있는 것은 아닙니다. 수년 동안 다양한 이유로 다른 언어와 기술로 방향을 틀도록 요청한 곳이 몇 군데 있습니다. Ruby에 의존할 때와 사용하지 않을 때에 대한 추가 통찰력을 제공하기 위해 그 중 세 가지를 살펴보겠습니다.

1. 발신자 서비스

결과적으로 Ruby는 단일 프로세스에서 매우 높은 수준의 동시 네트워크 요청을 처리하는 데 능숙하지 않습니다. Braze가 고객을 대신하여 메시지를 보낼 때 일부 종단 서비스 제공업체는 사용자당 하나의 요청을 요구할 수 있기 때문에 문제입니다. 보낼 준비가 된 100개의 메시지 더미가 있을 때 다음 메시지로 넘어가기 전에 각 메시지가 끝날 때까지 기다리고 싶지 않습니다. 우리는 그 모든 작업을 병렬로 수행하는 것이 훨씬 낫습니다.

Golang으로 작성된 상태 비저장 마이크로서비스인 "Sender Services"를 입력합니다. 위의 예에서 Ruby 코드는 100개의 모든 메시지를 이러한 서비스 중 하나로 보낼 수 있습니다. 이 서비스는 모든 요청을 병렬로 실행하고 완료될 때까지 기다린 다음 대량 응답을 Ruby에 반환합니다. 이러한 서비스는 동시 네트워킹과 관련하여 Ruby로 할 수 있는 것보다 훨씬 더 효율적입니다.

2. 전류 커넥터

Braze Currents의 대용량 데이터 내보내기 기능을 통해 Braze 고객은 하나 이상의 많은 데이터 파트너에게 데이터를 지속적으로 스트리밍할 수 있습니다. 플랫폼은 Apache Kafka에 의해 구동되며 스트리밍은 Kafka 커넥터를 통해 수행됩니다. 기술적으로 Ruby로 작성할 수 있지만 공식적으로 지원되는 방법은 Java입니다. Java 지원 수준이 높기 때문에 이러한 커넥터를 작성하는 것은 Ruby보다 Java에서 훨씬 쉽습니다.

3. 머신러닝

기계 학습에 관한 작업을 해 본 적이 있다면 선택한 언어가 Python이라는 것을 알고 있습니다. Python의 머신 러닝 워크로드를 위한 수많은 패키지와 도구는 동등한 Ruby 지원을 능가합니다. TensorFlow 및 Jupyter 노트북과 같은 것들은 우리 팀에 중요하며 이러한 유형의 도구는 단순히 Ruby 세계에 존재하지 않거나 잘 확립되어 있지 않습니다. 따라서 우리는 기계 학습을 활용하는 제품 요소를 구축할 때 Python에 의존했습니다.

언어가 중요한 경우

분명히 Ruby가 이상적인 선택이 아닌 몇 가지 훌륭한 예가 있습니다. 다른 언어를 선택하는 데에는 여러 가지 이유가 있습니다. 특히 고려해야 할 몇 가지 이유가 있습니다.

전환 비용 없이 새로운 것을 구축

기존 기능과 긴밀하게 결합된 통합 없이 새로운 도메인 모델을 사용하여 완전히 새로운 시스템을 구축하려는 경우 원하는 경우 다른 언어를 사용할 기회가 있을 수 있습니다. 특히 조직에서 다양한 기회를 평가하는 경우 소규모의 고립된 미개발 프로젝트가 새로운 언어나 프레임워크를 시험해 볼 수 있는 훌륭한 실제 실험이 될 수 있습니다.

작업별 언어 생태계 및 인체 공학

특정 언어나 프레임워크를 사용하면 일부 작업이 훨씬 더 쉽습니다. 특히 대시보드 기능 개발을 위해 Rails 및 Grape를 선호하지만 오픈 소스 도구가 존재하지 않기 때문에 기계 학습 코드를 작성하는 것은 Ruby에서 절대적으로 악몽입니다. 특정 프레임워크나 라이브러리를 사용하여 어떤 종류의 기능이나 통합을 구현하고 싶을 수 있으며, 때로는 언어 선택이 이에 영향을 받을 수 있습니다. 왜냐하면 거의 확실하게 더 쉽고 빠른 개발 경험을 제공할 것이기 때문입니다.

실행 속도

때때로 원시 실행 속도에 맞게 최적화해야 하며 사용되는 언어가 이에 크게 영향을 미칩니다. 많은 고주파 거래 플랫폼과 자율 주행 시스템이 C++로 작성된 데에는 그럴만한 이유가 있습니다. 기본적으로 컴파일된 코드는 엄청나게 빠를 수 있습니다! 우리의 Sender Services는 바로 그 이유로 Ruby에서 사용할 수 없는 Golang의 병렬 처리/동시성 기본 요소를 활용합니다.

개발자 친숙도

반면에 고립된 것을 구축하거나 사용하려는 라이브러리를 염두에 두고 있을 수 있지만 언어 선택은 나머지 팀에게는 완전히 낯설습니다. 함수형 프로그래밍에 중점을 둔 스칼라의 새 프로젝트를 도입하면 팀의 다른 개발자에게 친숙한 장벽이 생겨 궁극적으로 지식이 격리되거나 순 속도가 감소할 수 있습니다. 우리는 빠른 반복을 강조하기 때문에 이것이 Braze에서 특히 중요하다는 것을 알고 조직에서 이미 널리 사용되는 도구, 라이브러리, 프레임워크 및 언어의 사용을 권장하는 경향이 있습니다.

마지막 생각들

시간을 거슬러 올라가 거대한 시스템의 소프트웨어 엔지니어링에 대해 한 가지 말할 수 있다면 다음과 같을 것입니다. 대부분의 워크로드에서 전체 아키텍처 선택은 언어 선택보다 확장 제한과 속도를 더 많이 정의할 것입니다. 그 통찰력은 여기 Braze에서 매일 입증됩니다.

Ruby와 Rails는 적절하게 설계된 시스템의 일부일 때 엄청나게 잘 확장되는 놀라운 도구입니다. Rails는 또한 매우 성숙한 프레임워크이며 실제 고객 가치를 신속하게 반복하고 생성하는 Braze의 문화를 지원합니다. 이러한 이유로 Ruby와 Rails는 우리에게 이상적인 도구이며 앞으로도 계속 사용할 계획입니다.

Braze에서 일하는 데 관심이 있으세요? 엔지니어링, 제품 관리 및 사용자 경험 팀에서 다양한 역할을 위해 채용하고 있습니다. 우리의 열린 역할과 문화에 대해 자세히 알아보려면 채용 페이지를 확인하십시오 .