Статьи

Перемещение моего пива из Couchbase в MongoDB

Несколько дней назад я опубликовал в Twitter шутку :

Поэтому я решил перенести его из простой картины в реальный проект. Давайте посмотрим на две фазы этого так называемого проекта:

  • Перемещение данных из Couchbase в MongoDB
  • Обновление кода приложения для использования MongoDB

Посмотрите на этот скринкаст, чтобы увидеть его в действии:

Перемещение данных

Я создал сервер репликации, который использует протокол Couchbase XDCR, чтобы вывести документ и вставить его в MongoDB. Этот сервер использует проект Couchbase CAPI Server, доступный здесь .

Этот сервер получит все мутации, сделанные в Couchbase:

  • При вставке или обновлении документа отправляется полный документ
  • При удалении документа отправляются только медаты
  • Сервер replication server сохранит данные в MongoDB (вставляет и / или обновляет — не удаляет), а затем возвращает список в Couchbase как часть протокола XDCR.

Одна из проблем заключается в том, что у Couchbase нет понятия «типы» или «коллекции». Вы помещаете все в корзину, и код приложения знает, как обращаться с данными. Не обязательно проблема, просто выбор реализации, но сделать ее иногда сложнее, чем ожидалось, когда вы хотите написать инструменты. Итак, вот логика, которую я применяю на своем сервере репликации, чтобы организовать данные в несколько коллекций, когда это имеет смысл (и когда это возможно) :

  • Если документ JSON не содержит поле типа , все документы будут сохранены в одной коллекции
  • Если документ JSON содержит поле типа, для каждого типа будет создана коллекция, и документы будут вставлены / обновлены в этих коллекциях.
  • MongoDB не позволяет иметь атрибуты ключа. и знаки $, поэтому необходимо изменить имя на альтернативные символы. Это делается автоматически при копировании данных.

Все это и многое другое настраивается в инструменте.

Как вы можете видеть на скринкасте, это просто. (обратите внимание, что я тестировал только очень простые варианты использования и развертывания)

Вы можете скачать инструмент и исходный код здесь:

Обновление кода приложения

Следующим шагом является использование этих данных в приложении. Для этого я просто использую Java-приложение Beer Sample, доступное в репозитории Couchbase .

Я просто пересоздал проект и изменил несколько вещей, чтобы запустить приложение:

  • Изменить строку подключения
  • Удалить код, который генерирует представления
  • Заменить set / get операциями MongoDB
  • Замените вызов на представления простыми запросами

Код приложения MongoDBeer доступен здесь:

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Couchbase Query
View view = client.getView("beer", "by_name");
    Query query = new Query();
    query.setIncludeDocs(true).setLimit(20);
    ViewResponse result = client.query(view, query);
 
    ArrayList<HashMap<String, String>> beers =
      new ArrayList<HashMap<String, String>>();
    for(ViewRow row : result) {
      HashMap<String, String> parsedDoc = gson.fromJson(
        (String)row.getDocument(), HashMap.class);
 
      HashMap<String, String> beer = new HashMap<String, String>();
      beer.put("id", row.getId());
      beer.put("name", parsedDoc.get("name"));
      beer.put("brewery", parsedDoc.get("brewery_id"));
      beers.add(beer);
    }
    request.setAttribute("beers", beers);
 
 
// MongoDB Query
DBCursor cursor = db.getCollection("beer").find()
                                                   .sort( BasicDBObjectBuilder.start("name",1).get() )
                                                   .limit(20);
     ArrayList<HashMap<String, String>> beers =
             new ArrayList<HashMap<String, String>>();
     while (cursor.hasNext()) {
         DBObject row = cursor.next();
         HashMap<String, String> beer = new HashMap<String, String>();
         beer.put("id", (String)row.get("_id"));
         beer.put("name", (String)row.get("name"));
         beer.put("brewery", (String)row.get("brewery_id"));
         beers.add(beer);
     }
 
 
 
// Couchbase update
client.set(beerId, 0, gson.toJson(beer));
 
// MongoDB update
db.getCollection("beer").save(new BasicDBObject(beer));

Я не занимался оптимизацией кода MongoDB, а просто заменил как можно меньше строк кода.

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

Добавление новых функций

Как только у вас есть данные в MongoDB, вы можете сделать гораздо больше без чего-либо большего, чем MongoDB:

Полнотекстовый поиск

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
db.brewery.ensureIndex(
  {
    "name" : "text",
    "description" : "text"
  },
  {
    "weights" :
    {
      "name" : 10,
      "description" : 5
    },
    "name" : "TextIndex"
  }
 
);

Затем вы можете запросить базу данных, используя операцию $text , например, все пивоваренные заводы с Бельгией и без Але.

1
2
3
4
5
6
7
db.brewery.find( { "$text" : { "$search" : "belgium -ale" }   }  , { "name" : 1  } );
{ "_id" : "daas", "name" : "Daas" }
{ "_id" : "chimay_abbaye_notre_dame_de_scourmont", "name" : "Chimay (Abbaye Notre Dame de Scourmont)" }
{ "_id" : "brasserie_de_cazeau", "name" : "Brasserie de Cazeau" }
{ "_id" : "inbev", "name" : "InBev" }
{ "_id" : "new_belgium_brewing", "name" : "New Belgium Brewing" }
{ "_id" : "palm_breweries", "name" : "Palm Breweries" }

Немного аналитики

Не уверен, что эти запросы действительно имеют смысл, но это просто для того, чтобы показать, что теперь вы можете использовать свои документы без использования какого-либо стороннего инструмента.

Количество пива по категориям, от самого распространенного к меньшему:

01
02
03
04
05
06
07
08
09
10
11
12
db.beer.aggregate([
  {"$group" : { "_id" : "$category","count" : {"$sum" : 1 } } },
  {"$sort" : { "count" : -1 } },
  {"$project" : {  "category" : "$_id", "count" : 1, "_id" : 0 } }
]);
 
{ "count" : 1996, "category" : "North American Ale" }
{ "count" : 1468, "category" : null }
{ "count" : 564, "category" : "North American Lager" }
{ "count" : 441, "category" : "German Lager" }
...
...

Например, количество пива определенного ABV от пивоваренного завода: 3 лучших пивоварни с наибольшим количеством пива, у которых показатель выше или равен значению, скажем 5:

01
02
03
04
05
06
07
08
09
10
db.beer.aggregate([
... { "$match" : { "abv" : { "$gte" : 5 }  } },
... { "$group" : { "_id" : "$brewery_id", "count" : { "$sum" : 1} }},
... { "$sort" : { "count" : -1 } },
... { "$limit" : 3 }
... ])
 
{ "_id" : "midnight_sun_brewing_co", "count" : 53 }
{ "_id" : "troegs_brewing", "count" : 33 }
{ "_id" : "rogue_ales", "count" : 31 }

Геопространственные запросы

Первое, что нужно сделать с данными, это изменить структуру данных, чтобы сохранить различные данные в формате GeoJSON, для этого мы можем просто использовать скрипт в оболочке MongoDB:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
>mongo
 
use beers
 
db.brewery.find().forEach(
  function( doc ) {
    var loc = { type : "Point" };
    if (doc.geo && doc.geo.lat && doc.geo.lon) {
      loc.coordinates = [ doc.geo.lon , doc.geo.lat  ];
      db.brewery.update( { _id : doc._id } , {$set : { loc : loc } }  );
    }
  }
);
 
db.brewery.ensureIndex( { "loc" : "2dsphere" } );

Этот вызов берет все пивоваренные заводы и добавляет новый атрибут, имя loc в качестве точки GeoJSON. Я также мог удалить старую геоинформацию с помощью $ unset, но я этого не сделал; давайте представим, что некоторые API / приложения используют его. Это хороший пример гибкой схемы.

Теперь я могу искать все пивоваренные заводы, которые находятся менее чем в 30 км от Золотых Ворот в Сан-Франциско: [-122.478255,37.819929]

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
db.brewery.find(
  { "loc" :
    { "$near" :
      { "$geometry" :
        {
          "type" : "Point",
          "coordinates" : [-122.478255,37.819929]
        },
        "$maxDistance" : 20000
 
      }
    }
  }
  , { name : 1 }
)

Вы также можете использовать геопространственные индексы и операторы в запросах агрегации, использованных выше.

Вывод

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

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

Ссылка: Перемещение моего пива из Couchbase в MongoDB от нашего партнера по JCG Тугдуала Граля в блоге Tug’s Blog .