Computer >> 컴퓨터 >  >> 프로그램 작성 >> 데이터 베이스

MongoDB에서 낭비되는 공간을 줄이기 위한 전략

MongoDB에서 낭비되는 공간을 줄이기 위한 전략

Appboy는 모바일 앱을 위한 세계 최고의 마케팅 자동화 플랫폼입니다. 우리는 사용자가 고객의 모바일 앱에서 무엇을 하는지 추적하고 사용자의 행동이나 인구 통계를 기반으로 이메일, 푸시 알림 및 인앱 메시지를 타겟팅할 수 있도록 하여 매달 수십억 개의 데이터 포인트를 수집합니다. MongoDB는 대부분의 데이터베이스 스택을 지원하며 우리는 ObjectRocket의 여러 클러스터에 수십 개의 샤드를 호스팅합니다.

MongoDB의 일반적인 성능 최적화 전략 중 하나는 문서에서 짧은 필드 이름을 사용하는 것입니다. 즉, 다음과 같은 문서를 만드는 대신...

{first_name: "Jon", last_name: "Hyman"}

… 문서가 다음과 같이 보이도록 더 짧은 필드 이름을 사용하십시오.

{fn: "Jon", ln: "Hyman"}

MongoDB는 열 개념이나 미리 정의된 스키마가 없기 때문에 데이터베이스의 모든 문서에 필드 이름이 중복되기 때문에 이 구조가 유리합니다. 각각 "first_name" 필드가 있는 백만 개의 문서가 있는 경우 해당 문자열을 백만 번 저장하는 것입니다. 이로 인해 문서당 더 많은 공간이 발생하여 궁극적으로 메모리에 들어갈 수 있는 문서 수에 영향을 미치며, MongoDB가 문서를 읽을 때 문서를 메모리에 매핑해야 하므로 대규모로 성능에 약간의 영향을 미칠 수 있습니다.

이벤트 데이터를 수집하는 것 외에도 Appboy는 고객이 각 사용자에 대해 "커스텀 속성"이라고 부르는 것을 저장할 수 있습니다. 예를 들어 스포츠 앱은 사용자의 "즐겨찾는 선수"를 저장하고 싶어하는 반면 잡지나 신문 앱은 고객이 "연간 구독자"인지 여부를 저장할 수 있습니다. Appboy에는 추적하는 앱의 각 최종 사용자에 대한 문서가 있으며, 그 문서에 이름이나 성 같은 필드와 함께 해당 사용자 지정 속성을 저장합니다. 공간을 절약하고 성능을 향상시키기 위해 문서에 저장하는 모든 항목의 필드 이름을 줄입니다. 우리가 미리 알고 있는 필드(예:이름, 이메일, 성별 등)에 대해 자체적으로 별칭을 지정할 수 있지만(예:"fn"은 "이름"을 의미함) 사용자 정의 속성의 이름을 예측할 수는 없습니다. 고객이 기록할 것입니다. 고객이 "supercalifragilisticexpialidocious"라는 사용자 지정 속성을 만들기로 결정했다면 모든 문서에 해당 속성을 저장하고 싶지 않습니다.

이 문제를 해결하기 위해 "이름 저장소"라고 하는 것을 사용하여 사용자 정의 속성 필드 이름을 토큰화합니다. 효과적으로 "Favorite Player"와 같은 값을 고유하고 예측 가능하며 매우 짧은 문자열에 매핑하는 것은 MongoDB의 문서입니다. MongoDB의 원자 연산자만을 사용하여 이 맵을 생성할 수 있습니다.

이름 저장소 문서 스키마는 매우 기본적입니다. 각 고객에 대해 하나의 문서가 있고 각 문서에는 "목록"이라는 배열 필드가 하나만 있습니다. 아이디어는 배열이 사용자 정의 속성에 대한 모든 값을 포함하고 주어진 문자열의 인덱스가 토큰이 된다는 것입니다. 따라서 "Favorite Player"를 짧고 예측 가능한 필드 이름으로 번역하려면 "list"를 확인하여 배열의 위치를 ​​확인하기만 하면 됩니다. 존재하지 않는 경우 원자적 푸시를 발행하여 배열의 끝에 요소를 추가할 수 있습니다. (db.custom_attribute_name_stores.update({_id :X, list:{$ne :"Favorite Player"}}, {$ push:{list:“Favorite Player”}})), 문서를 다시 로드하고 색인을 결정합니다. 이상적으로는 $addToSet을 사용했지만 $addToSet은 순서를 보장하지 않는 반면 $push는 기본적으로 끝에 추가하도록 문서화되어 있습니다.

따라서 이 시점에서 "Favorite Player"와 같은 것을 정수 값으로 변환할 수 있습니다. 값이 1이라고 가정합니다. 그러면 사용자 문서는 다음과 같을 것입니다.

{
  fn: "Jon", 
  ln: "Hyman",
  custom: {
    1: "LeBron James"
  }
}

필드 이름은 짧고 깔끔합니다! 이것의 한 가지 큰 부작용은 달러 기호나 마침표와 같이 MongoDB가 이스케이프 없이는 지원할 수 없는 문자를 사용하는 고객에 대해 걱정할 필요가 없다는 것입니다.

이제 MongoDB가 지속적으로 증가하는 문서에 대해 경고하고 이름 저장소 문서가 무한히 성장할 수 있다고 생각할 수 있습니다. 실제로 우리는 고객당 하나 이상의 문서를 저장할 수 있도록 구현을 약간 확장했습니다. 이를 통해 새 문서를 생성하기 전에 허용하는 배열 요소의 수에 합리적인 제한을 둘 수 있습니다. 가장 좋은 점은 MongoDB만 사용하여 이 모든 것을 원자적으로 수행할 수 있다는 것입니다! 이를 달성하기 위해 "최소값"이라는 다른 필드를 각 문서에 추가합니다. "최소값" 필드는 이 문서가 생성되기 전에 이전 문서에 추가된 요소 수를 나타냅니다. 따라서 "최소값"이 100이고 "목록"이 ["시즌 티켓 소지자", "즐겨찾기 선수"]인 문서를 본다면 "즐겨찾기 선수"의 토큰 값은 101입니다(우리는 0을 사용하고 있습니다). 기반 인덱싱). 이 예에서는 새 문서를 만들기 전에 "목록" 배열에 100개의 값만 저장합니다. 이제 삽입할 때 가장 높은 "최소 value" 값을 지정하고 "list.99"가 존재하지 않는지 확인합니다("list" 배열의 인덱스 99에 아무것도 없음을 의미). 해당 인덱스에 요소가 이미 있는 경우 푸시 작업은 아무 작업도 수행하지 않습니다. 이 경우 모든 문서에 존재하는 요소의 총 수와 동일한 "최소 값"을 사용하여 새 이름 저장소 문서를 생성해야 한다는 것을 알고 있습니다. 원자 $findAndModify를 사용하여 새 문서가 존재하지 않는 경우 생성하고 다시 가져온 다음 $push를 다시 시도할 수 있습니다.

고객에게 몇 가지 사용자 정의 속성 이상이 있는 경우 값에서 토큰으로 변환하기 위해 모든 이름 저장소 문서를 다시 읽는 것은 대역폭 및 처리 측면에서 비용이 많이 들 수 있습니다. 그러나 주어진 필드의 토큰 값은 일단 계산되면 항상 동일하므로 토큰을 캐시하여 번역 속도를 높입니다.

유연한 스키마를 계속 사용하면서 필드 이름 크기를 줄이기 위해 애플리케이션의 다양한 부분에 "이름 저장소 토큰" 패러다임을 적용했습니다. 가치에도 도움이 될 수 있습니다. 라디오 방송국 앱이 사용자가 듣는 상위 50명의 아티스트의 배열인 사용자 정의 속성을 저장한다고 가정해 보겠습니다. 50개의 문자열이 포함된 배열을 사용하는 대신 라디오 방송국 이름을 토큰화하고 대신 사용자에게 50개의 정수 배열을 저장할 수 있습니다. 특정 아티스트를 좋아하는 사용자를 쿼리하려면 이제 두 가지 토큰 조회가 필요합니다. 하나는 필드 이름이고 다른 하나는 값입니다. 그러나 값에서 토큰으로의 변환을 캐시하기 때문에 캐시 계층에서 다중 가져오기를 사용하여 값을 변환할 때 캐시에 대한 단일 왕복을 유지할 수 있습니다.

이 최적화는 확실히 간접적이고 복잡성을 추가하지만 Appboy에서 하는 것처럼 수억 명의 사용자를 저장할 때 가치가 있는 최적화입니다. 이 트릭을 통해 수백 기가바이트의 값비싼 SSD 공간을 절약했습니다.

더 알고 싶으십니까? 9월 18일 Cipriani에서 열리는 Rackspace Solve NYC 컨퍼런스 기간 동안 Appboy에서 데브옵스에 대해 논의할 예정입니다.