Transacciones

Una transacción es un conjunto de operaciones en una o más entidades. Cada transacción siempre es atómica, lo que significa que las transacciones nunca se aplicarán de forma parcial. Se aplican todas las operaciones de la transacción o ninguna de ellas.

Usa transacciones

Las transacciones tienen una duración máxima de 60 segundos, con un tiempo de vencimiento por inactividad de 10 segundos una vez transcurridos 30 segundos.

Las operaciones pueden fallar cuando ocurren las siguientes situaciones:

  • Se intentan demasiadas modificaciones simultáneas en la misma entidad.
  • La transacción supera un límite de recursos.
  • La base de datos en modo Datastore encuentra un error interno.

En todos estos casos, la API de Cloud Datastore muestra un error.

Las transacciones son una característica opcional. No es obligatorio que uses las transacciones para realizar operaciones de bases de datos.

Una aplicación puede ejecutar un conjunto de instrucciones y operaciones en una sola transacción, de forma que, si alguna operación o instrucción genera una excepción, no se aplique ninguna de las operaciones de bases de datos en el conjunto. La aplicación define las acciones que se deben realizar en la transacción.

En el siguiente fragmento, se muestra cómo realizar una transacción. Transfiere dinero de una cuenta a otra.

C#

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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

Ten en cuenta que, para que nuestros ejemplos sean más concisos, a veces omitimos rollback si la transacción falla. En el código en producción, es importante garantizar que todas las transacciones se confirmen o reviertan de manera explícita.

¿Qué se puede hacer en una transacción?

Las transacciones pueden realizar consultas o búsquedas sobre cualquier cantidad de entidades. Puedes crear, actualizar o borrar hasta 500 entidades en cada transacción. El tamaño máximo de una transacción es de 10 MiB.

Aislamiento y coherencia

El aislamiento serializable se aplica en las bases de datos en modo Datastore. Esto significa que los datos que lee o modifica una transacción no pueden modificarse de forma simultánea. Para obtener más información sobre los niveles de aislamiento, lee los artículos sobre aislamiento serializable y aislamiento de transacciones.

Las consultas y las búsquedas en una transacción acceden a una instantánea coherente del estado de la base de datos. Se garantiza que esta instantánea contendrá el efecto de todas las transacciones y escrituras que se completaron antes del comienzo de la transacción.

La vista de esta instantánea coherente también se extiende a las lecturas posteriores a las escrituras dentro de las transacciones. A diferencia de la mayoría de las bases de datos, las consultas y búsquedas realizadas dentro de una transacción en modo Datastore no ven los resultados de escrituras anteriores dentro de esa transacción. En particular, si se modifica o borra una entidad dentro de una transacción, una consulta o una búsqueda muestran la versión original de la entidad como estaba al principio de la transacción, o no muestra nada si la entidad no existía en ese entonces.

Fuera de las transacciones, las consultas y las búsquedas también tienen aislamiento serializable.

Usos de las transacciones

Uno de los usos de las transacciones es la actualización de una entidad con un nuevo valor de propiedad relativo a su valor actual. El ejemplo de transferFunds anterior lo hace para dos entidades mediante la extracción de dinero de una cuenta y su transferencia a otra. La API de Cloud Datastore no intenta realizar otra transacción de forma automática, pero puedes agregar tu propia lógica para volver a intentarlo, por ejemplo, si necesitas controlar conflictos cuando otra solicitud actualiza la misma entidad al mismo tiempo.

C#

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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

Esto requiere una transacción porque otro usuario podría actualizar el valor de balance en una entidad después de que este código recupere el objeto, pero antes de que lo guarde modificado. Sin una transacción, la solicitud del usuario usa el valor de balance antes de la actualización del otro usuario, y el guardado reemplaza el valor nuevo. Mediante una transacción, se informa a la app sobre la actualización del otro usuario.

Las transacciones también suelen usarse para recuperar una entidad con una clave con nombre o crearla si aún no existe (este ejemplo se basa en el de TaskList de Crea una entidad):

C#

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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.
      transaction.rollback();
    } else {
      // Create the task entity.
      transaction.save(taskEntity);
      transaction.commit();
    }
    return taskEntity;
  } catch (err) {
    transaction.rollback();
  }
}

PHP

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de Cloud Datastore para PHP.

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

Python

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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, la transacción es necesaria para abordar el caso en el que otro usuario intente crear o actualizar una entidad con el mismo ID de string. Sin una transacción, si la entidad no existe y dos usuarios intentan crearla, el segundo reemplazará al primero sin saberlo.

Cuando una transacción falla, puedes hacer que tu app vuelva a intentarla hasta que tenga éxito, o bien, puedes dejar que tus usuarios manejen el error. Para ello, deberás propagarlo al nivel de la interfaz de usuario de la app. No es necesario crear un bucle de reintento en cada transacción.

Transacciones de solo lectura

Por último, puedes usar una transacción para leer una instantánea coherente de la base de datos. Esto puede ser útil cuando necesitas varias lecturas para procesar una página o exportar datos que deben ser coherentes. Puedes crear transacciones de solo lectura para estos casos.

Las transacciones de solo lectura no pueden modificar entidades, pero, a cambio, no compiten con ninguna otra transacción y no deben reintentarse. Si solo realizas operaciones de lectura en una transacción normal de lectura y escritura, entonces esa transacción puede competir con la transacción que modifica los mismos datos.

C#

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 datastore.get(taskListKey);
    const query = datastore.createQuery('Task').hasAncestor(taskListKey);
    const [taskListEntities] = await datastore.runQuery(query);
    await transaction.commit();
    return [taskList, taskListEntities];
  } catch (err) {
    transaction.rollback();
  }
}

PHP

Para obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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 obtener información sobre cómo instalar y usar la biblioteca cliente de Cloud Datastore, consulta la página acerca de las bibliotecas cliente de Cloud Datastore. Si deseas obtener más información, consulta la documentación de referencia de la API de 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

Qué sigue

  • Obtén más información sobre las consultas.
¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Datastore