Статьи

Как перейти с MySQL на Couchbase Server 2.0: часть 2

Примечание куратора: содержание этой статьи первоначально было написано MC Brown в блоге Couchbase.

В моей последней части мы рассмотрели базовую механику MySQL и Couchbase Server 2.0 и сравнили способы, которыми вы будете моделировать данные, и как работают базовые запросы и списки записей, от оператора MySQL SQL, до Couchbase. Просмотр сервера.

На этот раз мы начнем с рассмотрения того, как записать эти представления и конкретные элементы SQL-запросов, таких как предложения WHERE и GROUP BY, и процесс фактической миграции ваших данных и логики приложения на Couchbase Server.

Подумайте о запросах, которые вы хотите выполнить

Как мы уже видели, запрос к базе данных Couchbase Server действительно двухэтапный процесс. На первом этапе создается представление ваших данных, которое поддерживает поиск или выбор документов в вашей базе данных. Вторая половина — это URL-адрес и ключевые значения, которые вы хотите выбрать из представления. Это означает, что, думая о том, как вы хотите получить информацию из своей базы данных Couchbase, вам нужно подумать о том, как вы хотите искать данные, и это позволит вам определить структуру представления, которое необходимо записать.

Couchbase Server использует представления для создания списков документов из базы данных, а выходные данные представления представляют собой ключ и соответствующее значение. И ключи, и значения могут быть любыми значениями JSON. Ключ в выводе важен, потому что он лежит в основе механизма поиска, сортировки и разбиения на страницы.

Например, функция просмотра, которая просто выводит все документы с рецептами в базу данных, используя заголовок рецепта в качестве ключа, может выглядеть так:

function(doc) {
    if (doc.type === 'recipe' &&
        doc.title !== null) {
        emit(doc.title, null);
    }
} 

Это называется функцией карты — она ​​отображает информацию из документов в формат, который вы хотите использовать. Необязательный второй шаг называется функцией Reduce и аналогичен функциям агрегации MySQL и предложению GROUP BY.

При переходе с MySQL построение ваших представлений в соответствии с информацией, которую вы хотите вывести или запросить, также может повлиять на то, как вы храните информацию. Вернемся к примеру с рецептом. В MySQL вы должны использовать запрос к таблице ингредиентов для сравнения с морковью и получить список соответствующих рецептов. Например, в качестве запроса SQL вы можете выразить это как:

SELECT recipe.id, recipe.title FROM ingredients join (recipe) on
    (ingredients.recipeid = recipe.id) where ingredient = 'carrot'

В Couchbase одним из способов достижения того же результата является написание представления, которое выводит одну строку для каждого ингредиента из нашего документа рецепта, например:

function(doc) {
    if (doc.type == 'recipe' &&
        doc.title !== null) {
        for id in doc.ingredients {
              emit(doc.ingredients[id].ingredient, null);
         }
    }
} 

Для запроса необходимо указать ключ из выходных данных, который вы хотите сопоставить. Запросы выполняются через REST API или через вашу клиентскую библиотеку, но спецификация такая же. Например, если вы хотите найти «морковь», вы должны указать это как ключ, который вы ищете. Использование REST API:

http://localhost:8092/recipes/_design/dev_ingred/_view/byingred?key=%22carrots%22&connection_timeout=60000&limit=10&skip=0

Помните, что ключом является любое значение JSON, поэтому значения ключей, которые вы вводите в запрос, также должны быть правильно закодированы в JSON. Как видно из этого примера, представление обеспечивает основу вашего запроса, а база данных обращается к механизму выбора.

Использование представлений таким образом означает, что большинство приложений будут построены с несколькими одновременными представлениями. Процесс построения ваших представлений на самом деле ничем не отличается от построения ваших основных запросов в приложении, а затем оптимизации запроса и индексов, которые выдают результат. Основное отличие состоит в том, что определение представления хранится в базе данных, а не в виде запроса в коде вашего приложения.

Скопления

В MySQL агрегация используется в разных местах. Агрегация обрабатывается комбинацией предложения GROUP BY и функций, которые собирают или суммируют информацию. Используя наш пример рецепта снова, мы могли бы выполнить запрос, который предоставляет счет различных рецептов по ингредиентам, которые они содержат. Например, такой запрос:

SELECT ingredient,count(recipeid) FROM ingredients GROUP BY ingredient

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

Например, чтобы получить эквивалент вышеупомянутого, мы можем использовать ту же функцию карты, что и выше. Затем мы можем использовать одну из встроенных функций приведения, _count, которая будет производить подсчет, основанный на количестве уникальных ключей, выводимых функцией emit (). Если существует функция сокращения, она используется автоматически при обращении к представлению. Доступ к представлению дает список количества рецептов для отдельных ингредиентов:

{"rows":[
{"key":"banana","value":20},
{"key":"bananas","value":33},
{"key":"baps","value":1},
{"key":"barbecue sauce","value":1},
{"key":"basmati rice","value":16},
{"key":"bay leaf","value":58},
{"key":"bay leaves","value":35},
{"key":"bean sprouts","value":18},
{"key":"bean thread noodles","value":1},
{"key":"beef braising steak","value":17}
]
} 

Агрегации и функции сокращения можно использовать и для других операций. Например, в части 1 я показал рецепт с информацией о сроках приготовления и приготовления. Мы могли бы использовать карту и уменьшить функцию вместе, чтобы получить общее время приготовления для каждого рецепта. Сначала напишите функцию карты, которая выводит заголовок рецепта в качестве ключа и информацию о времени приготовления в качестве значения:

function (doc, meta) {
  if (doc.title) {
    emit(doc.title, parseInt(doc.preptime));
    emit(doc.title, parseInt(doc.cooktime));
  }
}

Мы гарантируем, что выводимое значение является числом (используя функцию JavaScript parseInt ()).
Использование встроенной функции _sum приведет к тому, что представление вычислит сумму всех значений для данного ключа (наш заголовок рецепта):
{"rows":[
{"key":"Apple pie","value":55},
{"key":"Apricot cheesecake","value":70},
{"key":"Barbecued aubergine","value":10},
{"key":"Barbecued beefburgers","value":30},
{"key":"Barbecued corn on the cob slices","value":13},
{"key":"Beef in red wine","value":144},
{"key":"Blue cheese and tomato dip","value":5},
{"key":"Cajun chicken","value":20},
{"key":"Caribbean cobbler stew","value":95},
{"key":"Chappati or roti","value":45}
]

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

Запросы диапазона

Более сложные запросы, когда вы выбираете из нескольких полей, — это случай создания подходящего представления и последующего использования ключевого аргумента запроса для указания того, что вы ищете. Если вы хотите извлечь информацию для диапазона значений, вы можете использовать клавиши запуска и окончания, чтобы указать диапазон, который будет возвращен представлением. Например, построение представления со временем приготовления в качестве ключа позволит вам вывести список всех подходящих рецептов, где время приготовления составляло от 5 до 25 минут:

<span style="font-size: 16px; ">http://localhost:8092</span>/recipes/_design/recipes/_view/by_cooktime?startkey=%225%22&endkey=%2225%22

Это аналогично:

SELECT title FROM recipe WHERE cooktime >= 5 AND cooktime <= 25

Запросы на нескольких полях

Чтобы выполнить запрос к нескольким полям, вам необходимо создать представление, которое выводит два (или более) поля, по которым вы хотите выполнить запрос. Например, при поиске рецептов довольно часто хочется искать рецепты с определенным ингредиентом, но также и в течение определенного периода времени. Когда вы открываете холодильник за 30 минут до того, как выходить на улицу и находить помидоры, вы хотите знать, что можно приготовить за 20 минут с помидорами. Для этого мы выводим представление, которое выдает ключ с ингредиентом, и время, необходимое для приготовления рецепта, например:

function (doc, meta) {
  if (doc.ingredients) {
    for (i=0; i < doc.ingredients.length; i++)                                                              {                                                                                                         if (doc.ingredients[i].ingredient != null)
      {
        emit([doc.ingredients[i].ingredient, parseInt(doc.totaltime)], null);  
      }
    }  
  }
}

Теперь, чтобы получить все рецепты приготовления помидоров за 20 минут, мы можем искать:
["tomatoes",20]

Или более явно:
http://localhost:8092/recipes/_design/dev_bytime/_view/byingredtime?full_set=true&key=%5B%22tomatoes%22%2C20%5D&stale=false&connection_timeout=60000&limit=10&skip=0

Конечно, если у вас есть 20 минут, чтобы приготовить рецепт, у вас есть менее 20 минут.
Таким образом, на самом деле мы можем использовать запрос диапазона с начальным ключом [«помидоры», 0] и конечным ключом [«помидоры», 20], и теперь мы получим все готовые рецепты в течение 20 минут, содержащие морковь:

{"total_rows":27446,"rows":[
{"id":"4997AFDE-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",5],"value":null},
{"id":"79C16D8A-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",5],"value":null},
{"id":"CF52D23E-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",5],"value":null},
{"id":"42F5D520-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",6],"value":null},
{"id":"1A0AB61C-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",8],"value":null},
{"id":"4D0F9DF2-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",8],"value":null},
{"id":"50CE1676-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",10],"value":null},
{"id":"564A66CC-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",10],"value":null},
{"id":"95E9464A-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",10],"value":null},
{"id":"9A78477E-3027-11E2-BB0D-B67A7E241592","key":["tomatoes",10],"value":null}
]
}

Здорово! По сравнению с оператором SQL:

SELECT recipeid from recipes JOIN ingredients ON (ingredients.recipeid = recipe.recipeid) WHERE ingredients.ingredient = 'tomatoes' and totaltime <= 20

Кстати, обратите внимание, что мы получаем идентификатор каждого документа, чтобы мы могли загрузить документ, и, следовательно, рецепт. У нас может не быть заголовка рецепта в нашем выводе, но клиентские библиотеки могут автоматически загружать информацию документа для нас. Трудная часть выполняет поиск. 

Сортировка Просмотр вывода

Сортировка вывода представления происходит автоматически. Вывод каждого представления автоматически сортируется по ключу, созданному каждым представлением. Это необходимо, потому что без него невозможно было бы выполнить запрос диапазона. Это делает некоторые операции быстрыми и легкими. Если вы хотите вывести список рецептов в алфавитном порядке по заголовкам, вы должны создать представление, которое выводит заголовок рецепта в качестве ключа. Это означает, что функция карты:

function (doc, meta)
{
    emit(doc.title, null);
}

Доступ через вид как:

<span style="font-size: 16px; ">http://localhost:8092</span>/recipes/_design/recipes/_view/by_title

Аналогично:

SELECT title FROM recipe ORDER BY title

Чтобы получить список в порядке убывания в MySQL, используйте ключевое слово DESC:

SELECT title FROM recipe ORDER BY title DESC

В Couchbase Server вы используете нисходящий параметр для URL:

<span style="font-size: 16px; ">http://localhost:8092/recipes</span>/_design/recipes/_view/by_title?descending=true

пагинация

В MySQL разбиение на страницы обрабатывается с помощью комбинации предложений LIMIT и OFFSET, например, чтобы получить первые 10 записей из нашего запроса заголовка рецепта:

SELECT title FROM recipe ORDER BY title LIMIT 10

Чтобы получить следующие 10 записей:

SELECT title FROM recipe ORDER BY title LIMIT 10 OFFSET 10

С Couchbase Server вы используете аналогичную настройку, предоставляя аргументы limit и skip для запроса вида. Чтобы получить первые десять записей:

<span style="font-size: 16px; ">http://localhost:8092/recipes</span>/_design/recipes/_view/by_title?limit=10

И следующие 10 записей:

<span style="font-size: 16px; ">http://localhost:8092/recipes</span>/_design/recipes/_view/by_title?limit=10&skip=10

При работе с относительно короткими блоками страниц это работает хорошо. Для более длинных страниц или когда вы потенциально пропускаете большое количество строк вывода, вы можете использовать startkey_docid для пропуска вперед в представлении. Это более эффективно для этих более длинных сценариев нумерации страниц. Проверьте документацию для получения дополнительной информации. 

Загрузка основного объекта

В нашем примере базы данных рецептов в MySQL есть несколько таблиц, необходимых для загрузки информации базы данных об одном рецепте, чтобы ее можно было отобразить. В Couchbase Server, когда у вас есть идентификатор документа для данного рецепта, вы получаете доступ ко всей структуре записи рецепта, так как она была сохранена в базе данных Couchbase Server.

В MySQL необходимость упростить механизм загрузки для таких сложных объектов привела к появлению ряда различных вспомогательных проектов, которые могут использоваться для кэширования загруженных сложных данных, чтобы они были более доступными (например, memcached). Couchbase Server не избавляет от необходимости загружать информацию из базы данных, но делает загрузку записи рецепта одной операцией, а не несколькими запросами и форматированием этих данных в структуре, используемой для отображения.

Например, в SQL вы можете загружать данные рецепта, просматривая отдельные таблицы и создавая свой объект, выполняя ряд различных запросов:

SELECT * FROM recipe WHERE recipeid=23434
SELECT * FROM ingredients WHERE recipeid=23434
SELECT * FROM methods WHERE recipeid=23434
SELECT * FROM keywords WHERE recipeid=23434

Как только вы узнаете идентификатор документа, получение содержимого всего документа будет одним запросом к Couchbase Server. Например, если вы получаете идентификатор документа Aromaticroastchicken из нашего представления «по ингредиентам», я могу получить весь рецепт для отображения с помощью одного запроса GET с помощью клиентской библиотеки. Например, в PHP:

$recipe = couchbase->get('Aromaticroastchicken');

Результат — данные рецепта в формате JSON:

{
   "title" : "Aromatic roast chicken",
   "preptime" : "15",
   "servings" : "6",
   "totaltime" : "120",
   "subtitle" : "A couscous stuffing, and aromatic spices with fragrant lime and roasted garlic, turn a simple roast chicken into something special.",
   "cooktime" : "105",
   "keywords" : [
      "diet@dairy-free",
      "cook method.hob, oven, grill@oven",
      "diet@peanut-free",
      "special collections@cheffy recommended",
      "diet@corn-free",
      "special collections@weekend meal",
      "occasion@entertaining",
      "special collections@very easy",
      "diet@yeast-free",
      "diet@shellfish-free",
      "special collections@feeling spicy!",
      "main ingredient@poultry",
      "diet@demi-veg",
      "meal type@main",
      "diet@egg-free",
      "diet@cow dairy-free"
   ],
 "ingredients" : [
 ...
   ],
 "method" : [
 ...
   ],

}

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

Миграция данных

Форматирование данных поднимает еще один важный момент, когда я думал об этой миграции, когда я начинал процесс. Подумайте о ваших данных и о том, как они будут использоваться. Если вы отображаете информацию, которая в MySQL поступает из нескольких источников, то имеет смысл сгруппировать эту информацию вместе и сохранить ее в Couchbase Sever как один документ. 

Как уже упоминалось, многие механизмы ускорения, используемые в MySQL, работают на основе кэширования информации после ее загрузки из нескольких источников, чтобы ускорить загрузку таких сложных структур и информации.

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

Например, в нашей структуре рецептов нет смысла хранить ингредиенты в виде единого текстового поля и терять детализацию содержимого. Это похоже на размещение всего в одном текстовом столбце в MySQL и имеет те же ловушки; это затрудняет поиск и запрос содержимого, а также усложняет возможности просмотра и отображения.

Фактический процесс миграции должен быть довольно простым. Загрузите информацию из MySQL, создайте документ для каждого основного объекта в вашей базе данных, а затем запишите документы на Couchbase Server. Фактически, вы даже можете использовать этот метод для переноса информации в работающее приложение. Вместо этого попробуйте загрузить элемент из Couchbase, и, если он не существует, загрузить его из MySQL, а затем сохранить созданный объект в Couchbase. 

Документы в Couchbase хранятся по их идентификатору. Идентификатор может быть любой строкой, которая вам нравится, что означает, что вы можете использовать что-то идентифицируемое для вас или вашего приложения, не всегда полагаясь на представление, чтобы найти его. Чтобы использовать нашу постоянную базу данных рецептов, вы можете использовать название рецепта, например, «Рыбное рагу». Ограничение этого подхода (как и в случае с MySQL и уникальными индексами) заключается в том, что вы не можете хранить два документа с одинаковым идентификатором, и существует множество различных рецептов Fish Stew. В качестве альтернативы вы можете использовать UUID или заголовок рецепта с суффиксом, чтобы можно было использовать несколько рецептов «рыбного рагу».

Как и в случае с полем MySQL AUTO_INCREMENT, Couchbase автоматически сгенерирует UUID для каждого документа. Это не соблюдается; Вы можете хранить как документы с определенным идентификатором документа, так и автоматически сгенерированные идентификаторы в одной и той же базе данных, что означает, что вы можете использовать известные идентификаторы для хранения конкретной информации и автоматически сгенерированные идентификаторы для основной части данных.

Завершение

Основное различие между Couchbase и MySQL заключается в представлении данных и способах доступа к ним. Для определенных моделей данных Couchbase предоставляет более простое решение проблемы загрузки данных стиля одного объекта, которые мы видели в этих частях. Рецепты являются хорошим примером, когда ключевым элементом хранилища данных является рецепт в целом (наш документ). Возможность поиска и поиска данных рецепта и представления их в виде списка является требованием с точки зрения способа представления и доступа к данным, а не требованием, связанным с тем, как они хранятся.

Надеемся, что эти два поста дали вам некоторые идеи о том, как ваши данные могут быть перемещены с MySQL на Couchase Server, и как смоделировать и разработать код данных и приложений вашего Couchbase Server, чтобы система работала и работала так же, как ваша существующая инфраструктура на основе MySQL.