Transações

Uma transação é um conjunto de operações em uma ou mais entidades. Todas as transações são atômicas, o que significa que elas nunca são aplicadas parcialmente. Ou todas as operações na transação são aplicadas ou nenhuma delas é aplicada.

Como usar transações

As transações expiram após 270 segundos ou se ficam inativas por 60 segundos.

Uma operação poderá falhar quando:

  • muitas modificações simultâneas forem tentadas na mesma entidade;
  • a transação exceder um limite de recursos;
  • o banco de dados do modo Datastore encontrar um erro interno.

Em todos esses casos, a API Datastore retorna um erro.

As transações são um recurso opcional. Usar transações para executar operações do banco de dados não é obrigatório.

Um aplicativo executa um conjunto de instruções e operações em uma única transação. Assim, caso alguma instrução ou operação gere uma exceção, não será aplicada nenhuma operação do banco de dados no conjunto. O aplicativo define as ações a serem executadas na transação.

Veja o snippet a seguir como realizar uma transação. A transação é uma transferência monetária de uma conta para outra.

C#

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para C#.

private void TransferFunds(Key fromKey, Key toKey, long amount)
{
    using (var transaction = _db.BeginTransaction())
    {
        var entities = transaction.Lookup(fromKey, toKey);
        entities[0]["balance"].IntegerValue -= amount;
        entities[1]["balance"].IntegerValue += amount;
        transaction.Update(entities);
        transaction.Commit();
    }
}

Go

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Go.

type BankAccount struct {
	Balance int
}

const amount = 50
keys := []*datastore.Key{to, from}
tx, err := client.NewTransaction(ctx)
if err != nil {
	log.Fatalf("client.NewTransaction: %v", err)
}
accs := make([]BankAccount, 2)
if err := tx.GetMulti(keys, accs); err != nil {
	tx.Rollback()
	log.Fatalf("tx.GetMulti: %v", err)
}
accs[0].Balance += amount
accs[1].Balance -= amount
if _, err := tx.PutMulti(keys, accs); err != nil {
	tx.Rollback()
	log.Fatalf("tx.PutMulti: %v", err)
}
if _, err = tx.Commit(); err != nil {
	log.Fatalf("tx.Commit: %v", err)
}

Java

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Java.

void transferFunds(Key fromKey, Key toKey, long amount) {
  Transaction txn = datastore.newTransaction();
  try {
    List<Entity> entities = txn.fetch(fromKey, toKey);
    Entity from = entities.get(0);
    Entity updatedFrom =
        Entity.newBuilder(from).set("balance", from.getLong("balance") - amount).build();
    Entity to = entities.get(1);
    Entity updatedTo = Entity.newBuilder(to).set("balance", to.getLong("balance") + amount)
        .build();
    txn.put(updatedFrom, updatedTo);
    txn.commit();
  } finally {
    if (txn.isActive()) {
      txn.rollback();
    }
  }
}

Node.js

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Node.js.

async function transferFunds(fromKey, toKey, amount) {
  const transaction = datastore.transaction();
  await transaction.run();
  const results = await Promise.all([
    transaction.get(fromKey),
    transaction.get(toKey),
  ]);
  const accounts = results.map(result => result[0]);

  accounts[0].balance -= amount;
  accounts[1].balance += amount;

  transaction.save([
    {
      key: fromKey,
      data: accounts[0],
    },
    {
      key: toKey,
      data: accounts[1],
    },
  ]);

  return transaction.commit();
}

PHP

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para PHP.

/**
 * Update two entities in a transaction.
 *
 * @param DatastoreClient $datastore
 * @param Key $fromKey
 * @param Key $toKey
 * @param $amount
 */
function transfer_funds(
    DatastoreClient $datastore,
    Key $fromKey,
    Key $toKey,
    $amount
) {
    $transaction = $datastore->transaction();
    // The option 'sort' is important here, otherwise the order of the result
    // might be different from the order of the keys.
    $result = $transaction->lookupBatch([$fromKey, $toKey], ['sort' => true]);
    if (count($result['found']) != 2) {
        $transaction->rollback();
    }
    $fromAccount = $result['found'][0];
    $toAccount = $result['found'][1];
    $fromAccount['balance'] -= $amount;
    $toAccount['balance'] += $amount;
    $transaction->updateBatch([$fromAccount, $toAccount]);
    $transaction->commit();
}

Python

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Python.

def transfer_funds(client, from_key, to_key, amount):
    with client.transaction():
        from_account = client.get(from_key)
        to_account = client.get(to_key)

        from_account['balance'] -= amount
        to_account['balance'] += amount

        client.put_multi([from_account, to_account])

Ruby

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Ruby.

def transfer_funds from_key, to_key, amount
  datastore.transaction do |tx|
    from = tx.find from_key
    from["balance"] -= amount
    to = tx.find to_key
    to["balance"] += amount
    tx.save from, to
  end
end

Para manter nossos exemplos mais sucintos, às vezes omitimos rollback se a transação falha. Em código de produção, é importante garantir que cada transação seja executada explicitamente ou revertida.

O que pode ser feito em uma transação

As transações podem consultar ou procurar qualquer número de entidades. É possível criar, atualizar ou excluir até 500 entidades em cada transação. O tamanho máximo de uma transação é de 10 MiB. É possível usar uma transação de leitura/gravação ou uma transação somente leitura.

Isolamento e consistência

Bancos de dados do modo Datastore implementam o isolamento serializável. Os dados lidos ou modificados por uma transação não podem ser modificados simultaneamente.

As consultas e pesquisas de uma transação analisam um instantâneo consistente do estado do banco de dados. Esse instantâneo contém o efeito de todas as transações e grava o que foi concluído antes do início da transação.

A visualização desse instantâneo consistente também se estende a leituras após gravações dentro de transações. Diferentemente da maioria dos bancos de dados, as consultas e pesquisas dentro de uma transação do Cloud Datastore não exibem os resultados de gravações anteriores dentro dessa transação. Especificamente, se uma entidade é modificada ou excluída em uma transação, uma consulta ou pesquisa retorna a versão original da entidade no início da transação ou nada, se a entidade não existia.

Fora das transações, as consultas e pesquisas também têm isolamento serializável.

Bloqueios de transação

Transações de leitura/gravação usam bloqueios de leitor/gravador para implementar o isolamento e a serialização. Quando duas transações de leitura/gravação simultâneas leem ou gravam os mesmos dados, o bloqueio retido por uma pode atrasar a outra.

Se sua transação não exigir gravações, será possível melhorar o desempenho e evitar a contenção com outras transações usando uma transação somente leitura.

Diferença do Cloud Datastore

No Cloud Datastore, a versão anterior do Firestore no modo Datastore, as transações usavam o controle de simultaneidade otimista. As transações não atrasavam outras transações, mas eram mais propensas a denunciar a contenção na confirmação. Para mais informações sobre transações na versão anterior, consulte Transações do Cloud Datastore.

Usos em transações

Um dos usos das transações é a atualização de uma entidade com um novo valor de propriedade relativo ao seu valor atual. O exemplo transferFunds acima faz isso para duas entidades, retirando dinheiro de uma conta e transferindo-o para outra. A API Datastore não tenta repetir transações automaticamente, mas é possível adicionar sua própria lógica para repeti-las, por exemplo, para lidar com conflitos quando outra solicitação atualiza a mesma entidade ao mesmo tempo.

C#

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para C#.

/// <summary>
/// Retry the action when a Grpc.Core.RpcException is thrown.
/// </summary>
private T RetryRpc<T>(Func<T> action)
{
    List<Grpc.Core.RpcException> exceptions = null;
    var delayMs = _retryDelayMs;
    for (int tryCount = 0; tryCount < _retryCount; ++tryCount)
    {
        try
        {
            return action();
        }
        catch (Grpc.Core.RpcException e)
        {
            if (exceptions == null)
                exceptions = new List<Grpc.Core.RpcException>();
            exceptions.Add(e);
        }
        System.Threading.Thread.Sleep(delayMs);
        delayMs *= 2;  // Exponential back-off.
    }
    throw new AggregateException(exceptions);
}

private void RetryRpc(Action action)
{
    RetryRpc(() => { action(); return 0; });
}

[Fact]
public void TestTransactionalRetry()
{
    int tryCount = 0;
    var keys = UpsertBalances();
    RetryRpc(() =>
    {
        using (var transaction = _db.BeginTransaction())
        {
            TransferFunds(keys[0], keys[1], 10, transaction);
            // Insert a conflicting transaction on the first try.
            if (tryCount++ == 0)
                TransferFunds(keys[1], keys[0], 5);
            transaction.Commit();
        }
    });
    Assert.Equal(2, tryCount);
}

Go

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Go.

type BankAccount struct {
	Balance int
}

const amount = 50
_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
	keys := []*datastore.Key{to, from}
	accs := make([]BankAccount, 2)
	if err := tx.GetMulti(keys, accs); err != nil {
		return err
	}
	accs[0].Balance += amount
	accs[1].Balance -= amount
	_, err := tx.PutMulti(keys, accs)
	return err
})

Java

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Java.

int retries = 5;
while (true) {
  try {
    transferFunds(fromKey, toKey, 10);
    break;
  } catch (DatastoreException e) {
    if (retries == 0) {
      throw e;
    }
    --retries;
  }
}
// Retry handling can also be configured and automatically applied using google-cloud-java.

Node.js

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Node.js.

async function transferFundsWithRetry() {
  const maxTries = 5;

  async function tryRequest(currentAttempt, delay) {
    try {
      await transferFunds(fromKey, toKey, 10);
    } catch (err) {
      if (currentAttempt <= maxTries) {
        // Use exponential backoff
        setTimeout(async () => {
          await tryRequest(currentAttempt + 1, delay * 2);
        }, delay);
      }
      throw err;
    }
  }

  await tryRequest(1, 100);
}

PHP

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para PHP.

$retries = 5;
for ($i = 0; $i < $retries; $i++) {
    try {
        transfer_funds($datastore, $fromKey, $toKey, 10);
    } catch (Google\Cloud\Exception\ConflictException $e) {
        // if $i >= $retries, the failure is final
        continue;
    }
    // Succeeded!
    break;
}

Python

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Python.

for _ in range(5):
    try:
        transfer_funds(client, account1.key, account2.key, 50)
        break
    except google.cloud.exceptions.Conflict:
        continue
else:
    print('Transaction failed.')

Ruby

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Ruby.

(1..5).each do |i|
  begin
    return transfer_funds from_key, to_key, amount
  rescue Google::Cloud::Error => e
    raise e if i == 5
  end
end

Isso requer uma transação porque o valor de balance em uma entidade pode ser atualizado por outro usuário após esse código buscar o objeto, mas antes de salvar o objeto modificado. Sem uma transação, a solicitação do usuário usa o valor de balance antes da atualização do outro usuário, e a gravação substitui o novo valor. Com uma transação, o aplicativo é informado sobre a atualização.

Outro uso comum das transações é buscar uma entidade com uma chave nomeada, ou criá-la, caso ela ainda não exista (este exemplo é baseado no exemplo da TaskList de como criar uma entidade):

C#

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para C#.

Entity task;
using (var transaction = _db.BeginTransaction())
{
    task = transaction.Lookup(_sampleTask.Key);
    if (task == null)
    {
        transaction.Insert(_sampleTask);
        transaction.Commit();
    }
}

Go

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Go.

_, err := client.RunInTransaction(ctx, func(tx *datastore.Transaction) error {
	var task Task
	if err := tx.Get(key, &task); err != datastore.ErrNoSuchEntity {
		return err
	}
	_, err := tx.Put(key, &Task{
		Category:    "Personal",
		Done:        false,
		Priority:    4,
		Description: "Learn Cloud Datastore",
	})
	return err
})

Java

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Java.

Entity task;
Transaction txn = datastore.newTransaction();
try {
  task = txn.get(taskKey);
  if (task == null) {
    task = Entity.newBuilder(taskKey).build();
    txn.put(task);
    txn.commit();
  }
} finally {
  if (txn.isActive()) {
    txn.rollback();
  }
}

Node.js

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Node.js.

async function getOrCreate(taskKey, taskData) {
  const taskEntity = {
    key: taskKey,
    data: taskData,
  };
  const transaction = datastore.transaction();

  try {
    await transaction.run();
    const [task] = await transaction.get(taskKey);
    if (task) {
      // The task entity already exists.
      await transaction.rollback();
    } else {
      // Create the task entity.
      transaction.save(taskEntity);
      transaction.commit();
    }
    return taskEntity;
  } catch (err) {
    await transaction.rollback();
  }
}

PHP

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para PHP.

$transaction = $datastore->transaction();
$existed = $transaction->lookup($task->key());
if ($existed === null) {
    $transaction->insert($task);
    $transaction->commit();
}

Python

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Python.

with client.transaction():
    key = client.key('Task', datetime.datetime.utcnow().isoformat())

    task = client.get(key)

    if not task:
        task = datastore.Entity(key)
        task.update({
            'description': 'Example task'
        })
        client.put(task)

    return task

Ruby

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Ruby.

task = nil
datastore.transaction do |tx|
  task = tx.find task_key
  if task.nil?
    task = datastore.entity task_key do |t|
      t["category"] = "Personal"
      t["done"] = false
      t["priority"] = 4
      t["description"] = "Learn Cloud Datastore"
    end
    tx.save task
  end
end

Como antes, uma transação é necessária para lidar com a situação em que outro usuário tente criar ou atualizar uma entidade com o mesmo código de string. Sem uma transação, se a entidade não existir e dois usuários tentarem criá-la, a segunda substituirá a primeira sem que se saiba que isso aconteceu.

Quando uma transação falha, é possível fazer com que o app repita a transação até ter êxito, ou é possível deixar que os usuários lidem com o erro propagando-o até o nível de interface do usuário do app. Não é necessário criar um ciclo de repetições em torno de cada transação.

Transações somente leitura

Por fim, é possível usar uma transação para ler um instantâneo consistente do banco de dados. Isso pode ser útil quando forem necessárias várias leituras para renderizar uma página ou para exportar dados que precisam ser consistentes. É possível criar uma transação somente leitura para esses casos.

As transações somente leitura não podem modificar entidades, mas, em contrapartida, não entram em conflito com outras transações e não precisam ser repetidas. Se alguma transação regular de leitura/gravação executar apenas leituras, essa transação pode entrar em conflito com transações que modificam os mesmos dados.

C#

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para C#.

Entity taskList;
IReadOnlyList<Entity> tasks;
using (var transaction = _db.BeginTransaction(TransactionOptions.CreateReadOnly()))
{
    taskList = transaction.Lookup(taskListKey);
    var query = new Query("Task")
    {
        Filter = Filter.HasAncestor(taskListKey)
    };
    tasks = transaction.RunQuery(query).Entities;
    transaction.Commit();
}

Go

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Go.

tx, err := client.NewTransaction(ctx, datastore.ReadOnly)
if err != nil {
	log.Fatalf("client.NewTransaction: %v", err)
}
defer tx.Rollback() // Transaction only used for read.

ancestor := datastore.NameKey("TaskList", "default", nil)
query := datastore.NewQuery("Task").Ancestor(ancestor).Transaction(tx)
var tasks []Task
_, err = client.GetAll(ctx, query, &tasks)

Java

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Java.

Entity taskList;
QueryResults<Entity> tasks;
Transaction txn = datastore.newTransaction(
    TransactionOptions.newBuilder()
        .setReadOnly(ReadOnly.newBuilder().build())
        .build()
);
try {
  taskList = txn.get(taskListKey);
  Query<Entity> query = Query.newEntityQueryBuilder()
      .setKind("Task")
      .setFilter(PropertyFilter.hasAncestor(taskListKey))
      .build();
  tasks = txn.run(query);
  txn.commit();
} finally {
  if (txn.isActive()) {
    txn.rollback();
  }
}

Node.js

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Node.js.

async function getTaskListEntities() {
  const transaction = datastore.transaction({readOnly: true});
  try {
    const taskListKey = datastore.key(['TaskList', 'default']);

    await transaction.run();
    const [taskList] = await transaction.get(taskListKey);
    const query = datastore.createQuery('Task').hasAncestor(taskListKey);
    const [taskListEntities] = await transaction.runQuery(query);
    await transaction.commit();
    return [taskList, taskListEntities];
  } catch (err) {
    await transaction.rollback();
  }
}

PHP

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para PHP.

$transaction = $datastore->readOnlyTransaction();
$taskListKey = $datastore->key('TaskList', 'default');
$query = $datastore->query()
    ->kind('Task')
    ->hasAncestor($taskListKey);
$result = $transaction->runQuery($query);
$taskListEntities = [];
/* @var Entity $task */
foreach ($result as $task) {
    $taskListEntities[] = $task;
}

Python

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Python.

with client.transaction(read_only=True):
    task_list_key = client.key('TaskList', 'default')

    task_list = client.get(task_list_key)

    query = client.query(kind='Task', ancestor=task_list_key)
    tasks_in_list = list(query.fetch())

    return task_list, tasks_in_list

Ruby

Para aprender a instalar e usar a biblioteca de cliente para o Cloud Datastore, consulte as bibliotecas de cliente do Cloud Datastore. Para mais informações, consulte a documentação de referência da API do Cloud Datastore para Ruby.

task_list_key = datastore.key "TaskList", "default"
datastore.read_only_transaction do |tx|
  task_list = tx.find task_list_key
  query = datastore.query("Task").ancestor(task_list)
  tasks_in_list = tx.run query
end

A seguir