Драйвер Cassandra nodejs, как правильно обновить данные - PullRequest
0 голосов
/ 10 июля 2020

Я новичок в Cassandra и не совсем уверен, верна ли моя модель данных. Я попытался создать его на основе запросов, которые хочу сделать в своем приложении. Я хочу создавать и обновлять объекты книг, и я хочу найти книги по автору и по дате публикации sh. Я использую драйвер DataStax Node.js для Cassandra (используя Typescript), и вот моя схема:

CREATE TABLE IF NOT EXISTS books_by_author (
    author_id UUID,
    book_id UUID,
    book_name TEXT,
    date_published TIMESTAMP,
    PRIMARY KEY (author_id, date_published);

CREATE TABLE IF NOT EXISTS books (
    book_id uuid PRIMARY KEY,
    book_name text,
    book_description TEXT,
    date_published TIMESTAMP,
    author_id uuid,
    author_name TEXT,
 + many more columns for book details);

Сделав author_id и date_published в качестве первичного ключа, я смог делать запросы с nodejs драйвер и с помощью документации DataStax:

const q = cassandra.mapping.q;

const results = await this.bookMapper.find(
          {
            authorId: '1', datePublished: q.and(q.gte(start), q.lte(end)), // given timerange for publish date, works fine
          },
          docInfo,
          options);

Приведенный выше код работает хорошо; Я могу получить список книг по автору и указав диапазон дат при публикации. BookMapper отображает обе таблицы (books_by_author, books), поэтому я использую его для выполнения всех запросов к БД.

Затем я столкнулся с проблемами. Я создал книгу в своем приложении, но указал неверную дату публикации sh, и я хотел бы это изменить. Итак, чтобы увидеть, как это можно сделать, я создал модульный тест, который сохраняет книгу в БД, а затем пытается использовать bookMapper.update для обновления свойства книги datePublished. Вот псевдокод того, чего я пытался достичь:

const bookId = '123uuid';

const existingBook = new Book({
    id: bookId,
    name: 'The Book',
    datePublished: '2020-07-03T13:00:00.000Z',
    description: 'Book description',
    author: {
      id: '1',
      name: 'A. Author',
    }
});
... // insert existingBook to DB and read book details from DB using bookMapper.get({bookId})

const modifiedBook = new Book({
    id: bookId,
    name: 'The Book',
    datePublished: '2020-07-02T13:00:00.000Z', // modified publish date
    description: 'Modified book description', // modified the book description as well
    author: {
      id: '1',
      name: 'A. Author',
    }
});

await this.bookMapper.update(modifiedBook); // update the book

await this.bookMapper.get({bookId}); // returns the book with data from existingBook, not modifiedBook

await this.bookMapper.find(
          {
            authorId: '1', datePublished: q.and(q.gte(start), q.lte(end)),
          },
          docInfo,
          options); 
// query with author id, returns a list of 2 books, both the existingBook and modifiedBook ??

Как видите, обновление фактически создало новую строку книги в БД, и теперь у меня есть 2 книги вместо 1. А у меня нет идея, как правильно обновить эти данные. Я пытался использовать пакетную обработку:

let changes = [];
changes.push(this.bookMapper.batching.remove(exisitingBook));
changes.push(this.bookMapper.batching.insert(modifiedBook));
await this.mapper.batch(changes);

const book = await this.bookMapper.get({bookId});
--> book is null!

Использование пакетной обработки для удаления и вставки, похоже, работает, так что remove - это последний вызов БД, не имеет значения, в каком порядке я добавляю эти операторы в свой массив изменений , и он удаляет книгу, в результате чего мой последний оператор get возвращает значение null.

Я хотел использовать пакетную обработку, чтобы выполнить операцию atomi c. Я не хочу попадать в ситуацию, когда я сначала удаляю существующую книгу, а затем вставляю новую книгу в отдельные вызовы БД без пакетной обработки, потому что если после удаления, но до вставки произойдет какая-то ошибка, я потеряю данные моей книги из БД.

Мой вопрос: как правильно обновить данные книги, когда обновленное свойство оказывается частью первичного ключа? Спасибо.

1 Ответ

1 голос
/ 10 июля 2020

Это хорошо известная «особенность» Cassandra - в пакете оба оператора получают одинаковую временную метку, поэтому операция DELETE имеет преимущество перед операцией INSERT. Единственное решение для исправления этого - явное задание меток времени для каждой операции с меткой времени для DELETE ниже, чем INSERT. Я не Node.js разработчик, поэтому он должен выглядеть так, как он должен выглядеть в псевдокоде / CQL (Node.js mapper должен поддерживать установку пользовательской отметки времени для операторов):

TS=currentTimestampInMicroseconds
BEGIN BATCH
DELETE FROM table USING TIMESTAMP TS-1 WHERE PK = ... US;
INSERT INTO table (....) VALUES (....) USING TIMESTAMP TS;
APPLY BATCH;
...