Этот пост принадлежит Дж. Крису Андерсону из блога Couchbase.
«Как мне получить доступ к моим данным?» разработчики часто спрашивают, как они рассматривают решение для хранения данных. Чтобы ответить на этот вопрос, нужно сначала понять рассматриваемое приложение. Кто самые важные пользователи, и какие варианты использования должны быть быстрыми, то есть какие действия пользователь предпринимает много? Что такое горячий путь?
Как только вы поймете горячий путь, вы будете готовы посмотреть на хранилище.
Некоторые действия, такие как загрузка большого файла, не так чувствительны к задержке, как другие. Например, нажав «купить сейчас» на сайте электронной коммерции или просмотрев вашу временную шкалу в социальных сетях, вы получите гораздо больше преимуществ от высокопроизводительных возможностей хранения и поиска.
Таким образом, для каждого приложения, если вы посмотрите на производительность в архитектуре, вы увидите, что есть места, где пользователь часто зависает, где отзывчивость является ключевым фактором. Речь идет не только о скорости, но и о высокой скорости работы под нагрузкой. Девятки имеют значение, особенно для латентности.
Вы хотите, чтобы ваши самые важные наиболее важные варианты использования были быстрыми. Поскольку память намного быстрее диска, она определенно должна обслуживаться из памяти. Неслучайно базы данных на основе памяти являются восходящими. Особенно в облачных средах обслуживание из памяти изолирует приложение от непостоянной производительности диска.
Расставьте приоритеты вашей схемы вокруг горячего пути
Даже с базой данных без схемы ваше приложение будет навязывать схему. Вы должны сделать эту схему благоприятной для высокопроизводительных сценариев использования. Таким образом, выявление типов горячих объектов и хранение их в постоянном сегменте — это один шаг. Так что, если у вас есть 4 ГБ данных в реальном времени, просто выделите 4 + ГБ ОЗУ только для этого. Если у вас есть 20 ГБ данных истории покупок, вы можете выделить только 5 ГБ ОЗУ для этих типов документов. Это позволяет использовать эффекты «больше диска», чем память, для менее чувствительных к задержкам данных, сохраняя при этом производительность самых горячих данных при любых обстоятельствах.
Теперь, когда вы думаете о «горячем пути», давайте поговорим о взглядах. Они очень гибкие (ключевым моментом здесь является возможность проецировать данные в реальном времени в формы, более выгодные для запросов). Так как вы хотите, чтобы ваш горячий путь работал на модели ключ-значение, вы в конечном итоге будете использовать много указателей и многократное получение. То есть высокопроизводительное приложение будет тесно привязывать свою модель данных к горячему пути. Это может сделать поддержку менее популярных сценариев использования более сложной. Вот где вступают представления. Представления достаточно мощны, чтобы дать этим управляемым структурам данных новую жизнь в качестве индексов с возможностью запроса. В результате вы получаете гибкую модель запросов, которая в дополнение к производительности ключевых значений.
Чтобы взять конкретный пример, посмотрите на набор данных образцов пива. Возможно, в вашем приложении основной экран, который хотят видеть пользователи, представляет собой список пива с его оценками, а для каждого пива — возможность быстро загружать отзывы о пиве, написанные конечными пользователями. В этом случае вы захотите выполнить массовый поиск по значению ключа (а не просмотреть запросы), чтобы заполнить эти экраны с максимально возможной производительностью. Если вторичный вариант использования просматривает пивоваренные заводы по городам, то это хорошее время для использования представлений, поскольку они могут обеспечить гибкое окно в базовые данные.
Давайте начнем с рассмотрения самих документов. В этом случае были хранятся рейтинги непосредственно по пивным документам. Встраивая рейтинги, мы можем тривиально отображать их в пользовательском интерфейсе без каких-либо запросов или дополнительных проверок. Мы также ссылаемся непосредственно на обзоры («комментарии») из пивного документа, чтобы можно было получить пиво и все его комментарии, не обращаясь к запросу просмотра на диске. Этот метод гарантирует, что даже при огромной загрузке трафика время отклика базы данных будет быстрым.
Вот иллюстрация:
Синий документ — это документ профиля пользователя, на который есть ссылки в нескольких других местах схемы. Каждый раз, когда у нас уже есть user_id, мы можем быстро просмотреть профиль пользователя. Так что ищите этот идентификатор в других документах JSON.
Желтый документ — это комментарий / рецензия на пиво. На нем есть идентификатор пива, но поиск по комментарию к пиву, на котором он находится, будет редким. Более типично у вас будет пиво в руках, и вы захотите посмотреть отзывы. Ранее мы упоминали, что в нашем гипотетическом примере мы рассматриваем этот раздел как критический для производительности, поэтому, хотя было бы возможно использовать представления для просмотра отзывов по beer_id, в этом случае мы хотим выполнить поиск через интерфейс значения ключа. Работая с этим интерфейсом, мы получаем преимущество в скорости в памяти, а также в большей масштабируемости, поскольку эти запросы используют меньше ресурсов сервера.
Зеленый документ — это настоящий пивной документ. У нас есть встроенные рейтинги, хранящиеся под user_id, чтобы обеспечить ограничение, согласно которому каждый пользователь может оценивать каждое пиво только один раз. Для отзывов / комментариев мы ссылаемся на них из массива комментариев. Таким образом, код для извлечения всех данных, необходимых для отображения страницы пива, выглядит примерно так (Пример не на каком-либо конкретном языке программирования).
beer = couchbase.get("my-beer-id"); reviews = couchbase.multiget(beer.comments); profiles = couchbase.multiget(reviews.map{|review|review.user_id});
Тогда на странице будет достаточно данных для отображения информации обо всех отзывах о пиве, а также информации о пользователе, который оставил отзыв. Все это заняло только 3 запроса к базе данных, поэтому общее затраченное время должно составлять всего несколько миллисекунд, что будет быстрее, чем альтернативный стиль отправки сложного запроса в базу данных, чтобы он мог создать набор результатов и верни это.
Многоуровневое представление поверх вашей высокопроизводительной схемы
В структуре документа, который мы разработали для облегчения взаимодействия ключ-значение, много полезного. Несмотря на то, что мы не поддерживаем путь поиска ключевого значения для обнаружения пива с самым высоким рейтингом, поскольку рейтинги встроены непосредственно в документ, легко написать представление, которое ранжирует пиво по рейтингу. Запросы к этому представлению не будут такими же быстрыми, как прямой поиск значения ключа, но что-то вроде пива с самым высоким рейтингом может быть легко кэшировано, так что пользователи получают высокопроизводительную работу, даже если базовый индекс основан на диске, а не в -Память.
Вот карта, которая сортирует пиво по среднему рейтингу.
function(doc, meta) { if (doc.ratings) { var total = 0, count = 0; for (var user_id in doc.ratings) { total = total + doc.ratings[user_id]; count++ } emit(total/count, null); } }
Вы бы запросили об этом с помощью ? Убывающего = true, чтобы сначала получить пиво с самым высоким рейтингом.
Исходя из этого же базового набора данных, мы также можем предоставить конкретному пользователю способ найти все оставленные им отзывы. Это не обычная операция (в нашем примере приложения), поэтому не важно, чтобы она была невероятно быстрой. Поэтому было бы очень трудно поддерживать для этого путь поиска значения ключа. Например, если ваши пользователи редко будут пытаться найти все отзывы, которые они написали, то будет сложно поддерживать список идентификаторов отзывов, прикрепленных к каждому профилю пользователя. Вместо этого просто пометьте отзывы идентификатором пользователя и используйте индекс для поддержки запроса. Просмотр всех отзывов для пользователя X прост:
function(doc, meta) { if (doc.type == "comment" && doc.user_id) { emit(doc.user_id, null); } }
Вы запрашиваете это с помощью ? Key = 525, чтобы найти все отзывы, написанные пользователем с номером 525.
Вывод: дизайн для горячего пути, пусть гибкость NoSQL поможет с остальными
Надеюсь, эта статья нарисовала достаточно ясную картину о том, как спроектировать ваше приложение для критических разделов производительности. Самое большое, что вы можете сделать, это настроить свою схему так, чтобы страницы, которые должны быть самыми быстрыми, были наиболее простыми для загрузки из базы данных. Конечно, это означает, что загрузка некритических страниц будет не такой быстрой, так как схема не предназначена специально для их быстрой загрузки.
Однако представления Couchbase позволяют легко переназначить данные для других ваших шаблонов доступа. Надеемся, что приведенные выше примеры показывают, как вы можете выполнять по существу один и тот же тип запроса двумя совершенно разными способами, в зависимости от ограничений производительности.
Бонус: на кеширование просмотров
Если ваш горячий путь включает в себя запрос на просмотр (например, на домашней временной шкале социальной сети), вы должны его кэшировать. Это означает, что, хотя первый запрос может занять несколько миллисекунд, последующие запросы будут иметь стабильно высокую производительность. Это более типично для использования memcached с mysql. В этом шаблоне memcached используется как для ускорения воспринимаемой производительности, так и для сохранения нагрузки на базу данных. По сути, мы говорим здесь об одном и том же, за исключением того, что вместо того, чтобы помещать результаты медленного запроса MySQL в memcached, мы помещаем результаты запроса представления Couchbase в область памяти.
Couchbase TTL может справиться с истечением кеша за вас. Или можно использовать более продвинутые стратегии аннулирования кэша (история на другой день …)