Multi-tenancy

Many database systems provide multi-tenant capabilities. They can contain multiple databases, each with their own access controls. Fauna takes this much further by allowing any database to have multiple child databases. This enables an operator to manage a single large Fauna cluster, create a few top-level databases, and give full administrative access of those databases to the respective teams. Each team is free to create as many databases as they need without requiring operator intervention. As far as the team is concerned, they have their own full Fauna cluster.

It is not possible to query databases outside of the current database. It is possible to perform read-only queries within child databases by specifying the optional database parameter when calling any of these functions: Collection, Collections, Database, Databases, Function, Functions, Index, Indexes, Keys, Role, Roles

To query another database, or perform queries involving writes within a child database, you must use a secret associated with that database. See Fauna key system for more information.

This tutorial demonstrates how to create multiple databases, create child databases, and provide access secrets.

This tutorial assumes that you have completed the Fauna Shell quick start.
  1. Start Fauna Shell

    In a terminal, start Fauna Shell by running:

    fauna shell my_db
    Starting shell for database my_db
    Connected to https://db.fauna.com
    Type Ctrl+D or .exit to exit the shell
     

    The secret used to setup and access the database my_db is an admin secret, which has permission to create and manage child databases.

  2. Create the top-level databases

    First, create a set of top-level databases that can be handed over to individual teams. Run the following query to create two databases, named production and internal:

    Map(
      ["production", "internal"],
      Lambda("name", CreateDatabase({ name: Var("name")}))
    )

    You should see output similar to:

    [ { ref: Database("production"),
        ts: 1558574366150000,
        name: 'production' },
      { ref: Database("internal"),
        ts: 1558574366150000,
        name: 'internal' } ]
  3. Create admin secrets for each database

    Here we create admin secrets for each database. These secrets can be handed to each team, and they give the teams full control over their respective databases (but not each other’s).

    Map(
      [Database("production"), Database("internal")],
      Lambda("db", CreateKey({ role: "admin", database: Var("db") }))
    )

    You should see output similar to:

    [ { ref: Ref(Keys(), "233115268679729664"),
        ts: 1558574894580000,
        role: 'admin',
        database: Database("production"),
        secret: 'fnADPDEaDWACAONo-I_v8FH9DEuWDZKCGQyborRY',
        hashed_secret:
         '$2a$05$ufSuq8vPlxU2KpX3qIxTMue59uz51D.VpXwfiZovyMAm.1lcY/IPK' },
      { ref: Ref(Keys(), "233115268694409728"),
        ts: 1558574894580000,
        role: 'admin',
        database: Database("internal"),
        secret: 'fnADQDExDkACAJxK4j3CwxjTWxB2CnqTHowktW4M',
        hashed_secret:
         '$2a$05$3XncjrjIPhkPAXX9DsV8B.46FVJZOmSAOF6dsqMz3/p5HiLt1aPDm' } ]
    Be sure to save a copy of each secret. Secrets are only displayed when they are created. If you lose a secret, you would need to delete the associated key and make a new one.
  4. Create per-team child databases

    Given the new keys, each team can create their own child databases that fit their needs. In this case, the production team does not need any child databases, but the internal team does.

    Open a new terminal window, and start Fauna Shell with the secret for the internal database (replace the secret below with the secret generated in the previous command):

    fauna shell --secret=fnADQDExDkACAJxK4j3CwxjTWxB2CnqTHowktW4M
    Starting shell for database my_db
    Connected to https://db.fauna.com
    Type Ctrl+D or .exit to exit the shell
     

    Fauna knows which database to connect to when you provide a secret.

    The internal team needs two child databases, personnel and bulletin-board. Run the following query:

    Map(
      ["personnel", "bulletin-board"],
      Lambda("db", CreateDatabase({ name: Var("db") }))
    )

    You should see output similar to:

    [ { ref: Database("personnel"),
        ts: 1558575584530000,
        name: 'personnel' },
      { ref: Database("bulletin-board"),
        ts: 1558575584530000,
        name: 'bulletin-board' } ]
  5. Create server secrets for the internal team’s child databases

    The internal team has an application for each database, so we now need to create server secrets to permit the applications to connect to their respective databases. Server secrets do not have permission to create or manage child databases; they can only be used to connect to their associated database.

    Run the following query:

    Map(
      [Database("personnel"), Database("bulletin-board")],
      Lambda("db", CreateKey({ role: "server", database: Var("db") }))
    )

    You should see output similar to:

    [ { ref: Ref(Keys(), "233116225856602624"),
        ts: 1558575807350000,
        role: 'server',
        database: Database("personnel"),
        secret: 'fnADPDH46ZACAIfnmFk883bVBkFVFodHhtVXXtBK',
        hashed_secret:
         '$2a$05$WWJkl8bydmoAo8D0KNN/w.4sglkxfDwKtSIYr3RU5jg36uT3l3atm' },
      { ref: Ref(Keys(), "233116225856603648"),
        ts: 1558575807350000,
        role: 'server',
        database: Database("bulletin-board"),
        secret: 'fnADPDH46ZAGAMjVDSoDHU89i0qsMq9uMArnLzoK',
        hashed_secret:
         '$2a$05$ZgftjR/OiajqV3qaDSDl0uHolpaO.HRfHQoM4NaWlinF18bgcIUli' } ]

    These secrets should be copied into the respective application’s configuration now; you won’t see the secrets again.

  6. Verify the configuration

    Now that the databases have been created, which databases can be seen?

    In the terminal with Fauna Shell connected to the internal database, run the following query:

    Paginate(Databases())

    You should see output similar to:

    { data: [ Database("personnel"), Database("bulletin-board") ] }

    In the terminal with the Fauna Shell connected to the my_db database, run the following query:

    Paginate(Databases())

    You should see output similar to:

    { data: [ Database("production"), Database("internal") ] }

    Surprised? Fauna only lists the immediate child databases within the connected database. However, the operator can use her admin secret to connect to the child databases to investigate further.

Conclusion

In this tutorial, we demonstrated how to set up a hierarchy of databases, starting with two top-level, broadly-scoped databases and continuing down to individual databases for the internal team. And we saw how to create access secrets and learned how much access those secrets provide.

Fauna works identically. An organization can build their database hierarchy according to their structure without the overhead of operating their own Fauna cluster.

Was this article helpful?

We're sorry to hear that.
Tell us how we can improve!
Visit Fauna's Discourse forums or email docs@fauna.com

Thank you for your feedback!