May 08, 2018


Blog Categories

Categories

Blog Categories

Secure Hierarchical Multi-tenancy Patterns

FaunaDB’s multi-tenant architecture is designed for operational simplicity, allowing a single FaunaDB cluster to support multiple regions and as many applications and databases as desired. Robust quality-of-service (QoS) controls mean your performance-sensitive production queries will never slow down due to analytic workloads, developer experiments, or operational movement. This capability supports use cases like SaaS backends, branch-office applications, app and game studios with a stable of content, data isolation for customer confidentiality (GDPR), and other shared services including identity management. FaunaDB’s multi-tenancy is a robust solution for managing access to database resources for teams and applications. Also, it can be paired with FaunaDB’s object level access control for fine-grained authentication and authorization, allowing for safe, direct mobile and web client access to databases in FaunaDB.

FaunaDB allows databases to be arranged in a hierarchy, so each database can contain other databases. Certain security and QoS rules are inherited down the tree. This makes managing groups of applications, or resource usage by teams across your organization, or customers with different payment plans, as easy as a few FaunaDB queries.

In this blog post, we’ll review the APIs for creating a database hierarchy, and how to manage quality-of-service at the connection and database level. We’ll also talk about practical uses for multi-tenancy and QoS.

Setting Database Priority

Database priority allows you to specify which databases are first in line should the cluster ever become overloaded. This way you can confidently run a large query with low priority, and know it won't impact the smaller queries your production users depend on.

Managing database priority is as easy as setting a numerical field on the database schema object. Here is an example that creates two databases in the top level of an account, one called “production” with high priority, and the other called “development” which will see reduced performance in overload scenarios, while production apps are not impacted.

Note: these code samples are written in Python. Our query language reference can help you translate these into any of our supported languages.

adminClient = FaunaClient(secret='YOUR_FAUNADB_ADMIN_SECRET');
adminClient.query(q.create_database({ 
    "name": "production", "priority": 88 }));
adminClient.query(q.create_database({
    "name": "development", "priority": 5 }));

Both of these databases are created in a container determined by the client secret used. Each connection connects to a particular database, and all operations run in a database root. The next section illustrates with examples.

Nesting Databases

Inside of both the high and low priority databases we could create as many nested databases as we like. Databases inherit priority from their containing database, so to extend the above example, you could create fast and slow apps, by setting relative priority among databases in the same container. If you need to throttle or boost one of your production apps, that's a query away.

Before we can create nested databases in the production database we need to connect in the right context. A connection to the “production” database will allow us to create nested databases and other schema objects inside it. To get a connection with those capabilities, we need a key secret. We’ll use our admin client to create an admin key inside the “production” database and use the key’s secret to connect and create some nested databases.

productionKey = adminClient.query(q.create_key({
    "database": q.database("production"), 
    "role": "admin" 
}));
productionClient = FaunaClient({ 
    "secret": productionKey.secret });
productionClient.query(q.create_database({ 
    "name": "ShoppingCart" }));
productionClient.query(q.create_database({ 
    "name": "IdentityService" }));

The productionKey we created can modify databases inside the production context, but it cannot access or modify databases in the development context. If we want to work on those, we need to do the same for the “development” database, creating an admin key that can create nested databases. Here we create some database inside the development database:

developmentKey = adminClient.query(q.create_key({
    "database": q.database("development"), 
    "role": "admin" 
}));
developmentClient = FaunaClient({ 
    "secret": developmentKey.secret });
developmentClient.query(q.create_database({ 
    "name": "HackDay" }));
developmentClient.query(q.create_database({ 
    "name": "TeamNotes" }));

Say we wanted to create a record in the “TeamNotes” database we just created, first we’d create a server key for accessing that database. Server keys can make schema and data changes but they can’t modify the database tenancy tree. Note we use the existing development client to create the server key. Once we have it we can use it to work in the TeamNotes database.

teamNotesKey = developmentClient.query(q.create_key({
    "database": q.database("TeamNotes"), 
    "role": "server" 
}));
teamNotesClient = FaunaClient({ 
    "secret": teamNotesKey.secret });
teamNotesClient.query(q.create_class({ 
    "name": "todos" }));
teamNotesClient.query(q.create(q.class_expr("todos"), {
    "data": {  "title": "Organize team BBQ." }
}));

These examples should be enough to get you started. If you want to go further and restrict access within a particular database, so that certain keys can only apply certain operations, you can achieve that level of control with FaunaDB’s object-level security. Learn more about access control in the documentation, or if you prefer a hands-on approach, see this code which makes use of object level access control to give users access to todo lists. Object level access control is beyond the scope of this post, but it’s an important complement to the security capabilities multi-tenancy offers.

Organizing Databases

Here are a few quick examples to get you thinking about how you could use hierarchical multi-tenancy in your enterprise. There are not meant to be gospel, just suggestions for jumping off points to address your own requirements.

Development Studio

Development houses, whether they specialize in line-of-business apps, consumer experiences, retail, or gaming, tend to end up owning a lot of product maintenance and uptime as their portfolio grows. Running a database service for each product quickly becomes unmanageable. Here’s how a shop like that might organize their FaunaDB databases.

  • Production
    • Clients
      • GlobalBrand
        • CouponApp
        • LoyaltyNotifier
        • CustomerServiceApp
      • BigLender
        • MortgageApplication
        • InsuranceOnsite
        • RateEstimator
      • RetailShop
        • Catalog
        • Inventory
        • PointOfSale
        • Payroll
        • Compliance
        • PromotionalApps
          • SpringSale
          • BlackFriday
    • InHouse
      • TimeAndTicketTracker
      • Vendors
      • Shipping
  • Development
    • Clients
      • GlobalBrand
      • BigLender
      • RetailShop
      • RideFinder
    • Teams
      • FrontEnd
      • Support
      • Admin
      • Exec

By organizing across development and production, they can give each team and client app a place to innovate, while also running the production traffic on the same cluster. By setting priority higher on the production root, they ensure performance for live traffic comes before experimental workloads. This solution works equally well in FaunaDB Cloud as it does on-premise with FaunaDB Enterprise.

SaaS Backend

Software-as-a-Service is characterized by self-service signup and payment. If users can onboard themselves, the business can cast a wider net. In this case we see a few root databases, and then a customer database with databases for each customer. It’s possible to set priority for individual customers, for instance if they are using a premium plan.

  • Admin
  • Billing
  • Customers
    • ab1d237c8ea
    • 7c8e5b1d23a
    • 1d687cbc8ea
    • c8e5ab1d237

This looks a lot like the backend of FaunaDB Cloud — it’s possible for each customer to have their own arbitrary schema and multi-tenant tree inside their top-level database. So your SaaS product can even allow your customers to automatically provision databases for their individual sites or users.

Resource usage can be aggregated on a per-database level, so it’s possible to bill your customers individually in proportion to their database usage.

Conclusion

Anytime you might want shared access to database resources, FaunaDB is your friend. From an enterprise-wide cluster that can be used for workloads across the organization, to high performance shared identity services, to flexible SaaS backends that can include both your large and small customers, FaunaDB multi-tenancy is designed to support you. For further reading our technical whitepaper will help you see how our multi-tenancy capabilities fit into the bigger picture and our multi-tenancy tutorial introduces the API.