Модель данных - moevm/nosql-2017-bandmap GitHub Wiki

Модель данных профилей

В одной коллекции хранятся сущности разных профилей, т.к. по сути это одно и то же, но для каждого типа добавляются специфические поля.

Модель документа музыканта:

{

"_id": ObjectId,

"log": string //логин (уникальный)

"user": string // логин пользователя-создателя

"n": string //имя

"t": int32 //тип профиля (Enum)

"loc": GeoJsonPoint //координаты местоположения

"a": string //адрес

"p": ObjectId, //фото, ссылка на бинарный файл в gridfs,

"pm": ObjectId, //мини-фото, ссылка на бинарный файл в gridfs,

"ytv":[

   {

       "_id":string// id видео на ютуб или вимео

       "vn": string // кастомное имя видео

       "vd": string // кастомное описание видео

   },...

  ], // список вложенных объектов описания видео ()

"d": string, //описание профиля

"vk": string, //ссылка на ВКонтакте

"ia": boolean, //флаг активности профиля

"fav": Array, //пользователи, отметившие данный профиль как избранное

"instr": int32, //тип инструмента (Enum)

"sex": int32, //пол музыканта (Enum)

"band": Array //ссылки на группы, в которых играет музыкант

}

  • Оценка удельного объема информации, хранимой в модели (сколько потребуется памяти, чтобы сохранить)
  1. "user"- string - содержит в среднем 10 символов - 10 байт

  2. "n"- string - содержит в среднем 10 символов - 10 байт

  3. "t"- int32 4 байта

  4. "loc" - GeoJsonPoint - содержит два значения double - 16 байт

  5. "a" - string - содержит в среднем 20 символов - 20 байт

  6. "p" - ObjectId - 30 байт

  7. "pm"- ObjectId - 30 байт

  8. "ytv":[

    {
    
        8.1. "_id"-  string - содержит в среднем 20 символов - 20 байт
    
        8.2. "vn"-  string - содержит в среднем 20 символов - 20 байт
    
        8.3. "vd"-  string - содержит в среднем 20 символов - 20 байт
    
    },...
    

    ], - занимает 60*n байт. где n - количество вложенных объектов. Будем считать что 300 байт.

  9. "d" - string - содержит в среднем 100 символов - 100 байт

  10. "vk" -string- содержит в среднем 17 символов - 17 байт

  11. "ia" - boolean - 1 байт

  12. "fav" - Array - 10*n байт, где n - размер списка. Будем считать что 100 байт.

  13. "instr" - int32 4 байта

  14. "sex" - int32 4 байта

  15. "band" - Array - 30*n байт, где n - размер списка. Будем считать что 120 байт в среднем.

В итоге выходит 766 байт памяти нужно для сохранения одной сущности.

Размер, который занимает коллекция из 15 элементов, равен 12561 байт, что равняется в среднем 837,4 байта на одну сущность.

Коллекция в которой 20000 элементов занимает 16 мегабайт

Из этого можно сделать вывод, что размер базы дынных увеличивается линейно при добавлении новых данных

Такие же данные могли бы выглядеть в виде SQL-таблиц следующим образом

  • Оценка удельного объема информации, хранимой в модели (сколько потребуется памяти, чтобы сохранить)

  • Таблица Band(2609223 байт)

  1. "id" - счетчик, 8 байт

  2. "EntityType" - TINYINT, 1 байта

  3. "Login" - varchar(20), 20 байт

  4. "Latitude" - DOUBLE - 8 байт

  5. "Longitude" - DOUBLE - 8 байт

  6. "Photo"

    6.1. "PhotoFileData" - blob - 2 Мб

    6.2. "PhotoFileName" - varchar(10), 10 байт

    6.3. "PhotoFileType" - varchar(3), 3 байт

  7. "PhotoMini"

    7.1. "PhotoMiniFileData" - blob - 500 Кб

    7.2. "PhotoMiniFileName" - varchar(10), 10 байт

    7.3. "PhotoMiniFileType" - varchar(3), 3 байт

  • Таблица Musician(2609263 байт)
  1. "id" - счетчик, 8 байт

  2. "EntityType" - TINYINT, 1 байта

  3. "Login" - varchar(20), 20 байт

  4. "Latitude" - DOUBLE - 8 байт

  5. "Longitude" - DOUBLE - 8 байт

  6. "Photo"

    6.1. "PhotoFileData" - blob - 2 Мб

    6.2. "PhotoFileName" - varchar(10), 10 байт

    6.3. "PhotoFileType" - varchar(3), 3 байт

  7. "PhotoMini"

    7.1. "PhotoMiniFileData" - blob - 500 Кб

    7.2. "PhotoMiniFileName" - varchar(10), 10 байт

    7.3. "PhotoMiniFileType" - varchar(3), 3 байт

  8. "Address" - varchar(20), 20 байт

  9. "Website" - varchar(20), 20 байт

  • Таблица Shop(2609263 байт)
  1. "id" - счетчик, 8 байт

  2. "EntityType" - TINYINT, 1 байта

  3. "Login" - varchar(20), 20 байт

  4. "Latitude" - DOUBLE - 8 байт

  5. "Longitude" - DOUBLE - 8 байт

  6. "Photo"

    6.1. "PhotoFileData" - blob - 2 Мб

    6.2. "PhotoFileName" - varchar(10), 10 байт

    6.3. "PhotoFileType" - varchar(3), 3 байт

  7. "PhotoMini"

    7.1. "PhotoMiniFileData" - blob - 500 Кб

    7.2. "PhotoMiniFileName" - varchar(10), 10 байт

    7.3. "PhotoMiniFileType" - varchar(3), 3 байт

  8. "Address" - varchar(20), 20 байт

  9. "Website" - varchar(20), 20 байт

  • Таблица Studio(2609263 байт)
  1. "id" - счетчик, 8 байт

  2. "EntityType" - TINYINT, 1 байта

  3. "Login" - varchar(20), 20 байт

  4. "Latitude" - DOUBLE - 8 байт

  5. "Longitude" - DOUBLE - 8 байт

  6. "Photo"

    6.1. "PhotoFileData" - blob - 2 Мб

    6.2. "PhotoFileName" - varchar(10), 10 байт

    6.3. "PhotoFileType" - varchar(3), 3 байт

  7. "PhotoMini"

    7.1. "PhotoMiniFileData" - blob - 500 Кб

    7.2. "PhotoMiniFileName" - varchar(10), 10 байт

    7.3. "PhotoMiniFileType" - varchar(3), 3 байт

  8. "Address" - varchar(20), 20 байт

  9. "Website" - varchar(20), 20 байт

  • Таблица Rehearsal(2609263 байт)
  1. "id" - счетчик, 8 байт

  2. "EntityType" - TINYINT, 1 байта

  3. "Login" - varchar(20), 20 байт

  4. "Latitude" - DOUBLE - 8 байт

  5. "Longitude" - DOUBLE - 8 байт

  6. "Photo"

    6.1. "PhotoFileData" - blob - 2 Мб

    6.2. "PhotoFileName" - varchar(10), 10 байт

    6.3. "PhotoFileType" - varchar(3), 3 байт

  7. "PhotoMini"

    7.1. "PhotoMiniFileData" - blob - 500 Кб

    7.2. "PhotoMiniFileName" - varchar(10), 10 байт

    7.3. "PhotoMiniFileType" - varchar(3), 3 байт

  8. "Address" - varchar(20), 20 байт

  9. "Website" - varchar(20), 20 байт

Документы являются оптимальными в данном случае по сравнению с реляционными данными по следующим причинам:

  • позволяют хранить данные похожей, но все же отличной, структуры в одном хранилище (коллекции). В случае реляционной базы надо было бы создавать по таблице на каждый профиль. При условии что количество профилей будет расти, будет увеличиваться количество запросов, а так же сложность поддержки при сложных агрегирующих, фильтрующих запросах. При документах всегда выполняется один запрос с любой сложностью.

Запрос поиска по тексту профилей всех типов:

db.bases.find({n:{$regex: /Кирилл/}})

Агрегирующий запрос статистик по количеству каждого типа сущности:

db.bases.aggregate({$group: { _id: "$t", count: {$sum: 1} } })

  • позволяет хранить вложенные элементы, массивы. Видео у музыкантов и групп в случае реляционной модели необходимо было бы создавать дополнительные таблицы для хранение этих данных и использовать затратные операции JOIN, или как в примере выше хранить данные в видео строки в полях сущностей, что было бы сложно поддерживать и при каждом запросе дополнительно сериализовать.

Запрос профиля со всей информацией о нем, включая вложенные элементы:

db.bases.find({log:"rogulenkoko"})

  • позволяет делать оптимизированную выборку используя индексы геолокации и специальный тип MongoDB 2dsphere, с помощью которого при запросе не будет просматриваться вся коллекция, в отличие от реляционных баз, а будет выбирать только профили, подходящие под сложные географические критерии.

Запрос первых 20 профилей в кругу радиуса 1 радиан с центром в точке (50,30):

db.bases.find({ loc: { $nearSphere: [50, 30], $minDistance: 0, $maxDistance: 1 } }).limit(20)

  • позволяет хранить и запрашивать данные через специальный инструмент GridFs, который хранит больший бинарные данные в виде небольших блоков. Запросы оптимизированы сравнительно с sql базами. Преимуществом от хранения файлов в фаловой системе является удобство переноса данных по разным серверам и обновление данных

Реляционными данные являются оптимальными в данном случае по сравнению с документам по следующим причинам:

  • связи профилей музыкантов и групп, позволяет каскадно удалять сущности и их связи

По выше указанным причинам была выбрана нереляционная база данных MongoDb, так как MongoDb имеет большее количество достоинств и меньшее количество недостатков в данном случае в отличие от любой реляционной СУБД.

Модель данных статистик

В одной колллекции хранятся статистики (по одному документу на каждый тип статистики)

Модель документа статистики по типам профилей

{

"_id": ObjectId,

"d": Date //дата обновления

"t": int32 //тип статистики (Enum)

"cs": [{

 "key":int32, // тип профиля

 "count": int32 //количество профилей данного типа

},...]

}

  1. "_id"- ObjectId - 30 байт

  2. "d" - Date - 5 байт

  3. "t" - int32 - 4 байт

  4. "cs": [{

    4.1. "key" - int32 - 4 байт

    4.2. "count"- int32 - 4 байт },...] - 24 байт

вес таблицы занимает 63 байта

Такие же данные могли бы выглядеть в виде SQL-таблиц следующим образом

  • CityStatistics(16 байт)
  1. "id" - счетчик, 8 байт

  2. "UpdateData" - DATE - 3 байта

  3. "Type" - TINYINT - 1 байт

  4. "CountStatistics" - INT - 4 байт

  • EntityTypeStatistics(16 байт)
  1. "id" - счетчик, 8 байт

  2. "UpdateData" - DATE - 3 байта

  3. "Type" - TINYINT - 1 байт

  4. "CountStatistics" - INT - 4 байт

  • TopProfileStatistics(16 байт)
  1. "id" - счетчик, 8 байт

  2. "UpdateData" - DATE - 3 байта

  3. "Type" - TINYINT - 1 байт

  4. "CountStatistics" - INT - 4 байт

Документы являются оптимальными в данном случае по сравнению с реляционными данными по следующим причинам:

  • позволяют хранить данные похожей, но все же отличной (отличается тип ключевого поля количественной статистики), структуры в одном хранилище (коллекции).

  • позволяет забрать все виды статистики одним запросом, что в реляционной надо было бы делать в n запросов, где n-количество типов статистик

  • позволяет хранить вложенные элементы (специфические количественные статистики для каждого типа статистик). В некоторых могут хранится дополнительные данные, например для статистики "Топ профилей" необходимо хранить логин профиля и его тип

  • для каждого типа статистики будет хранится только один экземпляр (за весь период). Поэтому нецелесообразно было бы делать n таблиц, зная, что в каждой из них будет по одной записи

⚠️ **GitHub.com Fallback** ⚠️