Meteor는 많은 클라이언트간에 거대한 컬렉션을 공유하는 동안 얼마나 효율적일 수 있습니까?


100

다음과 같은 경우를 상상해보십시오.

  • 1,000 명의 클라이언트가 "Somestuff"컬렉션의 내용을 표시하는 Meteor 페이지에 연결되어 있습니다.

  • "Somestuff"는 1,000 개의 항목을 보유한 컬렉션입니다.

  • 누군가 "Somestuff"컬렉션에 새 항목을 삽입합니다.

무슨 일이 일어날 것:

  • Meteor.Collection클라이언트의 모든 항목 이 업데이트됩니다. 즉 삽입이 모든 클라이언트에 전달됩니다 (즉, 하나의 삽입 메시지가 1,000 개의 클라이언트에 전송됨을 의미 함).

업데이트해야 할 클라이언트를 결정하는 서버의 CPU 비용은 얼마입니까?

전체 목록이 아닌 삽입 된 값만 클라이언트에 전달되는 것이 정확합니까?

실생활에서 어떻게 작동합니까? 그러한 규모의 벤치 마크 나 실험이 있습니까?

답변:


119

짧은 대답은 새로운 데이터 만 유선으로 전송된다는 것입니다. 작동 방식은 다음과 같습니다.

Meteor 서버에는 구독을 관리하는 세 가지 중요한 부분이 있습니다. 구독이 제공하는 데이터에 대한 논리를 정의하는 게시 기능 ; 몽고 드라이버 변경에 대한 데이터베이스를 시계; 및 클라이언트의 모든 활성 구독을 결합하여 네트워크를 통해 클라이언트로 보내는 병합 상자 .

게시 기능

Meteor 클라이언트가 컬렉션을 구독 할 때마다 서버는 게시 기능을 실행 합니다 . 게시 기능의 역할은 클라이언트가 가져야하는 문서 세트를 파악하고 각 문서 속성을 병합 상자로 보내는 것입니다. 새 구독 클라이언트마다 한 번씩 실행됩니다. 를 사용하는 임의의 복잡한 액세스 제어와 같이 게시 기능에 원하는 JavaScript를 넣을 수 있습니다 this.userId. 는 함수가 호출하여 병합 상자에 데이터를 전송 게시 this.added, this.changed하고 this.removed. 자세한 내용은 전체 게시 문서 를 참조하세요.

게시 대부분의 기능은 낮은 수준의 주위에 깨끗이 할 필요가 없습니다 added, changed그리고 removed하지만, API. A는 함수를 리턴한다 몽고 커서를 게시하면, 유성 서버는 자동으로 몽고 드라이버 (의 출력에 연결 insert, updateremoved병합 상자의 입력에 콜백을 () this.added, this.changedthis.removed). 게시 기능에서 모든 권한 확인을 미리 수행 한 다음 사용자 코드없이 데이터베이스 드라이버를 병합 상자에 직접 연결할 수 있다는 것은 매우 깔끔합니다. 그리고 자동 게시가 설정되면이 작은 부분조차 숨겨집니다. 서버는 각 컬렉션의 모든 문서에 대한 쿼리를 자동으로 설정하고 병합 상자로 푸시합니다.

반면에 데이터베이스 쿼리 게시에만 국한되지 않습니다. 예를 들어 내부의 기기에서 GPS 위치를 읽 Meteor.setInterval거나 다른 웹 서비스에서 레거시 REST API를 폴링하는 게시 함수를 작성할 수 있습니다 . 이러한 경우에, 당신은 낮은 수준을 호출하여 병합 상자의 변경을 방출 것 added, changedremovedDDP API.

몽고 드라이버

몽고 드라이버의 작업은 라이브 쿼리에 대한 변경 사항 몽고 데이터베이스를 시청하는 것입니다. 이러한 쿼리는 계속 실행 added되며 removed, 및 changed콜백 을 호출하여 결과가 변경되면 업데이트를 반환합니다 .

Mongo는 실시간 데이터베이스가 아닙니다. 그래서 운전자가 투표합니다. 각 활성 라이브 쿼리에 대한 마지막 쿼리 결과의 메모리 내 복사본을 유지합니다. 각 폴링주기에, 그것은의 최소 세트 컴퓨팅, 이전 저장 결과 새로운 결과를 비교 added, removed그리고 changed 그 차이를 설명하는 이벤트. 여러 호출자가 동일한 라이브 쿼리에 대한 콜백을 등록하는 경우 드라이버는 쿼리의 복사본 하나만 감시하여 동일한 결과로 등록 된 각 콜백을 호출합니다.

서버가 컬렉션을 업데이트 할 때마다 드라이버는 해당 컬렉션에 대한 각 라이브 쿼리를 다시 계산합니다 (향후 Meteor 버전에서는 업데이트시 다시 계산되는 라이브 쿼리를 제한하기 위해 스케일링 API를 제공합니다.) 또한 드라이버는 10 초 타이머에서 각 라이브 쿼리를 폴링하여 Meteor 서버를 우회하는 대역 외 데이터베이스 업데이트를 포착합니다.

병합 상자

의 작업 병합 상자 결과 (결합하는 것입니다 added, changed그리고 removed 단일 데이터 스트림으로 클라이언트의 활성 게시의 모든 기능 통화). 연결된 각 클라이언트에 대해 하나의 병합 상자가 있습니다. 클라이언트의 minimongo 캐시의 전체 사본을 보유합니다.

단일 구독이있는 예에서 병합 상자는 기본적으로 통과입니다. 그러나 더 복잡한 앱에는 중복 될 수있는 여러 구독이있을 수 있습니다. 두 구독이 모두 동일한 문서에 동일한 속성을 설정하면 병합 상자는 우선 순위를 갖는 값을 결정하고 클라이언트에게만 보냅니다. 아직 구독 우선 순위 설정을위한 API를 공개하지 않았습니다. 현재 우선 순위는 클라이언트가 데이터 세트를 구독하는 순서에 따라 결정됩니다. 클라이언트가 만드는 첫 번째 구독이 가장 높은 우선 순위를 가지며 두 번째 구독이 그 다음으로 높은 방식입니다.

병합 상자는 클라이언트의 상태를 유지하기 때문에 게시 기능이 제공하는 내용에 관계없이 각 클라이언트를 최신 상태로 유지하기 위해 최소한의 데이터를 보낼 수 있습니다.

업데이트시 발생하는 사항

이제 시나리오에 대한 단계를 설정했습니다.

1,000 개의 연결된 클라이언트가 있습니다. 각각은 동일한 라이브 Mongo 쿼리 ( Somestuff.find({}))를 구독합니다 . 쿼리는 각 클라이언트에 대해 동일하므로 드라이버는 하나의 라이브 쿼리 만 실행합니다. 1,000 개의 활성 병합 상자가 있습니다. 그리고 각 클라이언트의 기능이 등록 게시 added, changed그리고 removed그 라이브 쿼리 병합 상자 중 하나에 피드가. 병합 상자에 다른 것은 연결되어 있지 않습니다.

먼저 몽고 드라이버. 클라이언트 중 하나가에 새 문서를 삽입 Somestuff하면 재 계산이 트리거됩니다. Mongo 드라이버는의 모든 문서에 대한 쿼리를 다시 실행하고 Somestuff, 결과를 메모리의 이전 결과와 비교하고, 새 문서가 하나 있음을 확인하고, 1,000 개의 등록 된 insert콜백을 각각 호출합니다.

다음으로 게시 기능입니다. 여기서는 거의 일어나지 않습니다. 1,000 개의 insert콜백 각각 은를 호출하여 병합 상자로 데이터를 푸시합니다 added.

마지막으로 각 병합 상자는 클라이언트 캐시의 메모리 내 사본에 대해 이러한 새 속성을 확인합니다. 각각의 경우 값이 아직 클라이언트에 없으며 기존 값을 숨기지 않음을 발견합니다. 따라서 병합 상자 DATA는 클라이언트에 대한 SockJS 연결에서 DDP 메시지를 내보내고 서버 측 메모리 내 복사본을 업데이트합니다.

총 CPU 비용은 하나의 Mongo 쿼리를 비교하는 비용과 클라이언트의 상태를 확인하고 새로운 DDP 메시지 페이로드를 구성하는 1,000 개의 병합 상자 비용을 더한 비용입니다. 유선을 통해 흐르는 유일한 데이터는 데이터베이스의 새 문서에 해당하는 1,000 개의 클라이언트 각각에 전송 된 단일 JSON 개체와 원본 삽입을 만든 클라이언트 에서 서버 로 보내는 하나의 RPC 메시지 입니다.

최적화

우리가 확실히 계획 한 것은 다음과 같습니다.

  • 보다 효율적인 Mongo 드라이버. 우리는 드라이버를 최적화 된 별개의 쿼리 당 하나의 관찰자를 실행하는 0.5.1에서.

  • 모든 DB 변경이 쿼리 재 계산을 트리거하는 것은 아닙니다. 일부 자동 개선을 수행 할 수 있지만 가장 좋은 방법은 개발자가 재실행이 필요한 쿼리를 지정할 수있는 API입니다. 예를 들어, 하나의 채팅방에 메시지를 삽입하는 것이 두 번째 방의 메시지에 대한 실시간 쿼리를 무효화해서는 안된다는 것은 개발자에게 분명합니다.

  • Mongo 드라이버, 게시 기능 및 병합 상자는 동일한 프로세스 또는 동일한 시스템에서 실행할 필요가 없습니다. 일부 애플리케이션은 복잡한 라이브 쿼리를 실행하고 데이터베이스를보기 위해 더 많은 CPU가 필요합니다. 다른 쿼리에는 몇 가지 고유 한 쿼리 만 있지만 (블로그 엔진을 상상해보세요) 연결된 클라이언트가 많을 수 있습니다. 이러한 클라이언트에는 병합 상자에 더 많은 CPU가 필요합니다. 이러한 구성 요소를 분리하면 각 조각을 독립적으로 확장 할 수 있습니다.

  • 많은 데이터베이스는 행이 업데이트 될 때 실행되는 트리거를 지원하고 이전 및 새 행을 제공합니다. 이 기능을 사용하면 데이터베이스 드라이버가 변경 사항을 폴링하는 대신 트리거를 등록 할 수 있습니다.


Meteor.publish를 사용하여 커서가 아닌 데이터를 게시하는 방법에 대한 예가 있습니까? 답변에 언급 된 레거시 나머지 API의 결과와 같은?
Tony

@Tony : 문서에 있습니다. 방 계산 예를 확인하십시오.
Mitar

이 릴리스 0.7, 0.7.1에서 0.7.2 유성은 대부분의 질의에 대한 드라이버 (예외가 관찰 OpLog로 전환 있음을 주목할 필요가있다 skip, $near$whereCPU 부하, 네트워크 대역폭에서 훨씬 더 효율적이며 응용 프로그램을 확장 할 수 있습니다 포함 된 쿼리) 서버.
imslavko 2014 년

모든 사용자가 동일한 데이터를 보지 못하는 경우는 어떻습니까? 1. 그들은 다른 주제를 구독했습니다 .2. 그들은 다른 역할을 가지고 있기 때문에 동일한 주요 주제 내에서 그들에게 도달하지 않아야 할 몇 가지 메시지가 있습니다.
tgkprog 2014 년

@debergalis 캐시 무효화에 관한 것은, 아마 당신은 나의 종이에서 아이디어를 찾을 수 있습니다 vanisoft.pl/~lopuszanski/public/cache_invalidation.pdf을 가치
qbolec

29

내 경험상 Meteor에서 거대한 컬렉션을 공유하는 동안 많은 클라이언트를 사용하는 것은 버전 0.7.0.1부터 본질적으로 작동하지 않습니다. 이유를 설명하려고합니다.

위의 게시물과 https://github.com/meteor/meteor/issues/1821 에서 설명한 것처럼 유성 서버는 병합 상자 에 각 클라이언트에 대해 게시 된 데이터의 복사본을 보관해야합니다 . 이것이 Meteor 마법이 일어날 수있게 해주지 만, 노드 프로세스의 메모리에 반복적으로 유지되는 대용량 공유 데이터베이스를 초래합니다. ( meteor에 컬렉션이 정적이라고 말할 수있는 방법이 있습니까? ) 와 같은 정적 컬렉션에 대해 가능한 최적화를 사용하더라도 Node 프로세스의 CPU 및 메모리 사용량에 큰 문제가 발생했습니다.

우리의 경우에는 완전히 정적 인 각 클라이언트에게 15k 문서 모음을 게시했습니다. 문제는 연결시 이러한 문서를 클라이언트의 병합 상자 (메모리)에 복사하면 기본적으로 Node 프로세스가 거의 1 초 동안 100 % CPU로 전환되어 추가 메모리 사용량이 증가한다는 것입니다. 연결하는 클라이언트가 서버를 무릎에 놓고 (동시 연결이 서로를 차단할 것임) 메모리 사용량이 클라이언트 수에 따라 선형 적으로 증가하기 때문에 이것은 본질적으로 확장 할 수 없습니다. 우리의 경우 전송 된 원시 데이터가 약 5MB에 불과했지만 각 클라이언트는 추가로 ~ 60MB 의 메모리 사용량을 발생 시켰습니다.

우리의 경우 컬렉션이 정적 이었기 때문에 모든 문서를 .jsonnginx에 의해 gzip으로 압축 된 파일 로 전송 하고 익명 컬렉션에로드하여 추가 CPU없이 1MB의 데이터 만 전송함으로써이 문제를 해결 했습니다. 또는 노드 프로세스의 메모리 및 훨씬 더 빠른로드 시간. 이 컬렉션에 대한 모든 작업 _id은 서버에있는 훨씬 작은 출판물의 s를 사용하여 수행 되었으므로 Meteor의 대부분의 이점을 유지할 수 있습니다. 이를 통해 앱을 더 많은 클라이언트로 확장 할 수있었습니다. 또한 앱이 대부분 읽기 전용이기 때문에 각 노드 인스턴스가 단일 스레드이기 때문에로드 밸런싱 (단일 Mongo 사용)이있는 nginx 뒤에서 여러 Meteor 인스턴스를 실행하여 확장 성을 더욱 향상 시켰습니다.

그러나 여러 클라이언트간에 큰 쓰기 가능한 컬렉션을 공유하는 문제는 Meteor가 해결해야하는 엔지니어링 문제입니다. 각 클라이언트에 대한 모든 사본을 보관하는 것보다 더 좋은 방법이있을 수 있지만 분산 시스템 문제로 심각한 생각이 필요합니다. 대규모 CPU 및 메모리 사용의 현재 문제는 확장되지 않습니다.


@Harry oplog는이 상황에서 중요하지 않습니다. 데이터가 정적이었습니다.
Andrew Mao

서버 측 minimongo 사본의 차이를 표시하지 않는 이유는 무엇입니까? 1.0에서 모두 변경 되었나요? 내 말은 보통 그것들은 내가 바라는 것과 같고, 심지어 콜백하는 기능도 비슷할 것이라는 것을 의미한다 (만약 내가 거기에 저장되고 잠재적으로 다른 무언가를 따르는 경우).
MistereeDevlord

@MistereeDevlord 클라이언트 데이터의 변경 사항 및 캐시 비교는 현재 분리되어 있습니다. 모든 사람이 동일한 데이터를 가지고 있고 하나의 diff 만 필요하더라도 서버가 동일하게 처리 할 수 ​​없기 때문에 클라이언트 당 캐시가 다릅니다. 이것은 기존 구현보다 확실히 더 현명하게 수행 될 수 있습니다.
Andrew Mao

@AndrewMao gzip으로 압축 된 파일을 클라이언트로 보낼 때 보안을 유지하려면 어떻게해야합니까? 즉, 로그인 한 클라이언트 만 액세스 할 수 있습니다.
FullStack

4

이 질문에 답하기 위해 사용할 수있는 실험 :

  1. 테스트 유성 설치 : meteor create --example todos
  2. Webkit 검사기 (WKI)에서 실행합니다.
  3. 회선을 통해 이동 하는 XHR 메시지 의 내용을 조사하십시오 .
  4. 전체 컬렉션이 전선을 가로 질러 이동하지 않는지 확인합니다.

WKI 사용 방법에 대한 팁은이 기사를 확인 하십시오 . 약간 구식이지만 특히이 질문에 대해서는 여전히 유효합니다.


2
폴링 메커니즘에 대한 설명 : eventedmind.com/posts/meteor-liveresultsset
cmather

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.