Cloud Spanner | La base de données globale de Google

spanner-google

En formation sur la Google Cloud Platform le jour de la sortie de Cloud Spanner, j’ai eu la chance de pouvoir expérimenter ce nouvel outil. Voici mes premières impressions.

Cloud Spanner ?

Cloud Spanner est une base de données relationnelle, assez classique en apparence (ACID, SQL), mais globalement distribuée, ce qui lui confère le buzzword « NewSQL ». Google l’utilise depuis quelques années dans ses produits, notamment Gmail et AdWords, et vient de la mettre à disposition en bêta.

Une installation flash

En 3 clics et autant de secondes, on se retrouve avec une instance Spanner prête à héberger notre première database. La création des tables se fait le plus naturellement du monde avec un simple script SQL à copier/coller dans l’interface.

Script à copier/coller (source Quick Start)

CREATE TABLE Singers (
  SingerId INT64 NOT NULL,
  FirstName STRING(1024),
  LastName STRING(1024),
  SingerInfo BYTES(MAX),
  BirthDate DATE,
) PRIMARY KEY(SingerId);

CREATE TABLE Albums (
  SingerId INT64 NOT NULL,
  AlbumId INT64 NOT NULL,
  AlbumTitle STRING(MAX),
  MarketingBudget INT64,
) PRIMARY KEY(SingerId, AlbumId),
  INTERLEAVE IN PARENT Singers ON DELETE CASCADE;

Rien de particulier à noter à ce stade, si ce n’est l’instruction INTERLEAVE IN PARENT au lieu du FOREIGN KEY habituel. En effet, tout cela étant shardé derrière les rideaux, il vaut mieux rapprocher les lignes mises en relation (on parle de colocalisation), d’où le concept parent-enfant (le terme « foreign » prêtant à confusion).

De plus, la clé primaire est obligatoire sur chaque table, et les tables « enfants » doivent posséder toutes les colonnes de la clé primaire de la table « parent », et ce, dans le même ordre.

Ces spécificités mises à part, les requêtes SQL SELECT sont standards, et directement exécutables depuis l’interface (vous verrez même le plan d’exécution dans l’onglet « Explanation »).

En revanche, ce qui m’a vraiment dérouté dans le « Quick Start » c’est que l’on doit se contenter de faire un SELECT qui ne retourne absolument aucune ligne… Car les commandes d’écriture UPDATE et DELETE n’existent tout simplement pas ! Gageons que cela n’est qu’une question de temps.

Cloud Spanner

Heureusement que le logo de Cloud Spanner est là pour nous rappeler que ce n’est pas une simple base de données !

Comment insère-t-on les données ?

Une API Java (mais aussi Go, Python et Node.js) est mise à disposition. Elle est relativement simple et intuitive, et c’est elle qui va nous permettre d’insérer nos données (entre autres).

Remarque : Si vous souhaitez suivre le Getting Started pour jouer vous-même avec Spanner, ne perdez pas votre temps avec la Cloud Shell. En effet, même si elle est généralement parfaite, vous n’aurez pas assez de RAM pour faire tourner la commande mvn package. Installez plutôt gcloud sur votre machine et suivez les instructions.

L’API repose sur le concept de « mutations » qui sont exécutées de manière atomique :

  List<Mutation> mutations = new ArrayList<>();
  mutations.add(
        Mutation.newInsertBuilder("Singers")
            .set("SingerId")
            .to(1)
            .set("FirstName")
            .to("Marc")
            .set("LastName")
            .to("Richards")
            .build());
  mutations.add(
        Mutation.newInsertBuilder("Albums")
            .set("SingerId")
            .to(1)
            .set("AlbumId")
            .to(1)
            .set("AlbumTitle")
            .to("Total Junk")
            .build());
  // This writes all the mutations to Cloud Spanner atomically.
  dbClient.write(mutations);
}

Spanner respecte les principes ACID et supporte 2 types de transactions :
read-only (plus performante puisqu’elle ne nécessite pas de lock)
read-write (pessismistic lock, two-phase commit)

Pour les curieux, voici un aperçu d’une transaction read-write :

dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
              // Transfer marketing budget from one album to another. We do it in a transaction to
              // ensure that the transfer is atomic.
              Struct row =
                  transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
              long album2Budget = row.getLong(0);
              // Transaction will only be committed if this condition still holds at the time of
              // commit. Otherwise it will be aborted and the callable will be rerun by the
              // client library.
              if (album2Budget >= 300000) {
                long album1Budget =
                    transaction
                        .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
                        .getLong(0);
                long transfer = 200000;
                album1Budget += transfer;
                album2Budget -= transfer;
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(1)
                        .set("MarketingBudget")
                        .to(album1Budget)
                        .build());
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(2)
                        .set("AlbumId")
                        .to(2)
                        .set("MarketingBudget")
                        .to(album2Budget)
                        .build());
              }
              return null;
            }
          });

Les index secondaires

La pose des index secondaires est aisée…

CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);

…mais particulièrement lente ! Il m’a fallu attendre environ 2 minutes pour une dizaine de lignes à indexer. La clause INTERLEAVE est également disponible pour les index secondaires afin d’optimiser les JOIN (grâce à la colocalisation des lignes). On peut d’ailleurs ajouter d’autres colonnes directement dans l’index pour éviter de recourir au JOIN (une sorte de dénormalisation en somme).

CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle) STORING (MarketingBudget);

Il suffit ensuite de lancer une requête SQL avec une jointure et Spanner devrait utiliser vos index à bon escient. Mais vous pouvez toujours l’aider en forçant l’utilisation de votre index.

SELECT AlbumId, AlbumTitle, MarketingBudget FROM Albums@{FORCE_INDEX=AlbumsByAlbumTitle}
WHERE AlbumTitle >= 'Aardvark' AND AlbumTitle < 'Goo';

Conclusion

C’est un peu la base de données fantasmée puisqu’elle se paie le luxe de remettre en cause le théorème CAP. En effet, jusqu’à présent il a toujours fallu faire l’impasse sur une des lettres de ce théorème, une bonne partie des bases NoSQL étant eventually consistent : on était dans le compromis.

La consistance C de Spanner a été résolue en se basant sur le TrueTime qui s’appuie sur une synchronisation des horloges, tous les noeuds retournant la même valeur à un instant (timestamp) donné. La disponibilité A n’est pas totale, mais la qualité de l’infrastructure de Google apporte une garantie proche de 100%.

Pour plus de détail, vous pouvez lire ce PDF (Research Google).

Google recommande au moins 3 noeuds pour la production : à 0.9$ l’heure, ça nous amène à peu près à 2 000 dollars par mois (+ 0.3$ par GB stocké). Sans doute un poil trop cher pour vos projets perso mais cadeau pour vos projets pro !

Les bases relationnelles sont encore très présentes, et même si les bases NoSQL nous apportent une plus grande souplesse, notamment avec le schema-less pour certaines d’entre elles, il ne faut pas complètement oublier que c’est d’abord la scalabilité horizontale qui les a fait couronner rois…

Et c’est désormais une chose révolue !

Envie de vous former sur GCP

Formation Gratuite SFEIR School

Apprendre à développer des applications basées sur le Cloud à l'aide de Google App Engine, Google Cloud Datastore et Google Cloud Endpoints.


Vous aimerez aussi...