Автоматическая метка времени созданного (не обновленного) времени при вставке (не обновлении) в Google Spanner - PullRequest
0 голосов
/ 09 января 2019

Насколько я могу судить, не существует безопасного способа (в параллельной среде) сделать это, но я хотел убедиться, что ничего не пропустил.

Часто в наших БД нам нравится отслеживать, когда строка была изначально создана и когда она последний раз обновлялась. По отдельности. Это не столбец «made_at», который на самом деле должен называться «updated_on».

В Spanner, с отметками времени коммита (или даже просто с указанием текущего времени), updated_on легко. Тем не менее, обычные инструменты, которые я использую для create_on:

  • значения по умолчанию, и никогда не обновлять
  • на дубликат ключа ...
  • 1012 * триггеры *

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

Самое близкое, что я могу предложить, это не совсем странно, это попробовать вставить мутацию, поймать исключение, проверить ErrorCode.ALREADY_EXISTS, а затем обновить. И только установите create_on в блок вставки. Уродливо ... а также не очень безопасно перед лицом одновременного удаления (вставляешь, ловишь ошибку, кто-то удаляет между ними, пытается обновить, бум)

Есть другие предложения? Желательно через SDK?

Ответы [ 2 ]

0 голосов
/ 23 января 2019

Обратите внимание, что, как поясняется в моем комментарии, я искал что-то чистое, а не транзакцию, поскольку способ работы транзакций немного уродлив (интерфейс должен по крайней мере иметь лямбды в качестве опции, а не анонимные классы: / .) Или лучше, просто beginTransaction (), endTransaction (), например :

            this.dbClient.readWriteTransaction()
            .run(
                new TransactionCallable<Void>() {
                    @Nullable
                    @Override
                    public Void run(TransactionContext transactionContext) throws Exception {
                        Struct row = transactionContext.readRow(
                            MY_TABLE,
                            Key.of(
                                keyCol1,
                                keyCol2
                            ),
                            Collections.singletonList(keyCol1)
                        );
                        //this creates a write builder with a bunch of columns
                        //set to mutated, except for CREATED_ON
                        WriteBuilder writeBuilder = updateBuilder(
                            Mutation.newInsertBuilder(MY_TABLE),
                            myDataModel
                        );
                        if(row == null) {
                            writeBuilder.set(CREATED_ON).to(Timestamp.now()).build();
                        }
                        Mutation recMut =
                            updateBuilder(Mutation.newUpdateBuilder(MY_TABLE), myDataModel).build();
                        transactionContext.buffer(recMut);
                        return null;
                    }
                }
            );

@ RedPandaCurious - это правильно, ответ Скотта только наполовину работает: (1) обречен на провал, по причинам, изложенным в вопросе, или иначе, просто повторяет то, что я хочу достичь, не иллюстрируя, как ( 2) просто повторяет мой последующий комментарий, не предоставляя никаких подробностей или документов.

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

ОК, получается, что если вы удалите аннотацию @Nullable, вы можете использовать лямбда-выражения и, с помощью небольшого дополнительного перефакторинга, уменьшить это значение до:

 /**
 * Lambda interface for factoring out transactional execution logic
 */
 public interface SpannerOperation {
      Boolean doOperation(TransactionContext ctxt, Struct row);
  }

  private Boolean executeIfExists(
        ... key fields ...
        SpannerOperation spannerOperationIfExists,
        SpannerOperation spannerOperationifNotExists,
        Iterable<String> columns
    ) {
        return this.dbClient.readWriteTransaction().run(
            transactionContext -> {
                Struct row = transactionContext.readRow(
                    MY_TABLE,
                    Key.of(...), //you could even pass the key in as a key
                    columns
                );
                if(row != null) {
                    spannerOperation.doOperation(transactionContext, row);
                    return true;
                } else {
                   spannerOperationifNotExists.doOperation(transactionContext, null);
                    return false;
                }
            }
        );
    }

     public boolean doSomething(... keyStuff.. )
        return this.executeIfExists(
            .. key fields ...
            (ctxt, row) -> {
                Mutation mut = Mutation
                    .newUpdateBuilder(MY_TABLE)
                    .....//as you like it...
                    .build()
                ctxt.buffer(mut);
                return true;
            },
            (ctxt, row) -> false, //set created_on or whatever
            Collections.singleton(..some column you want to read in..)
        );

Обратите внимание, что это также работает для таких вещей, как добавление в список и т. Д., И все это сводится к тому, что вам нужно. Google действительно нужен метод ifExists () - я использовал его в нескольких местах ..;)

0 голосов
/ 09 января 2019

Я могу придумать два возможных решения для этого:

  1. Вы можете добавить два столбца, один для созданного_о дня и один для обновленного_она. При вставке строки установите для selected_at и updated_on местозаполнитель spanner.commit_timestamp (). При обновлении строки измените только updated_on на spanner.commit_timestamp ().

  2. Создать транзакцию для инкапсуляции мутации. В одной транзакции вы можете:

    • Чтение из таблицы, чтобы проверить, существует ли строка
    • Если строка уже существует, обновите строку
    • Если строка не существует, вставьте строку

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

Более подробную информацию о отметках времени коммита можно найти здесь: https://cloud.google.com/spanner/docs/commit-timestamp

...