Snowflake Guide

Snowflake account and database layout: what to split, what to keep together

By the engineer who builds Datadef, from client work on data platforms · Reviewed August 21, 2026

Snowflake gives you accounts, databases, schemas, roles, and virtual warehouses, and each of them can carry the environment boundary. Pick the wrong axis and every promotion becomes a copy job, or every developer needs a role they should not have. This page covers the layout decisions in the order they constrain each other, with the practical consequence of each one rather than a naming template.

8 min readFor data platform owners setting up or cleaning up a Snowflake account

See it as a diagram

Everything below, as a diagram you can edit. Describe yours and see it in seconds.

172/20003 credits left
Try:

No account needed · Editable canvas, not a picture

One account, or one account per environment

The decision that constrains everything else is whether development, test, and production live in one account or in separate ones. The strongest argument for a single account is cloning. create database analytics_test clone analytics_prod; is a metadata operation on objects inside the account, so a full-size test environment costs no storage until something is written to it. Moving data to a different account is a different feature, replication, with its own configuration and its own running cost. Teams that split accounts early usually end up rebuilding test data by hand, which is the exact problem cloning solves.

Separate accounts earn their cost in three situations: a regulatory requirement for hard isolation of production data, a need to run in different regions or cloud providers, and organizations large enough that account-level settings themselves need to differ. Replication between accounts is available but it is a running cost and an operational surface, not a free boundary.

The common middle ground is one account with environments as databases and role-based access enforcing the boundary, plus a separate account only where a regulator asked for one in writing.

Databases, schemas, and where the layer goes

The layout that survives is environment on the database and layer on the schema. ANALYTICS_DEV and ANALYTICS_PROD each contain RAW, STAGING, INT, and MARTS. Promotion is then running the same code against a different database, which is a one-variable change in dbt or in your deployment pipeline, and object names below the database are identical across environments so no query needs rewriting.

The inverse layout, a database per layer with environment in the schema name, looks tidier in a list of databases and creates constant friction. Grants have to be repeated per layer database, clones become multi-database operations, and every piece of code that references a schema now carries the environment string.

Keep the environment out of schema names for the same reason. A schema called MARTS_PROD inside a database called ANALYTICS_PROD states the environment twice and guarantees that a copy between environments will leave a wrong name behind.

Virtual warehouses are a separate axis and should not follow the database layout. Size and separate them by workload, one for loading, one for transformations, one for BI, one for ad hoc, so that a heavy query cannot slow a dashboard and so credit consumption is attributable per workload without tagging every query.

use role sysadmin;

create database if not exists analytics_prod;
create schema   if not exists analytics_prod.raw;
create schema   if not exists analytics_prod.staging;
create schema   if not exists analytics_prod.int;
create schema   if not exists analytics_prod.marts;

-- access roles describe a permission, functional roles describe a job
use role securityadmin;
create role if not exists ar_analytics_prod_marts_r;
grant usage  on database analytics_prod          to role ar_analytics_prod_marts_r;
grant usage  on schema   analytics_prod.marts    to role ar_analytics_prod_marts_r;
grant select on all tables in schema analytics_prod.marts to role ar_analytics_prod_marts_r;
grant select on future tables in schema analytics_prod.marts to role ar_analytics_prod_marts_r;

create role if not exists fr_analyst;
grant role ar_analytics_prod_marts_r to role fr_analyst;
grant role fr_analyst to role sysadmin;

Roles: the split that keeps grants reviewable

Separate access roles from functional roles. An access role represents one permission on one object set, for example read on the marts schema of the production database. A functional role represents a job, for example analyst or data engineer, and is granted a set of access roles. Users only ever get functional roles.

The benefit shows up during an access review. With the split, answering what an analyst can read is reading one role membership list. Without it, the same question requires walking every grant on every object, which is why review gets skipped.

Two operational rules matter more than any naming scheme. Objects should be owned by a role in the SYSADMIN hierarchy, not by ACCOUNTADMIN and not by a personal role, so that ownership survives staff changes. And grants should be created by code in the repository rather than by hand in a worksheet, because a grant nobody can diff is a grant nobody will remove.

Drawing the account so people can navigate it

A Snowflake account is a hierarchy plus a permission graph, and the two are hard to hold in your head at once. On the Datadef canvas, databases become zones, schemas become nodes inside them, virtual warehouses sit apart with labelled edges to the workloads they serve, and the functional roles form their own zone with edges to the access roles they carry. The result is one picture that answers both what exists and who can reach it.

If the account is provisioned with the Snowflake Terraform provider, connect that repository read-only and the layout is parsed from the .tf files themselves, with no init, no state, and no Snowflake credentials handed to a diagramming tool. The parser knows the provider: snowflake_database and snowflake_warehouse are drawn as individual boxes with warehouse_size resolved and shown on the node, snowflake_schema is supporting detail, so ten schemas declared in one module roll up into a single node carrying the count and the member names instead of ten boxes, and any type whose name matches role, grant, or permission is classified as wiring and counted rather than drawn. The picture answers what exists and how big it is, while the permission graph stays a number rather than a hairball.

The daily sync regenerates the diagram and an architecture.md from the chosen branch, and hand-placed nodes keep their positions across syncs so the map stays familiar. See how repository sync works.

FAQ

Should each environment have its own Snowflake account?

Usually not. Cloning a database is a metadata operation on objects inside one account, so a single account with a database per environment gives you full-size test data for free, while getting data into another account means configuring replication. Separate accounts are worth their cost for hard regulatory isolation, for multi-region or multi-cloud deployments, and when account-level settings genuinely need to differ.

Should the environment be in the database name or the schema name?

The database name. Keeping the environment on the database and the layer on the schema means object paths below the database are identical across environments, so promoting code is a one-variable change. Putting the environment in the schema name repeats it and leaves wrong names behind after any clone.

What is the difference between a Snowflake warehouse and a data warehouse?

A Snowflake virtual warehouse is compute, a cluster that executes queries and is billed while it runs. The data warehouse is the databases, schemas, and tables that hold the data. They scale independently, which is why warehouses should be split by workload rather than mirroring the database layout.

How many virtual warehouses should we create?

One per workload with a distinct performance profile or a distinct cost owner, commonly loading, transformation, BI, and ad hoc. That isolates a heavy query from a dashboard and makes credit consumption attributable per workload without tagging every statement. Adding one per team on top of that usually costs more than it explains, because idle warehouses still resume and bill on the first query.

What is the point of splitting access roles from functional roles?

It makes access reviewable. Access roles hold one permission on one object set, functional roles represent a job and are granted a bundle of access roles, and users receive only functional roles. Answering what a job can read then means reading one membership list instead of walking every object grant.