Distributed lock on GCP Datastore

So, I needed a distributed lock on the GCP Datastore. In theory, it is all very simple:

  • Get the lock
  • Do what you need to do
  • Release the lock

And, when I started thinking about it, it became clear that I need to have some kind of transaction mechanism in order to obtain the lock. Basically, when couple of applications ask for the lock, only one should get it and others should fail.

I am using Spring Cloud GCP, but I found nothing I can use for this. So, I had to dig a bit deeper to find out Google’s docs.

Google’s examples

Google docs

Key idea is here:

You can save the entity to the database by using upsert (which will overwrite an entity if it already exists in Datastore mode) or insert (which requires that the entity key not already exist).

Basically, what it means is that this code will fail if we try to insert already existing entity:

Key taskKey = datastore.add(FullEntity.newBuilder(keyFactory.newKey()).build()).getKey();

So, my idea is that I can use this method to create a row in a database whose id is a string value I need to get a lock on. This is the first version of something that might do just that:

@RequiredArgsConstructor
@Component
@Slf4j
public class DatastoreLocks {

    private final Datastore datastore;

    DateTimeFormatter formatter =
            DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT )
                    .withZone( ZoneId.systemDefault() );

    public boolean acquireLock(final String value) {
        try {
            Key entityKey = datastore.newKeyFactory()
                    .setKind("locks")
                    .newKey(value);
            Entity entity = Entity.newBuilder(entityKey)
                    .set("dateCreated",  formatter.format(Instant.now()))
                    .set("description", "Datastore lock for value " + value)
                    .build();

            datastore.add(entity);
            return true;
        } catch (Exception e) {
            log.error("Cannot acquire lock: " + e.getMessage());
            return false;
        }
    }

    public boolean releaseLock(final String value) {
        Key entityKey = datastore.newKeyFactory()
                .setKind("locks")
                .newKey(value);
        datastore.delete(entityKey);
        return true;
    }
}

I’m thinking this might work just fine…