How to handle creating schema/tables on the fly for a multi-tenant web app

Viewed 209

Problem

I'm building a web app, where each user needs to have segregated data (due to confidentiality), but with exactly the same data structures/tables.

Looking around I think this concept is called multi-tenants? And it seems as though a good solution is 1 schema per tenant.

I think sqlalchemy 1.1 implemented some support for this with

session.connection(execution_options={
    "schema_translate_map": {"per_user": "account_one"}})

However this seems to assume the schema and tables are already created.

I'm not sure how many tenants I'm going to have, so I need to create the schema, and the tables within them, on the fly, when the user's account is created.

Solution

What I've come up with feels like a bit of a hack, which is why I'm posting here to see if there's a better solution.

To create schemas on the fly I'm using

if not engine.dialect.has_schema(engine, user.name):
   engine.execute(sqlalchemy.schema.CreateSchema(user.name))

And then directly afterwards I'm creating the tables using

table = TableModel()
table.__table__.schema = user.name
table.__table__.create(db.session.bind)

With TableModel defined as

class TableModel(Base):

    __tablename__ = 'users'

    __table_args__ = {'schema': 'public'}

    id = db.Column(
        db.Integer,
        primary_key=True
    )

    ...

I'm not too sure why to inherit from Base vs db.Model - db.Model seems to automatically create the table in public, which I want to avoid.

Bonus question

Once the schema are created, if, down the line, I need to add tables to all the schema - what's the best way to manage that? Does flask-migrations natively handle that?

Thanks!

1 Answers

If anyone sees this in the future, this solution seems to broadly work, however I've recently run into a problem.

This line

table.__table__.schema = user.name

seems to create some odd behaviour where the value of user.name seems to persist in order areas of the app, so if you switch user, the table from the previous user is incorrectly queried.

I'm not totally sure why this happens, and still investigating how to fix it.

Related