Couchbase: как поддерживать массивы без повторяющихся элементов?

У нас есть магазин Couchbase, в котором есть данные о клиентах.

  • У каждого клиента есть ровно один документ в этой корзине.
  • Ежедневные транзакции приведут к обновлению этих данных клиента.

Образец документа. Давайте сосредоточимся на массиве purchased_product_ids.

{
  "customer_id" : 1000
  "purchased_product_ids" : [1, 2, 3, 4, 5 ] 
      # in reality this is a big array - hundreds of elements
  ... 
  ... many other elements ...
  ...
} 

Existing purchased_product_ids : 
    [1, 2, 3, 4, 5]

products purchased today : 
    [1, 2, 3, 6]  // 6 is a new entry, others existing already

Expected result after the update: 
    [1, 2, 3, 4, 5, 6]

Я использую Subdocument API, чтобы избежать передачи больших объемов данных между сервером и клиентами.

Option1 "arrayAppend":

customerBucket.mutateIn(customerKey)
    .arrayAppend("purchased_product_ids", JsonObject for [1,2,3,6] )
    .execute();

It results in duplicate elements. 
"purchased_product_ids" : [1, 2, 3, 4, 5, 1, 2, 3, 6]

Option2 "arrayAddUnique":

customerBucket.mutateIn(customerKey)
    .arrayAddUnqiue("purchased_product_ids", 1 )
    .arrayAddUnqiue("purchased_product_ids", 2 )
    .arrayAddUnqiue("purchased_product_ids", 3 )
    .arrayAddUnqiue("purchased_product_ids", 6 )
    .execute();

It throws exception for most of the times, 
because those elements already existing.

Есть ли лучший способ сделать это обновление?


person ramu    schedule 28.11.2018    source источник


Ответы (2)


Вы можете использовать N1QL и функции ARRAY_APPEND() и ARRAY_DISTINCT().

UPDATE customer USE KEYS "foo" 
SET purchased_product_ids = ARRAY_DISTINCT(ARRAY_APPEND(purchased_product_ids, 9))

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

Кроме того, если вы хотите добавить несколько элементов в массив одновременно, ARRAY_CONCAT() будет лучшим выбором. Подробнее здесь:

https://docs.couchbase.com/server/6.0/n1ql/n1ql-language-reference/arrayfun.html

person Johan Larson    schedule 28.11.2018
comment
спасибо Йохан. Кажется простым, если я использую маршрут N1QL вместо API SDK. Если я буду использовать N1QL вместо SDK, потеряю ли я что-нибудь в производительности или функциональности? Избегает ли это передачи данных с сервера на клиент туда и обратно, как это делают API-интерфейсы SubDocument? - person ramu; 28.11.2018
comment
Документ не будет передан клиенту, если вы запустите запрос формы, которую я использую выше. Выполнение происходит в механизме запросов. Механизм запросов должен будет извлечь документ с любого сервера, на котором он хранится, и записать его обратно туда, но это происходит внутри кластера. - person Johan Larson; 28.11.2018
comment
Если вы используете N1QL, вы также можете рассмотреть ARRAY_PUT(). docs.couchbase.com/server/6.0/n1ql/ n1ql-язык-ссылка/ - person vsr; 28.11.2018
comment
Вообще говоря, API SDK будет быстрее, чем маршрут N1QL. - person Paddy; 29.11.2018

Нужны ли для заказа Purchase_product_ids? Если нет, вы можете преобразовать его в карту, например.

{
  "customer_id" : 1000
  "purchased_product_ids" : {1: {}, 3: {}, 5: {}, 2: {}, 4: {}}
}

а затем напишите на эту карту с поддокументом, зная, что вы не будете конфликтовать (при условии, что идентификаторы продуктов уникальны):

customerBucket.mutateIn(customerKey)
   .upsert("purchased_product_ids.1", JsonObject.create()) // already exists
   .upsert("purchased_product_ids.6", JsonObject.create()) // new product
   .execute();

что приведет к:

{
  "customer_id" : 1000
  "purchased_product_ids" : {1: {}, 3: {}, 6: {}, 5: {}, 2: {}, 4: {}}
}

(Здесь я использовал JsonObject.create() в качестве заполнителя на тот случай, если вам нужно связать дополнительную информацию для каждого оплаченного заказа клиента, но вы также можете просто написать null. временная метка заказа, например 1: {date: <TIMESTAMP>}, а затем заказывать его в коде при получении.)

person Graham Pople    schedule 28.11.2018
comment
Итак, вы предлагаете использовать словари JSON вместо массивов JSON. Это решает мою проблему с обновлением данных. Только один вопрос: можем ли мы UNNEST словари в N1QL, как мы делаем массивы? (Большинство наших шаблонов запросов используют N1QL UNNEST). Пожалуйста, помогите, если есть способ сделать это со словарями. В противном случае эти данные не будут очень полезны, когда мы запросим их позже. - person ramu; 28.11.2018
comment
Я не уверен. Возможно, вместо того, чтобы изменять структуру данных, было бы проще избежать поддокумента и просто получить () и заменить () документ, используя CAS для обнаружения любых проблем параллелизма? - person Graham Pople; 28.11.2018
comment
Грэм, в моем случае, по сравнению с размером транзакции, размер документа клиента настолько велик. Таким образом, получение, обработка и замена приведут к перемещению гораздо большего объема данных (весь клиентский документ) дважды для каждой транзакции. Но изменения, которые я хочу внести, меньше, поэтому я выбрал поддок. Я попробую подход N1QL, предложенный Йоханом. - person ramu; 29.11.2018
comment
Верно, но если вы знаете идентификатор документа, то все же может быть быстрее (хотя, по общему признанию, более высокая пропускная способность) выполнить полное SDK документа «получить и заменить», чем запрос N1QL, даже с двумя сетевыми круговыми поездками, поскольку ваше приложение может отправлять запросы непосредственно к правильному узлу данных, а также не требуется синтаксический анализ индекса или запроса. Это также уменьшит нагрузку на узлы запросов. Если это критически важная часть приложения и задержка имеет решающее значение, возможно, стоит сравнить оба подхода. - person Graham Pople; 29.11.2018
comment
ты прав. Мне нужно проверить, как ведет себя каждый вариант при значительной нагрузке в системе. с несколькими записями это может ввести в заблуждение. - person ramu; 30.11.2018