Skip to content
abhinavbhatia
Sheet 02 / 05 — Blogs← Archive

Databricks on Google Cloud, Part 5: Storage, Cloud Storage and how Unity Catalog uses it

Where does Databricks actually keep data on Google Cloud? Cloud Storage from first principles for a lakehouse, the bucket settings that matter for Delta tables, why hierarchical namespace buckets cannot be external locations, the workspace storage buckets and default storage, storage credentials and external locations, managed storage at metastore, catalog and schema level, managed and external tables and volumes, what DROP really deletes, and the legacy DBFS.

TL;DR Pocket Books · Part 5

Read this chapter as 5 illustrated mini-books

Flip pages horizontally like a hardcover book, or swipe up like a reel to move through all 5 topics (19 animated pages).

0 of 5 readAll Series Shorts →
Part 1 · TL;DR 1 of 5
← → Turn Page · ↑ ↓ Next TL;DR ReelSwipe ← → page · Swipe ↑ ↓ next TL;DR
100%
1.0×
Full post ↗
PART 1 · FOUNDATIONSPLATE 01.1

i
TL;DR 01PAGE 1 OF 4
MINI-BOOK 01 · STEP 1

In Part 4 we followed the packets: how serverless compute reaches your data and the internet, and how you control what it can reach. This part follows the bytes. When Acme’s distribution centres drop a delivery file, when a pipeline writes a Delta table, or when an analyst runs CREATE TABLE in a notebook, which bucket do the files land in, whose project is that bucket in, and what happens to them when someone types DROP?

Storage is the part of Databricks on Google Cloud that confuses people most, and in my experience it confuses both audiences of this series for different reasons. Databricks people from AWS or Azure expect an S3 bucket or an Azure storage account with a hierarchical namespace, and find neither quite where they expect it. Google Cloud people know Cloud Storage well, but have not met Unity Catalog’s vocabulary: storage credentials, external locations, managed locations, managed and external tables, and volumes. Several of these have similar names and mean very different things.

I start with what Cloud Storage gives a lakehouse and the bucket settings that matter for Delta tables, including why Databricks does not support hierarchical namespace buckets for external locations. Then I go through every kind of storage a workspace uses, how Unity Catalog connects to a bucket, where Acme’s tables and volumes land, and what DROP really deletes and when. I finish with DBFS, a bucket layout I would use, and the errors I see most often. All status claims are as of October 2026, and the ones that changed since are updated and dated (last checked 8 November 2026).

If you prefer to watch first, here is the heart of this part as a short animated explainer.

Video: where Databricks keeps data on Google Cloud, and what DROP really deletes, in three minutes. The narration is a synthetic voice; the script and animation are mine.

What are the key ideas?

Eight terms carry the whole post. The first two are Google Cloud terms, the rest are Databricks terms.

  • Bucket. A named container for objects in Cloud Storage, Google Cloud’s object storage. It belongs to one Google Cloud project and has a location: a region, a dual-region or a multi-region. Acme’s data bucket is gs://acme-lake-supply in the project acme-lake, in europe-west2.
  • Object. One file in a bucket, found by its name (its key). Prerequisite 1 showed what an object is made of and why objects cannot be edited in place.
  • Workspace storage. The storage that belongs to a workspace itself, not to any table: notebook revisions, job results, logs and the legacy file system. In a classic workspace it is two or three buckets in the workspace project; in a serverless workspace it is default storage.
  • Default storage. Object storage that Databricks provisions and manages inside your Databricks account, never in your Google Cloud project.
  • Storage credential. A Unity Catalog object that holds the identity Databricks uses to reach a bucket. On Google Cloud that identity is a Google service account that Databricks generates.
  • External location. A Unity Catalog object that pairs a Cloud Storage path with a storage credential, so that Unity Catalog can govern everything under that path.
  • Managed storage location. A path, set on a metastore, a catalog or a schema, where Unity Catalog stores managed tables and managed volumes and controls their files completely.
  • External table and external volume. A table or volume whose files live at a path you choose inside an external location. Unity Catalog governs access, but you control the files.

The diagram shows where each of these sits for Acme. Keep it in mind: every storage question in this post becomes which account, which project, which bucket, and which path?

WHERE DATABRICKS KEEPS DATA ON GOOGLE CLOUD DATABRICKS' ACCOUNT Unity Catalog metastore storage credential acme-lake-sc external location acme_lake catalog acme_supply managed location .../managed serverless compute SQL warehouses, jobs and notebooks that Databricks runs for you default storage serverless workspaces: workspace storage and their default catalog acme-dbx-prod (workspace project) workspace storage buckets (classic only): databricks-<workspace-id> DBFS root, legacy databricks-<workspace-id>-system system data workspace catalog bucket if UC auto-enabled classic cluster VMs (databricks-compute) reach Cloud Storage via Private Google Access acme-lake (data project) gs://acme-lake-supply (Acme owns it) managed/ managed tables, volumes landing/ external volume supply_service/ external table every path is inside the external location acme_lake, so Unity Catalog governs it credential's service account (IAM roles on the bucket) serverless reads it too Tables and volumes live wherever their location points: here, Acme's bucket. Workspace storage holds the workspace's own files and moves to default storage in a serverless workspace.

where Databricks keeps data on Google Cloud: Unity Catalog in the control plane holds the storage credential, the external location and the catalog's managed location; a classic workspace has workspace storage buckets in the workspace project; Acme's own bucket gs://acme-lake-supply in the acme-lake project holds managed tables, an external volume and an external table; classic and serverless compute both reach that bucket through the credential's service account, and serverless workspaces keep their own storage in default storage

What does Cloud Storage give a lakehouse?

The short answer is: everything a table format needs, as long as you choose a few settings well. Prerequisite 2 introduced Cloud Storage as one of the services this series uses; here I go one level deeper.

A bucket lives in a project and has one location. There is no extra layer between the project and the bucket, unlike the storage account that sits between an Azure subscription and its containers. The bucket location is chosen when you create the bucket. A region such as europe-west2 (London) keeps data in one region, copied synchronously across its zones. A dual-region is a specific pair of regions with data copied asynchronously between them, and an optional turbo replication feature that replicates within 15 minutes. A multi-region such as EU spreads data across a large geographic area. The location type decides price and data transfer charges, which is why it matters for Databricks: the Databricks guide to Cloud Storage external locations says the bucket “should be in the same region as the workspace” to avoid egress charges.

A storage class sets the price of keeping and reading an object. The storage classes are Standard, Nearline, Coldline and Archive. The colder classes cost less to store but have a minimum storage duration (you pay for at least 30, 90 or 365 days even if you delete earlier) and a retrieval fee on every read. One thing surprises people from AWS: all classes, including Archive, return data in milliseconds, with no restore step.

1. WHERE DOES THE BUCKET LIVE? Location type Example How data is copied Reading it from compute Region europe-west2 across zones in one region (synchronous) no outbound charge from the same region; lowest price Dual-region a pair of regions across two regions (async); turbo replication: 15 min no outbound charge within either region; replication charge on write Multi-region EU, US across regions in a large area (asynchronous) outbound data transfer charges always apply when reading 2. WHICH STORAGE CLASS? Storage class Minimum storage duration Retrieval fee Where it fits a lakehouse Standard none none tables and active landing files Nearline 30 days yes landing files read about monthly Coldline 90 days yes raw files kept for audits Archive 365 days yes long-term copies, still milliseconds Acme: a regional bucket in europe-west2, the workspace's region, on Standard storage. The right-hand column of table 2 is my recommendation, not a Google rule.

choosing where a lakehouse bucket lives and which storage class it uses: a region such as europe-west2 copies data across zones and has no outbound charge when compute in the same region reads it, which is Acme's choice; dual-regions and multi-regions copy across regions at a higher price; Standard has no minimum duration or retrieval fee, while Nearline, Coldline and Archive have 30, 90 and 365 day minimums and retrieval fees but still answer in milliseconds

Every read sees the latest write. Cloud Storage offers strong global consistency for object read-after-write, read-after-delete and object listing. “Consistency” here means how soon a change is visible: strongly consistent means that as soon as a write succeeds, every reader everywhere sees it, and a listing immediately includes the new object. Delta Lake depends on this. A table is a set of Parquet files plus a log, and a commit is the creation of the next log file. If a reader could miss a log file that had just been written, it would read an old version of the table without knowing.

The commit itself uses a precondition. Prerequisite 1 showed how a Delta writer creates _delta_log/00000000000000000002.json with a generation-match precondition of 0, which tells Cloud Storage to create the object only if no object with that name exists. That put-if-absent is what lets two writers race safely: only one can create version 2. Keep this in mind for the hierarchical namespace question below.

A simple way to picture both guarantees. Imagine a club’s guest book with numbered pages, kept at the front desk. Two rules make it trustworthy. First, the moment someone writes on page 2, anyone who opens the book sees page 2; nobody is handed an old photocopy. Second, the desk only lets you write on a page that is still blank, so if two members arrive at once to claim page 2, one gets it and the other is told to read the book again and try page 3. The book is the _delta_log folder, each page is one numbered log file, the first rule is strong consistency and the second is put-if-absent, enforced by Cloud Storage acting as the desk.

How does this apply to Acme? Suppose (a hypothetical moment) Acme’s nightly pipeline and an analyst’s backfill both commit to supply_gold.supply_service at the same time, and both try to create _delta_log/00000000000000000002.json. Cloud Storage lets exactly one create succeed. The other writer’s precondition fails, so it re-reads the log, sees version 2, and tries again as version 3 if its change does not conflict. A dashboard query that starts a second later sees version 2 straight away, because the listing already includes the new log file. Where people get tripped up is assuming object storage is only eventually consistent, so a reader might miss a fresh commit; on Cloud Storage that cannot happen.

Request rates scale, but gradually. The request rate guidelines say a bucket starts with roughly 1,000 object writes and 5,000 object reads per second and then autoscales by spreading load across more servers, which takes minutes. Above those rates, ramp up no faster than doubling every 20 minutes, and avoid strictly sequential object names, which concentrate load on one range of the bucket’s index. In practice Spark writes files with random suffixes, so this matters most for a big first load into a new bucket.

Which bucket settings matter for Delta tables?

The rule behind every choice: on a table path, only Delta decides which files to delete. Any bucket feature that deletes files on its own timer, or refuses to let Delta delete them, fights the Delta log.

A Cloud Storage bucket has many settings. Most are about security or cost, and a few can break a Delta table if you set them without thinking about how Delta works.

Picture a library whose catalogue lists which books are current. The librarian weeds out books the catalogue no longer lists. Now add a cleaner who throws away every book older than 90 days without reading the catalogue: readers soon find catalogue entries that point at empty shelves. Add a sign saying no book may leave the building for seven years, and the librarian can no longer weed at all. A bin by the door that keeps thrown-out books for a week before shredding them is harmless, but the space it takes is paid for. The catalogue is the Delta log, the librarian is VACUUM, the cleaner is a lifecycle Delete rule, the sign is a retention policy and the bin is soft delete.

How does this apply to Acme? On gs://acme-lake-supply, the table paths (managed/ and supply_service/) get no lifecycle rules, no retention policy and no Autoclass, because a log reads them. The landing/ path holds raw files that no log points at, so the cleaner is welcome there. The remaining settings are about who may reach the bucket at all and where it lives. Here is each one with what I choose for a bucket that holds Databricks tables.

BUCKET SETTINGS FOR A DATABRICKS DATA BUCKET Setting What it does On a Databricks data bucket Location fixed at creation (relocation needs Storage Intelligence) same region as the workspace (europe-west2 for Acme) Hierarchical namespace real folders; chosen only at creation, cannot be changed keep OFF: not supported for Unity Catalog external locations Uniform bucket-level access IAM only; object ACLs are disabled ON Public access prevention blocks public access to data enforced Soft delete keeps deleted objects 7 days by default (7 to 90 days, 0 = off) choose per bucket: deleted Delta files are billed while retained Object Versioning keeps a noncurrent version of every replaced object usually off: Delta keeps its own history Lifecycle rules (Delete, SetStorageClass) act on objects by age, prefix and other conditions never on table paths; fine for landing/ files Autoclass moves unread objects to colder classes automatically landing buckets only, if at all Retention policy (Bucket Lock) objects cannot be deleted until older than the period avoid on table buckets: VACUUM and purge cannot delete files Highlighted rows break things if set wrongly; the others cost money or weaken security.

Cloud Storage bucket settings and what to choose for a Databricks data bucket: same region as the workspace, hierarchical namespace off because Unity Catalog external locations do not support it, uniform bucket-level access on, public access prevention enforced, soft delete chosen deliberately because deleted Delta files are billed while retained, Object Versioning usually off, no lifecycle rules on table paths, Autoclass only for landing data, and no retention policy on table buckets

A few of these need a sentence more.

  • Uniform bucket-level access turns off ACLs (access control lists, the older per-object permission system) so that only IAM decides who can reach the bucket. With Unity Catalog in front, almost nobody should have IAM on the bucket at all, so one permission system is plenty. It is also required for hierarchical namespace.
  • Public access prevention stops anyone from granting allUsers or allAuthenticatedUsers access. Enforce it on every data bucket, ideally through the organisation policy.
  • Soft delete is on by default for new buckets, with a 7-day retention that you can set between 7 and 90 days, or 0 to turn it off. A soft-deleted object cannot be read but can be restored, and you pay for it while it is retained. Since 4 November 2026 you can also pause hard deletions on a bucket: soft-deleted objects are then kept “indefinitely, regardless of their soft delete retention durations”, and billed, until you resume. You can pause or resume at most once every 24 hours, and a bucket cannot be deleted while paused. For example, if someone runs a wrong cleanup job on acme-lake-supply on a Friday and the restore will take longer than the 7-day window, a storage admin pauses hard deletions first, restores, then resumes. Delta tables delete files all the time: VACUUM, as Prerequisite 1 explains, removes data files that the table no longer references. Google’s own page warns that soft delete on buckets of temporary data “can result in significantly increased storage costs”. My view: keep the default on table buckets until you see the bill, and decide per bucket with your recovery policy in mind, because Delta time travel already covers most mistakes inside the retention period.
  • Object Versioning keeps a noncurrent copy each time an object is replaced or deleted. Delta never replaces files, it adds new ones, so versioning mostly keeps deleted files alive and billed. I leave it off on table buckets.
  • Lifecycle rules delete objects or change their storage class by age, prefix and other conditions. A Delete rule does not know which Parquet files a Delta log still points to, so on a table path it can delete live data and leave a table whose log refers to files that no longer exist. Use them on landing/ paths for raw files, never on table paths. Let Delta and predictive optimization delete table files.
  • Autoclass moves objects that are not read to colder classes automatically, Nearline by default. That suits a landing bucket of raw files read once. On a table that is read unevenly it adds retrieval fees, so I avoid it there.
  • A retention policy means objects can only be deleted or replaced once they are older than the retention period, and Bucket Lock makes the policy permanent. On a table bucket, VACUUM and Unity Catalog’s purge would fail to delete files. Keep retention policies for archive buckets.

Here is how Acme creates its data bucket with the gcloud CLI. The flags come from the bucket creation guide and the gcloud storage buckets create reference; I have not run this exact command.

# Flat namespace (no --enable-hierarchical-namespace), regional, IAM only, never public
gcloud storage buckets create gs://acme-lake-supply \
  --project=acme-lake \
  --location=europe-west2 \
  --default-storage-class=STANDARD \
  --uniform-bucket-level-access \
  --public-access-prevention \
  --soft-delete-duration=7d

# Labels for cost reports: who owns it and what it holds
gcloud storage buckets update gs://acme-lake-supply \
  --update-labels=env=prod,domain=supply,owner=data-platform

In Terraform, the equivalent google_storage_bucket resource sets location = "europe-west2", uniform_bucket_level_access = true, public_access_prevention = "enforced" and, if you want to be explicit, a soft_delete_policy block with retention_duration_seconds = 604800 (7 days), per the provider documentation.

What is hierarchical namespace, and why can Databricks not use it?

Hierarchical namespace makes folder renames atomic; Delta never renames a folder, so Databricks asks for flat buckets and loses nothing. This is the storage question I hear most from teams that come from Azure, so it deserves its own section. Where they get tripped up is expecting a Google Cloud copy of the Azure Data Lake Storage setup they know. On Google Cloud, Unity Catalog external locations need a flat bucket.

What it is. By default a Cloud Storage bucket has a flat namespace: there are no folders, only object names that happen to contain slashes. A bucket created with hierarchical namespace (HNS) has real folders, which are resources of their own. Folders can be listed, deleted and, most importantly, renamed atomically: renaming landing/ to archive/ is one operation that either happens completely or not at all, instead of a copy and delete of every object underneath. Google also documents up to eight times higher initial queries per second for reads and writes.

Why would anyone want that? Older Hadoop and Spark jobs, and many AI training tools, assume a file system. Some of them commit their output by writing to a temporary folder and renaming it, which on a flat bucket is slow and not atomic. People who know Azure Databricks know the same idea as Azure Data Lake Storage with a hierarchical namespace, which is why they ask for it on Google Cloud.

What it costs you. HNS must be chosen when the bucket is created and can never be changed. It requires uniform bucket-level access. And it does not support Object Versioning, Bucket Lock, Object Retention Lock, object holds, object ACLs or cross-bucket replication.

What Databricks says. The Databricks documentation is short and clear: “Google Cloud Storage hierarchical namespace (HNS) is not supported with external locations. Disable hierarchical namespace before creating an external location.” Because an HNS setting cannot be changed, in practice “disable” means using a different, flat bucket. And since every managed storage location for a catalog or schema must sit inside an external location, HNS buckets are out for Unity Catalog’s managed storage too.

Why that costs a Delta table nothing. Databricks does not publish its reasons, so what follows is my reading of how Delta works, not a quote. The headline feature of HNS is the atomic folder rename, and Delta never renames anything. A commit writes new Parquet files under new names and then creates one small log file with put-if-absent. Readers find the files of a version by reading the log, not by listing folders. So a flat bucket already gives Delta everything its commit protocol needs.

A filing room makes the difference concrete. In a flat room there are no drawers, only files with a label such as landing/report-17. Renaming landing to archive means relabelling every file, one at a time, and anyone who walks in halfway sees a mix of old and new labels. In a room with real drawers (hierarchical namespace) you change the sign on the drawer once. Now picture a clerk who never relabels anything: to change a record, they file new pages under new names and then add the next numbered line to the index, which the desk accepts only if that line number is still free. That clerk is Delta, and the drawer sign is of no use to them.

RENAMING A FOLDER: FLAT BUCKET VS HIERARCHICAL NAMESPACE flat namespace (the default) landing/2026-05-04/dc-rotterdam.json landing/2026-05-04/dc-leeds.json landing/2026-05-05/dc-rotterdam.json ... one name per object, no folders rename landing/ to archive/ = copy + delete every object, not atomic hierarchical namespace landing/ 2026-05-04/ dc-rotterdam.json dc-leeds.json 2026-05-05/ rename landing/ to archive/ = one atomic folder operation HOW A DELTA COMMIT WORKS ON ANY BUCKET 1 write data files part-00005.parquet and part-00006.parquet (new objects, new names) 2 create the log entry _delta_log/...0012.json only if it does not exist (ifGenerationMatch=0) 3 version 12 visible readers now see the new files through the log another writer took 0012: re-read the log, try 0013 No step renames a folder. Delta's commit is a put-if-absent of one small object, so the atomic rename that hierarchical namespace adds is not on the commit path; the log, not a listing, is the index.

why hierarchical namespace does not help Delta tables: in a flat bucket, renaming the folder landing/ to archive/ means copying and deleting every object, while a bucket with hierarchical namespace renames the folder in one atomic operation; a Delta commit never renames anything: it writes new Parquet files, then creates the next _delta_log JSON file only if no object with that name exists (generation-match precondition 0); success makes the version visible, and a writer that loses the race re-reads the log and retries

How does this apply to Acme? gs://acme-lake-supply is created without --enable-hierarchical-namespace, and supply_service commits by adding the next log file, exactly like the clerk. Nothing in Acme’s lakehouse depends on renaming a folder.

My advice when a team asks for HNS: keep Databricks data in flat buckets and govern folders with Unity Catalog grants on external locations, volumes and tables, not with folder permissions in the bucket. If another tool genuinely needs HNS, give it its own bucket and share data with it through tables, not through a shared folder tree. Moving existing data between buckets is a migration task, and Part 10 covers it.

What are managed storage, external storage and default storage?

Who owns the bucket and who controls the files are two separate questions. Workspace storage and default storage hold the workspace’s own files; managed and external storage hold your tables and volumes, and both normally sit in a bucket you own. The most common misreading is that a “managed” table lives in Databricks’ storage. On Google Cloud a managed table usually lives in your bucket: managed means Unity Catalog controls its files, not that Databricks hosts them. Only default storage is in Databricks’ account.

With Cloud Storage itself covered, we can look at how Databricks uses it. Two questions untangle it: how many buckets are there, and in whose project? Then: where does each table or volume actually end up?

How many buckets, and whose are they? A common way to put it is “a classic workspace has four buckets and a serverless workspace has one”. That is close, with two refinements.

  • A classic workspace gets two or three workspace storage buckets that Databricks creates in the workspace’s project: the DBFS root, the system data bucket and, only if Unity Catalog was enabled automatically (as it is for most new workspaces), a bucket for the workspace catalog. On top of those you normally create one or more buckets of your own for your data, such as Acme’s gs://acme-lake-supply. So “four” is typical, but it is three of Databricks’ plus however many of yours.
  • A serverless workspace gets no Databricks-created buckets in your project at all. Its workspace storage lives in default storage, in Databricks’ account. Your own buckets are optional, though any company with real data will have at least one.

Here is the whole picture side by side, with Acme’s names. Notice that the buckets Acme creates sit in Acme’s project in both cases, and Unity Catalog reaches them the same way: through the storage credential’s service account, which has IAM roles on each bucket.

WHERE THE BUCKETS ARE: CLASSIC VS SERVERLESS CLASSIC WORKSPACE UNITY CATALOG (DATABRICKS' CONTROL PLANE) storage credential acme-lake-sc (an SA) external location acme_lake (gs:// path) ACME'S GOOGLE CLOUD PROJECTS Databricks creates databricks-<id>- system: system data, revisions, logs databricks-<id> legacy DBFS root, not for production workspace catalog bucket: default catalog and default schema Acme creates; SA has roles catalog storage managed location for acme_supply: managed/ lakehouse bucket external tables and external volumes landing zone raw files for Auto Loader classic compute (GCE, Acme's VPC) reads both SERVERLESS WORKSPACE UNITY CATALOG (DATABRICKS' CONTROL PLANE) storage credential acme-lake-sc (an SA) external location acme_lake (gs:// path) DATABRICKS' ACCOUNT default storage workspace storage the workspace's own files; no DBFS root workspace catalog default catalog and default schema default-storage catalogs: CREATE CATALOG, no location ACME'S PROJECT (OPTIONAL) Acme creates; SA has roles catalog storage managed location for acme_supply: managed/ lakehouse bucket external tables and external volumes landing zone raw files for Auto Loader serverless compute reads both Acme's buckets work the same in both: storage credential plus external location. Only the workspace's own storage and its workspace catalog move to default storage in serverless.

the complete storage picture for a classic and a serverless workspace: in both, Unity Catalog in Databricks' control plane holds the storage credential and the external location, and the credential's service account has IAM roles on the buckets Acme creates for catalog managed storage, lakehouse data and landing files; a classic workspace also has three buckets that Databricks creates in Acme's project (system data, the legacy DBFS root and the workspace catalog), while a serverless workspace keeps that workspace storage and its workspace catalog in default storage in Databricks' account

Workspace storage and default storage are not the same thing. It is tempting to think that default storage is just another name for the classic workspace buckets. It is not.

  • Workspace storage (classic) is the two or three buckets in your project. They hold the workspace’s own files: the legacy DBFS root, workspace system data, and the managed files of the workspace catalog.
  • Default storage is “a fully managed object storage platform that provides ready-to-use storage in your Databricks account”. It lives in Databricks’ account, never in your project. “Serverless workspaces use default storage for internal and workspace storage, and for the default catalog that gets created with the workspace.” Classic workspaces use it only behind the scenes, for features such as Clean Rooms and data classification. Only serverless workspaces can create catalogs on it, and “classic compute (any compute that is not serverless) cannot interact with data assets in default storage”.

What is the third bucket for? It is the managed storage location of the workspace catalog. When Unity Catalog is enabled automatically, “Databricks creates a workspace catalog with a managed storage location”. That catalog “typically shares its name with your workspace name”, it is the workspace’s default catalog, and “all workspace users can create managed tables and volumes in the default schema of the workspace catalog”. So if an analyst runs CREATE TABLE test_orders (...) without naming a catalog, the table goes into the workspace catalog: in a classic workspace its files land in that third bucket, and in a serverless workspace in default storage.

Managed and external, first as a cloakroom. Picture a venue with a doorman. The venue has a cloakroom: you hand over your coat, the attendant chooses the hook, and you get it back by your ticket, never by walking to a hook yourself. Hand back the ticket and leave, and the coat is kept for a week in case you return, then disposed of. Some guests instead have their own hook in their own hallway. The doorman decides who may enter that hallway, but if a guest’s name comes off the doorman’s list, the coat stays on the hook. The doorman carries one key that opens the hallways, and a list of which hallway that key may open.

Mapped to Unity Catalog: the cloakroom is a managed location, a coat handed in is a managed table or volume, the ticket is its name, and the week of grace is the recovery window. The private hook is an external table or volume at a path you choose. The doorman is Unity Catalog, his key is the storage credential, and “this key opens this hallway” is an external location.

How does this apply to Acme? Four more terms explain where everything else goes, each with Acme’s name for it.

  • A storage credential holds the identity Databricks uses to reach a bucket. On Google Cloud it is “a Databricks-generated Google Cloud service account”, and you grant that service account IAM roles on your bucket. Acme’s credential is called acme-lake-sc.
  • An external location “combines a cloud storage path with a storage credential that authorizes access to the specified path”, for example gs://acme-lake-supply/ with acme-lake-sc. Acme calls it acme_lake.
  • A managed storage location is a path that you set on a metastore (optional), a catalog or a schema; for a catalog or schema it must sit inside an external location. Managed tables and managed volumes created there are stored under paths that Unity Catalog chooses (<storage-root>/__unitystorage/...), and you use them by name only: “path-based access to Unity Catalog managed tables is not supported”. When you drop a managed table, “Databricks deletes the data files in cloud storage after the recovery period expires (default 7 days)”. Databricks recommends assigning managed storage “at the catalog level for logical data isolation”.
  • An external table or external volume is one you create at a path you choose inside an external location. Dropping it removes only the Unity Catalog entry: “for external tables, you must manually delete the files from your storage bucket”, and an external volume’s data “remains in cloud storage when you drop the volume”.

A volume, if the word is new, is governed storage for files that are not tables, such as Acme’s raw delivery JSON files, reached by a path like /Volumes/acme_supply/supply_bronze/landing/. A managed volume keeps its files in Unity Catalog’s managed storage; an external volume governs an existing bucket path, and the files stay there if you drop the volume.

In cloakroom terms: acme-lake-sc is the doorman’s key, acme_lake says it opens gs://acme-lake-supply/, a managed table such as supply_bronze.deliveries_raw is a coat handed in (Unity Catalog picks the path, Acme uses the name), and the external table supply_service is a coat on Acme’s own hook at gs://acme-lake-supply/supply_service/.

For a managed object, the location is resolved from the bottom up: the schema’s managed location if it has one, otherwise the catalog’s, otherwise the metastore’s.

WHERE DOES A NEW TABLE OR VOLUME LAND? CREATE TABLE or CREATE VOLUME a LOCATION or path given? must be inside an external location EXTERNAL stays at your path; DROP keeps the files yes no: MANAGED schema has a managed location? use it else the catalog's managed location else the metastore's (optional; none in new workspaces) Managed files go under <storage-root>/__unitystorage/... and are read by name only; DROP deletes them after the recovery period (7 days by default). Databricks recommends managed storage per catalog.

where Unity Catalog puts a new table or volume: if you give a path inside an external location, it is external and stays at that path; otherwise it is managed and goes to the schema's managed location, or else the catalog's, or else the metastore's

Putting the four kinds of storage side by side:

FOUR KINDS OF STORAGE Storage Where it lives Who owns it What goes there On DROP Workspace storage buckets (classic only) 2 or 3 buckets in the workspace's Google Cloud project your project; Databricks manages the contents DBFS root, system data, the workspace catalog's files not dropped; deleting them breaks the workspace Default storage (serverless workspaces) Databricks' account, not your project Databricks workspace files, the workspace catalog, default-storage catalogs managed data kept 7 days, then purged within 48 hours Managed storage location (yours) a path in your bucket, set on a metastore, catalog or schema you own the bucket; Unity Catalog owns the paths in it managed tables and managed volumes files deleted after the recovery period (7 days by default) External tables and volumes a path you choose in an external location you external tables, external volumes, raw files only the catalog entry goes; the files stay Access to your own buckets always goes through a storage credential (a Databricks-generated Google service account you grant IAM roles to) and an external location (that credential plus a path).

the four kinds of Databricks storage on Google Cloud: workspace storage buckets in your project for classic workspaces, default storage in Databricks' account for serverless workspaces, managed storage locations in your own bucket for managed tables and volumes, and external paths for external tables and volumes; where each lives, who owns it, what goes there and what happens when you drop an object

What can default storage not do, and when should you use your own bucket?

Default storage is zero-setup storage that only Databricks serverless compute can open. Anything that needs to reach the files from outside Databricks serverless belongs in your own bucket.

Think of a hotel room safe. There is nothing to install and it works the moment you arrive, but it opens only through the hotel’s keypad, and you cannot send a courier with their own key to collect from it. A safe deposit box at your own bank takes an afternoon to arrange, but you decide who may collect. The hotel is your Databricks account, the keypad is serverless compute, the couriers are classic clusters, BigQuery and other engines, and the bank box is a bucket in your project.

Default storage is the easiest place to start in a serverless workspace, because there is nothing to set up. That convenience comes with rules, and they decide whether a catalog belongs there or in a bucket you own. All of them come from the default storage page (checked 8 November 2026) and the serverless workspaces page.

How it behaves.

  • Only serverless workspaces can create catalogs on it. You run CREATE CATALOG without a MANAGED LOCATION, or tick “Use default storage” in Catalog Explorer.
  • Only serverless compute can use it. “All interactions with default storage require serverless, Unity Catalog-enabled compute.” Serverless compute means compute Databricks runs in its own account: serverless notebooks, jobs, Lakeflow pipelines and SQL warehouses.
  • It holds managed tables and managed volumes, with the same Unity Catalog privileges as any other catalog.
  • It is bound to its workspace by default. Workspace-catalog binding (a setting that limits which workspaces may use a catalog) can open it to other workspaces, even classic ones, but they “must use serverless compute to access data in the catalog”.
  • Dropped data goes quickly. After a drop, the files are kept for the 7-day recovery window and then “permanently deleted (purged) within 48 hours”. Billing stops when the window ends.
  • Encryption and billing work differently from your buckets. If you use customer-managed keys, the managed services key “encrypts data in the workspace catalogs and the workspace root storage”. Usage appears in system.billing.usage, and because default storage is “not deployed in your cloud account, your cloud-provider credits or discounts do not apply to this usage”.

What it cannot do.

  • Classic compute cannot reach it. “Classic compute (any compute that is not serverless) cannot interact with data assets in default storage.”
  • Outside engines cannot read the files. External Iceberg and Delta clients “cannot directly access the underlying metadata, manifest list, and data files”, and credential vending (Unity Catalog handing an outside engine short-lived credentials so it can read the files itself) “is not supported”. BI tools still work through the ODBC and JDBC drivers, and volumes through the Files API.
  • BigQuery cannot see it. Google’s cross-cloud setup page supports only Unity Catalogs “that use an external location”, not those on default storage. Part 8 covers that path.
  • Sharing is narrower. OpenSharing (formerly Delta Sharing) can share tables to any recipient, but other assets only to Databricks recipients on the same cloud using serverless compute, never tables with partitioning, and never to external Iceberg clients.
  • One network catch. With front-end Private Service Connect (private connectivity from users to the workspace), ODBC queries on default storage tables that return more than about 5 MiB fail, because the driver’s Cloud Fetch optimisation (downloading large results straight from storage) does not support that path yet.

How does this apply to Acme? A worked example. Suppose Acme’s marketing analysts get a serverless workspace and someone creates a sandbox catalog on default storage (a hypothetical; Acme’s real data stays in acme_supply):

-- Serverless workspace: no MANAGED LOCATION, so the catalog uses default storage
CREATE CATALOG acme_sandbox;
CREATE TABLE acme_sandbox.default.campaign_test (campaign STRING, clicks INT);

Here is what happens when four different things try to use that table:

1. Serverless notebook: SELECT * FROM acme_sandbox.default.campaign_test
   -> works; serverless compute, Unity Catalog grants checked as usual
2. Power BI over the Databricks ODBC driver
   -> works; the query runs on a SQL warehouse and only results travel
3. Nightly job on a classic job cluster reading the same table
   -> fails; classic compute cannot interact with default storage
4. BigQuery analyst querying it through cross-cloud Lakehouse access
   -> not available; only catalogs on an external location are supported

The first two work because Databricks serverless compute does the reading. The last two need something outside Databricks serverless to reach the data, and default storage does not allow that. If Acme wants the table in BigQuery or in a classic job, it belongs in a catalog like acme_supply, whose managed location is Acme’s own bucket.

WHO CAN REACH THE DATA? acme_sandbox on default storage acme_supply gs://acme-lake-supply Serverless notebook, job, pipeline or SQL warehouse ✓ yes ✓ yes Power BI or Tableau over ODBC or JDBC ✓ yes ✓ yes Files in a volume through the Files API ✓ yes ✓ yes Classic job cluster (not serverless) ✗ no ✓ yes BigQuery cross-cloud access (Google Cloud Lakehouse) ✗ no ✓ yes External Iceberg or Delta client, credential vending ✗ no ✓ yes Grants still apply everywhere: a yes means the path exists, not that every user may use it. acme_sandbox is a hypothetical catalog; acme_supply is the catalog used through this post.

who can reach a catalog on default storage compared with a catalog in your own bucket: serverless compute, BI tools over ODBC or JDBC and the Files API reach both; classic clusters, BigQuery cross-cloud access, external Iceberg or Delta clients and credential vending reach only the catalog in your own bucket

Which do I need? Four quick tests, decided per catalog rather than per workspace:

  • A training catalog that only serverless notebooks touch: default storage is fine.
  • Gold tables that BigQuery analysts query through cross-cloud access: your own bucket.
  • Data that a classic job cluster still reads overnight: your own bucket.
  • Regulated data where the customer must control retention, lifecycle rules and bucket audit logs: your own bucket.

As a litmus test, asked per catalog with the cheapest option first:

1. Will only Databricks serverless compute ever read or write it?       no  -> your own bucket
2. Must you control retention, lifecycle rules or bucket audit logs?   yes -> your own bucket
3. Only if question 1 is yes and question 2 is no: default storage, with no setup at all

My view: start on default storage if it gets a team moving, but put anything shared, regulated or read outside Databricks in your own bucket from day one. Changing a catalog’s managed location later “affects only new managed tables and volumes created after the change”, as Can you move a managed location later? explains.

What does a classic workspace put in your project?

Part 1 listed the workspace storage buckets among the things workspace creation leaves behind. Here they are in depth.

A classic workspace has three workspace storage buckets in your project. They belong to the workspace, not to any table: Databricks creates them in the workspace’s Google Cloud project when the workspace is created, one set per workspace.

  • databricks-<workspace id>: the root storage for DBFS (Databricks File System), a legacy file system under dbfs:/ that Databricks no longer recommends for storing data.
  • databricks-<workspace id>-system: workspace system data, “internal data generated by Databricks features”, such as job run results, SQL query results, notebook revisions and cluster logs.
  • A third bucket, if the workspace was enabled for Unity Catalog automatically, holding the managed storage of the workspace catalog, where all users of the workspace can create assets in its default schema. The documentation does not give this bucket a fixed name.

The documentation is blunt about them: do not delete or modify them, because “if workspace storage is deleted, the workspace cannot be recovered”. (The bucket names come from the workspace creation guide.) Your own table data normally lives elsewhere: in buckets you register in Unity Catalog as external locations (a bucket path plus a storage credential, which for Cloud Storage is a service account), or in the managed storage location you set for a metastore, catalog or schema. A serverless workspace has none of these buckets in your project. It uses default storage, “a fully managed storage location for internal workspace system data and Unity Catalog data assets”, and can still connect to your own buckets. What are managed storage, external storage and default storage? puts all of these side by side.

INSIDE ACME-DBX-PROD: THE WORKSPACE STORAGE BUCKETS databricks-<workspace-id> DBFS root (legacy) dbfs:/ paths, FileStore, Hive warehouse default every workspace user can read what is stored here databricks-<workspace-id>-system workspace system data notebook revisions, job run details, command results and Spark logs (used by Databricks itself) workspace catalog bucket only if UC auto-enabled managed storage of the workspace catalog; any user can create tables in its default schema Never delete or modify these buckets: "if workspace storage is deleted, the workspace cannot be recovered". They are created per workspace and named after its ID. do review IAM members (check IAM Conditions) enable Data Access audit logs on them keep business data in your own buckets switch off the legacy part workspace: Disable DBFS root and mounts account: legacy features off and locked for every new workspace since 8 Oct 2026

the three workspace storage buckets of a classic workspace and what is in each: databricks-<workspace-id> is the legacy DBFS root, readable by every workspace user and deprecated for data; databricks-<workspace-id>-system holds workspace system data such as notebook revisions, job run details, command results and Spark logs; a third bucket holds the managed storage of the workspace catalog when Unity Catalog was enabled automatically; never delete or modify them, review their IAM members, enable Data Access audit logs, and disable DBFS root and mounts once nothing uses them

How should you look after them? The workspace creation guide asks for three things, and they are worth doing on day one.

  1. Review who can reach them. Open each bucket’s permissions and check every member. Some entries, such as the one named “Databricks service account for workspace”, carry IAM Conditions (rules that limit a grant to certain resources) that restrict them to certain buckets; the console does not evaluate the condition, so it can show access that does not really exist. When you grant Cloud Storage roles at project level or above, use IAM Conditions to exclude the Databricks buckets, and prefer narrow roles such as Storage Viewer over Storage Admin.
  2. Turn on Data Access audit logs for these buckets. Databricks “strongly recommends” it, while noting that it adds to the Google Cloud bill.
  3. Keep business data out of them. The DBFS root bucket “is not intended for storage of production customer data”.

Two more facts help in operations. The workspace admin settings have a Storage section where you can permanently purge deleted notebooks and other workspace objects, notebook revision history and cluster logs, which is how you remove that data from workspace storage on purpose. And if you use customer-managed keys, the workspace storage key encrypts these buckets; that belongs to Part 6.

What is DBFS, and why is it legacy?

DBFS root is a shared drawer that everyone in the workspace can open; a volume is a locked cabinet with a sign-out log. That difference in governance is why Databricks deprecated DBFS.

DBFS stands for Databricks File System: the layer that lets code on a cluster use paths such as dbfs:/FileStore/report.csv as if cloud storage were a local disk. It has two parts. The DBFS root is the dbfs:/ area backed by the databricks-<workspace id> bucket. DBFS mounts are the dbfs:/mnt/<name> paths that attach other buckets with stored credentials.

Both are deprecated. The DBFS page says “Both DBFS root and DBFS mounts are deprecated and not recommended by Databricks. New accounts are provisioned without access to these features.” The reason is governance: “the DBFS root is accessible to all users in a workspace”, so anything stored there is readable by everyone, and neither the root nor a mount goes through Unity Catalog’s grants, audit or lineage. Volumes replace both. The dbfs: scheme itself lives on, because dbfs:/Volumes/... is an accepted way to write a volume path.

In office terms: a shared drawer is convenient, but nobody can tell who took a file or stop a colleague from reading it. A locked cabinet with a sign-out log answers both questions. The drawer is the DBFS root (and a mount is a drawer wired to another room), the cabinet is a volume, the lock is Unity Catalog’s grants, and the sign-out log is its audit and lineage.

How do you switch it off? There are two settings.

  • The account setting Disable legacy features creates new workspaces without DBFS root and mounts, the Hive metastore, no-isolation shared clusters and Databricks Runtime versions before 13.3 LTS. As of 8 November 2026 the page marks the setting deprecated: “Starting October 8, 2026, Databricks provisions all new workspaces in all accounts without access to legacy features”, and the setting “is enabled and locked, and you can’t change it”. So any workspace Acme creates now starts without them; only older workspaces need the next step.
  • For an existing workspace, an admin can disable DBFS root and mounts. After that every interface blocks them (UI, APIs, CLI and FUSE, the local file path), reads and writes fail with errors such as Public DBFS root is disabled, mounts and FileStore stop working, and runtimes below 13.3 LTS are disabled. Volumes keep working.

So the order for an older workspace is: move files and libraries to volumes and workspace files, point Hive metastore tables at Unity Catalog, then flip the setting. How does this apply to Acme? Acme’s workspace was created recently, so it never had DBFS root access in the first place.

How do you connect Unity Catalog to a bucket?

Unity Catalog holds the only key to the bucket, and people ask Unity Catalog, never the bucket. Everything in this section sets up that one arrangement.

Picture an office building whose storeroom belongs to the landlord. Instead of cutting a key for every employee, the landlord gives one key to the facilities firm, and the facilities desk keeps a list of who may ask for what and logs every request. The landlord’s lock only knows about the firm; the desk’s list knows about people. The landlord is Google Cloud IAM on the bucket, the firm’s key is the storage credential’s service account, “this key opens this storeroom” is the external location, and the desk with its list and log is Unity Catalog with its grants and audit. Where teams go wrong is cutting extra keys: granting analysts a storage role on the bucket “so their queries work”. Queries never need it, and it lets people walk past the desk, as the end of this section explains.

Part 2 explained why this path is keyless: Databricks creates the service account in its own project and impersonates it, so no JSON key ever exists. How does this apply to Acme? Here are the five steps in full, on Acme’s bucket: gs://acme-lake-supply is the storeroom, acme-lake-sc the firm’s key, acme_lake the pairing, and groups such as supply-engineers are on the desk’s list.

CONNECTING A BUCKET TO UNITY CATALOG 1 storage credential in Databricks: GCP service account type name: acme-lake-sc 2 copy the email Databricks generates the service account in its own project; no key 3 grant roles on bucket in Google Cloud, on gs://acme-lake-supply: Legacy Bucket Reader + Object Admin (+ events) 4 external location acme_lake = gs://acme-lake-supply/ + acme-lake-sc; test it 5 grant privileges CREATE MANAGED STORAGE, CREATE EXTERNAL VOLUME, READ FILES to groups Sam runs a query Unity Catalog checks his grants; the credential's account reads the bucket Steps 1, 2, 4 and 5 happen in Databricks; step 3 is the only Google Cloud change. Users never get IAM roles on the bucket: Unity Catalog is the gate. The credential and the location can be bound to specific workspaces and marked read-only.

connecting Unity Catalog to Acme's bucket in five steps: create the storage credential acme-lake-sc in Databricks, which generates a Google service account in Databricks' project with no key; copy its email; grant it Storage Legacy Bucket Reader and Storage Object Admin on gs://acme-lake-supply, plus the file events role; create the external location acme_lake for the bucket with that credential and test it; grant Unity Catalog privileges to groups; from then on Sam's queries are checked by Unity Catalog and the credential's service account reads the bucket

Step 1: create the storage credential. In Catalog Explorer, choose Create a credential and the type GCP Service Account, or use Terraform. You need the CREATE STORAGE CREDENTIAL privilege on the metastore, which account admins and metastore admins have by default. Two options are worth knowing: Limit to read-only use, which makes every external location built on this credential read-only, and workspace binding, which lets only chosen workspaces use the credential. The storage credential management page notes that administrators often bind production credentials to production workspaces.

Step 2: copy the service account email. Databricks shows it when the credential is created. It is a Google service account in Databricks’ project; you only grant it roles.

Step 3: grant it roles on the bucket. This is the only change on the Google Cloud side. The external location guide asks for two roles on the bucket: Storage Legacy Bucket Reader (roles/storage.legacyBucketReader, which lets it list objects and read bucket metadata) and Storage Object Admin (roles/storage.objectAdmin, which lets it read, write and delete objects). With the gcloud CLI:

UC_SA=<email shown when the credential is created>
for ROLE in roles/storage.legacyBucketReader roles/storage.objectAdmin; do
  gcloud storage buckets add-iam-policy-binding gs://acme-lake-supply \
    --member="serviceAccount:$UC_SA" --role="$ROLE"
done

The same page recommends also granting the permissions for file events, which the next section explains: a custom role on the bucket’s project with the pubsub.subscriptions.* and pubsub.topics.* permissions it lists plus storage.buckets.update, granted to the same service account, and the Pub/Sub Publisher role for the project’s Cloud Storage service agent. The Pub/Sub API must be enabled in that project.

Step 4: create the external location. You need CREATE EXTERNAL LOCATION on both the metastore and the credential. Paths may contain only standard ASCII characters. Databricks tests access when you create it, and you can check by listing the path from a notebook with dbutils.fs.ls('gs://acme-lake-supply/').

Step 5: grant Unity Catalog privileges. Nobody can use an external location until you grant privileges on it. The ones that matter, from the privileges reference, are CREATE MANAGED STORAGE (use a path here as a managed location for a catalog or schema), CREATE EXTERNAL TABLE and CREATE EXTERNAL VOLUME, and READ FILES and WRITE FILES (read or write files directly by path). Grant them to groups, never to individuals.

Steps 4 and 5 in SQL look like this. I have not run these exact statements:

-- Step 4: register the bucket as an external location
CREATE EXTERNAL LOCATION IF NOT EXISTS acme_lake
  URL 'gs://acme-lake-supply/'
  WITH (STORAGE CREDENTIAL `acme-lake-sc`)
  COMMENT 'Acme supply lake, europe-west2';

-- Step 5: who may do what with it
GRANT CREATE MANAGED STORAGE ON EXTERNAL LOCATION acme_lake TO `data-platform-admins`;
GRANT CREATE EXTERNAL TABLE, CREATE EXTERNAL VOLUME ON EXTERNAL LOCATION acme_lake TO `supply-engineers`;
GRANT READ FILES ON EXTERNAL LOCATION acme_lake TO `supply-engineers`;

The backticks around acme-lake-sc are needed because the name contains hyphens. In Terraform, the whole chain is three Databricks resources and one Google resource, following the databricks_storage_credential and databricks_external_location documentation (not run here):

resource "databricks_storage_credential" "lake" {
  name = "acme-lake-sc"
  databricks_gcp_service_account {}            # Databricks creates the service account
}

resource "google_storage_bucket_iam_member" "lake" {
  for_each = toset(["roles/storage.legacyBucketReader", "roles/storage.objectAdmin"])
  bucket   = "acme-lake-supply"
  role     = each.value
  member   = "serviceAccount:${databricks_storage_credential.lake.databricks_gcp_service_account[0].email}"
}

resource "databricks_external_location" "lake" {
  name            = "acme_lake"
  url             = "gs://acme-lake-supply/"
  credential_name = databricks_storage_credential.lake.id
  depends_on      = [google_storage_bucket_iam_member.lake]
}

Users never get roles on the bucket. This is the point of the whole design. The Unity Catalog cloud storage overview warns: “Do not give non-Unity Catalog identities storage-level access to Unity Catalog managed tables or volumes.” Anyone with direct bucket access can bypass Unity Catalog’s grants, is invisible to its audit and lineage, and can corrupt tables by writing outside Databricks. The only identity with IAM on gs://acme-lake-supply should be the credential’s service account.

A side note for completeness: on Google Cloud, Unity Catalog can also register Amazon S3 buckets for cross-cloud read-only access and Cloudflare R2 buckets, which the documentation positions mainly for sharing data without egress fees. Cloud Storage is the right choice for nearly everything else.

What are file events, and why turn them on?

File events replace listing a folder over and over with one notification per new file. They grant no new access to your data, and they spare Auto Loader and file arrival triggers from listing big folders.

Compare checking your letterbox every five minutes with fitting a doorbell. Checking works, but the more post you get, the longer each check takes, and most checks find nothing new. A doorbell tells you the moment something arrives. Listing a path is checking the letterbox; a Cloud Storage notification to Pub/Sub is the doorbell; and with file events Databricks installs one doorbell per external location and keeps a note of every delivery.

Auto Loader is the Databricks feature that ingests new files from a path incrementally, and file arrival triggers start a job when files land. Both need to discover new files. Listing a big folder over and over is slow, so cloud storage can instead send a notification for every new object. On Google Cloud, a Cloud Storage notification publishes a message to a Pub/Sub topic (Google’s messaging service), and a subscriber reads it.

With file events, Databricks sets up and manages that pipeline once per external location, using the permissions from step 3, and caches the file metadata. Auto Loader streams that set cloudFiles.useManagedFileEvents read from this cache instead of listing. One shared queue serves every stream on the bucket, which avoids the per-bucket notification limits of the older per-stream approach. Databricks recommends running such streams at least once every seven days; otherwise the stored position expires and the stream falls back to a full listing. The external location guide stresses that the permissions “do not grant any new access to your data; file contents are never transmitted through this infrastructure”.

File events are on by default for new external locations, and validation fails if the permissions are missing. You can force-create the location without them, but you then lose those features for that location.

How does this apply to Acme? The distribution centres drop delivery files into landing/, the external volume supply_bronze.landing inside the external location acme_lake. A stream that ingests that folder with cloudFiles.useManagedFileEvents (an illustration; this part does not build it) reads new file names from the cached events instead of listing every file the centres have ever dropped, and keeps working that way as long as it runs at least weekly.

Where do Acme’s tables and volumes land?

Wherever their managed location or external path points. They do not go into “the fourth bucket” automatically; you send them there. Here is the SQL that sets up Acme’s catalog, from the managed storage page, the CREATE VOLUME reference and the external tables page (not run here):

-- Catalog with its own managed location (inside external location acme_lake)
CREATE CATALOG acme_supply
  MANAGED LOCATION 'gs://acme-lake-supply/managed';

CREATE SCHEMA acme_supply.supply_bronze;
CREATE SCHEMA acme_supply.supply_silver;
CREATE SCHEMA acme_supply.supply_gold;

-- Managed table: no path, Unity Catalog picks it under managed/__unitystorage/...
CREATE TABLE acme_supply.supply_bronze.deliveries_raw (
  delivery_id STRING, dc STRING, sku STRING, units INT, delivered_at TIMESTAMP);

-- External volume over the folder where distribution centres drop files
CREATE EXTERNAL VOLUME acme_supply.supply_bronze.landing
  LOCATION 'gs://acme-lake-supply/landing';

-- External table at a fixed path
CREATE TABLE acme_supply.supply_gold.supply_service (
  brand STRING, region STRING, week_start DATE,
  units_ordered BIGINT, units_delivered BIGINT, otif_pct DOUBLE)
  LOCATION 'gs://acme-lake-supply/supply_service';

So, for Acme:

  • The catalog acme_supply was created with MANAGED LOCATION 'gs://acme-lake-supply/managed', so managed tables such as supply_bronze.deliveries_raw are stored under gs://acme-lake-supply/managed/__unitystorage/....
  • The volume supply_bronze.landing is an external volume at gs://acme-lake-supply/landing/, where the distribution centres drop their delivery files.
  • supply_gold.supply_service is an external table at gs://acme-lake-supply/supply_service/, which keeps a stable path that other engines, such as BigQuery in Part 8, can be pointed at. That is a choice for this example: Iceberg reads work for managed and external tables alike.
  • Anything someone creates in the workspace catalog without a location goes to the third workspace bucket (classic) or to default storage (serverless).

What does that look like inside the bucket? When you set a managed location, Unity Catalog records it as the storage root and adds hashed subfolders so that every managed object gets a unique path: <storage-root>/__unitystorage/catalogs/<uuid> for a catalog, and .../schemas/<uuid> for a schema. Below that, Unity Catalog chooses the path of each table. A quick snapshot of Acme’s bucket:

INSIDE GS://ACME-LAKE-SUPPLY gs://acme-lake-supply/ |-- managed/ storage root of catalog acme_supply | `-- __unitystorage/ added by Unity Catalog | `-- catalogs/<catalog-uuid>/ | `-- ... (path picked by Unity Catalog) managed tables such as deliveries_raw | _delta_log/, part-*.parquet use the table name, never this path |-- landing/ external volume supply_bronze.landing | `-- 2026-05-04/dc-rotterdam.json /Volumes/acme_supply/supply_bronze/landing/ `-- supply_service/ external table supply_gold.supply_service |-- _delta_log/00000000000000000000.json a path that other engines can read `-- part-00000-<uuid>.snappy.parquet Managed and external paths never overlap: managed/ belongs to Unity Catalog, the other two folders are registered paths inside the same external location, acme_lake.

what gs://acme-lake-supply looks like inside: the catalog acme_supply has its storage root at managed/, under which Unity Catalog creates __unitystorage/catalogs/<catalog id>/ and chooses the rest of each managed table's path; landing/ is the external volume supply_bronze.landing with the distribution centres' JSON files; supply_service/ is the external table supply_gold.supply_service with its own _delta_log; managed paths are reached by name only

supply_gold.supply_service is Acme’s gold table, so here is a quick look at what those Parquet files hold. Prerequisite 2 introduces Acme, and the business and data prerequisite explains every column.

ACME_SUPPLY.SUPPLY_GOLD.SUPPLY_SERVICE brand region week_start units_ordered units_delivered otif_pct Acme Pure Western Europe 2026-04-27 14,338 13,691 95.48 Acme Pure Western Europe 2026-05-04 17,475 15,585 89.18 Acme Pure Western Europe 2026-05-11 16,679 14,799 88.73 One row per brand, region and week; files at gs://acme-lake-supply/supply_service/.

supply_gold.supply_service: one row per brand, region and week, owned by supply, in Databricks; stored as an external Delta table at gs://acme-lake-supply/supply_service/

The same holds in a serverless workspace. acme_supply still points at Acme’s bucket, so its tables and volumes stay in Acme’s project; only the workspace’s own storage and the workspace catalog move to default storage.

Compute and storage sit in different places. Classic compute runs in your project; serverless compute runs in Databricks’ account. Both read and write gs://acme-lake-supply through the same storage credential, so Acme can move a job from classic to serverless without moving any data. The data bucket does not have to be in the workspace project; it only needs to grant the credential’s service account access. How the bytes travel, over Private Google Access for classic and over Google’s backbone for serverless (never the public internet in either case), is the subject of Part 3 and Part 4.

SAME DATA BUCKET, DIFFERENT WORKSPACE STORAGE CLASSIC WORKSPACE DATABRICKS' ACCOUNT control plane default storage feature data only; classic compute cannot read it ACME'S GOOGLE CLOUD PROJECT(S) classic compute GCE VMs in the VPC databricks-<id> DBFS root databricks-<id>-system system data workspace catalog its managed files workspace storage buckets, created with the workspace gs://acme-lake-supply (Acme's bucket) managed/__unitystorage/... acme_supply managed tables landing/ external volume landing supply_service/ external table supply_service as the storage credential's service account SERVERLESS WORKSPACE DATABRICKS' ACCOUNT control plane serverless compute VMs in Databricks' account default storage workspace system data notebooks and files the workspace catalog catalogs created on default storage ACME'S GOOGLE CLOUD PROJECT no Databricks buckets, no compute gs://acme-lake-supply (Acme's bucket) managed/__unitystorage/... acme_supply managed tables landing/ external volume landing supply_service/ external table supply_service reads Acme's bucket as the storage credential's service account In both, acme_supply's tables and volumes stay in Acme's bucket. Only the workspace's own storage and the workspace catalog move: buckets in Acme's project (classic) or default storage (serverless). Your data bucket can sit in the workspace project or another project you grant access to.

where Acme's objects are stored in a classic and in a serverless workspace: in both, the acme_supply catalog's managed tables, the external landing volume and the external supply_service table live in Acme's bucket gs://acme-lake-supply, reached through the storage credential's service account; what differs is the workspace's own storage and the workspace catalog, which are buckets in Acme's project for classic and default storage in Databricks' account for serverless, and where the compute runs

My view: give every production catalog a managed location in a bucket you own, keep the workspace catalog for experiments, and treat the workspace storage buckets as Databricks’ internals that nobody should touch.

Can you move a managed location later?

Yes, with limits. ALTER CATALOG ... SET MANAGED LOCATION and ALTER SCHEMA ... SET MANAGED LOCATION need Databricks Runtime 18.1 or above (or Databricks SQL), and “affects only new managed tables and volumes created after the change. Existing managed objects are not moved.” The location string, including gs://, must be 150 characters or fewer. In Terraform, changing storage_root on a databricks_catalog forces a new catalog, which is rarely what you want. Choose the catalog’s location carefully on day one.

Managed or external?

Choose managed unless something outside Databricks must reach the files by path, or the data already sits in a format managed tables do not support.

The beginner question is: if both kinds live in my bucket, why does it matter which I choose? It matters because it decides who owns the files. In the cloakroom from earlier, handing in your coat means the venue looks after it and clears it away when you leave; your own hook means nobody tidies up for you, but anyone you let into the hallway can reach it.

MANAGED OR EXTERNAL? Managed table or volume External table or volume Where the files live under the managed location, at a path Unity Catalog chooses a path you choose inside an external location How you use it by name only: path-based access to managed tables is not supported by name, or by gs:// or /Volumes path; other engines can read files Table formats Delta Lake or Apache Iceberg Delta, CSV, JSON, Avro, Parquet, ORC or text Maintenance predictive optimization runs OPTIMIZE, VACUUM and ANALYZE you schedule OPTIMIZE and VACUUM yourself DROP TABLE files deleted after the recovery period (default 7 days); UNDROP only the catalog entry goes; the files stay until you delete them DROP VOLUME files deleted after 7 days; UNDROP VOLUME within 7 days files remain in cloud storage Choose it when by default: Databricks owns the layout and the lifecycle the data already exists, or other systems write or read the files Databricks recommends managed tables in most cases; Acme uses external only for landing/ and supply_service/.

managed versus external tables and volumes in Unity Catalog: managed objects live at a path Unity Catalog chooses under the managed location and are used by name only, support Delta Lake and Iceberg and are maintained by predictive optimization, and their files are deleted after the recovery period when dropped; external objects live at a path you choose inside an external location, can be read by path and by other engines, support more file formats, need your own maintenance, and keep their files when dropped

The managed tables page calls them “the default and recommended table type”, and says they “cost less to store and query, maintain and optimize themselves automatically”. Some features exist only for managed tables, including predictive optimization, which runs OPTIMIZE, VACUUM and ANALYZE for you and is on by default for accounts created on or after 11 November 2024. External tables still have two clear uses, per the external tables page: registering existing data in a format managed tables do not support, such as JSON or Avro, and giving non-Databricks clients direct access to the files. If another engine changes an external table’s metadata, Databricks recommends running MSCK REPAIR TABLE <table> SYNC METADATA to bring Unity Catalog up to date.

For volumes, the volumes page makes the same split: managed volumes are the “simplest option for Databricks-only workloads”, and external volumes add governance “where data already resides, without requiring a data copy”. Volumes need Databricks Runtime 13.3 LTS or above, and you cannot register files in a volume as a table.

A litmus test, cheapest to run first:

1. Does a managed table or managed volume do the job?                      yes -> managed
2. Does a client outside Databricks need to read the files by path?        yes -> external
3. Does the data already live somewhere, in a format such as JSON or Avro? yes -> external
Only then is external the answer, and you take on maintenance and deletion.

How does this apply to Acme? supply_bronze.deliveries_raw passes question 1, so it is managed. The landing volume is external because the distribution centres already write files to gs://acme-lake-supply/landing/, which is question 3. supply_service is external by choice, for a stable path, even though, as noted above, Iceberg reads would work for a managed table too.

What happens when you drop a table or a volume?

Dropping a managed table starts a countdown to deletion; dropping an external table only removes its name. Neither lowers the storage bill on the day you drop, which is the misconception that causes most surprises.

Deleting a file on a laptop works the same way. It first goes to the recycle bin, where you can restore it. Later, an automatic clean-up empties the bin, at a time you do not choose. If the office then keeps the emptied rubbish in a bin by the door for a week before collection, it can still be fished out, and it still takes space. And deleting a desktop shortcut never touches the file it points to. The recycle bin is the recovery window and restore is UNDROP; the automatic clean-up is the purge; the bin by the door is Cloud Storage soft delete; the shortcut is an external table.

This is where managed and external really differ, and where storage bills surprise people. The object storage lifecycle page describes three phases for a managed table or volume in your own bucket.

  1. Recovery window. For 7 days after the drop, Unity Catalog keeps the files and the metadata. You can bring back a table with UNDROP (and, on recent runtimes, a volume with UNDROP VOLUME), and storage billing continues.
  2. Purge. After the window, an asynchronous process deletes the files. It does not run at once: it “can delete the data files at any point from 7 days up to about 30 days after the delete”.
  3. Your bucket’s own policies. Once Unity Catalog deletes the files, soft delete, Object Versioning or lifecycle rules on the bucket may keep them longer, and Google Cloud bills for them.
AFTER DROP: WHEN DO THE BYTES REALLY GO? day 0 day 7 up to ~day 30 + 7 days managed table in your bucket recovery window UNDROP works purge runs at some point: files deleted from bucket Cloud Storage soft delete keeps them, still billed managed volume in your bucket recovery window UNDROP VOLUME purge runs at some point: files deleted from bucket Cloud Storage soft delete keeps them, still billed external table or volume only the Unity Catalog entry is removed; the files stay in the bucket and are billed until you delete them yourself Storage is billed by Google Cloud through every coloured phase. Soft delete applies only if the bucket has it on (the default is 7 days). Catalogs and schemas cannot be undropped, so DROP CATALOG and DROP SCHEMA default to RESTRICT: they refuse to drop non-empty objects.

what happens after DROP, on a timeline that is not to scale: for a managed table in Acme's bucket, the files are kept for a 7-day recovery window in which UNDROP works, then an asynchronous purge deletes them at any point up to about 30 days after the drop, then Cloud Storage soft delete keeps the deleted objects for 7 more days by default and bills for them; a managed volume follows the same file lifecycle and, on Databricks Runtime 19.11 or above, can be recovered with UNDROP VOLUME within 7 days; for an external table or volume only the metadata goes and the files stay until you delete them

Three details catch people out. First, not everything comes back. Tables, materialized views and streaming tables return with UNDROP, and, as of 8 November 2026, volumes return too: the lifecycle page now says to “use UNDROP VOLUME within 7 days of the delete”, and the UNDROP VOLUME reference needs Databricks Runtime 19.11 or above and the volume’s ID, because “there is no form that recovers a volume by name”. For a managed volume the files come back too. What stays unrecoverable is “Catalogs, schemas, views, functions, models: Cannot be recovered after delete.” Second, DROP CATALOG and DROP SCHEMA default to RESTRICT, which refuses to drop anything that is not empty; CASCADE drops the contents, each according to its own managed or external rules. Third, for default storage the default storage page says the files are purged within 48 hours after the 7-day window and storage billing stops when the window ends.

How does this apply to Acme? If someone drops the managed table acme_supply.supply_bronze.deliveries_raw, its files under managed/__unitystorage/... stay for 7 days and can come back with UNDROP, are purged somewhere between day 7 and about day 30, and then sit in soft delete for the 7 days Acme set with --soft-delete-duration=7d, billed throughout. If someone drops the external table supply_service, only its Unity Catalog entry goes; the files at gs://acme-lake-supply/supply_service/ stay until someone deletes them.

Here is how Acme finds and recovers a table that someone dropped by mistake, using SHOW TABLES DROPPED (Public Preview) and UNDROP (not run here):

SHOW TABLES DROPPED IN acme_supply.supply_silver;
-- If several dropped tables share a name, use the table ID from the list
UNDROP TABLE acme_supply.supply_silver.deliveries_clean;
UNDROP TABLE WITH ID '<table-id>';

-- Volumes (Databricks Runtime 19.11 or above): find the ID, then undrop by ID
SHOW VOLUMES DROPPED IN acme_supply.supply_bronze;
UNDROP VOLUME WITH ID '<volume-id>';

-- Public Preview: keep dropped gold tables recoverable for 30 days
ALTER SCHEMA acme_supply.supply_gold RETAIN DROPPED TO 30 DAYS;

The recovery period is configurable at catalog or schema level, in Public Preview: 0 hours (no recovery) or 7 to 30 days. A schema setting overrides the catalog setting, and it only applies to tables dropped after you set it. UNDROP restores privileges, columns and properties, but not primary and foreign key constraints, which you add again with ALTER TABLE ADD CONSTRAINT.

How should Acme lay out its buckets?

There is no single right layout, but the rules below keep grants, keys and bills simple. They are my recommendations, built on the documented constraints above.

  • One bucket per environment and data domain, so that IAM, keys and cost reports follow the bucket: gs://acme-lake-supply for production supply data, gs://acme-lake-supply-dev for development. Bind the production credential and external location to the production workspace acme-prod-euw2-supply only.
  • One managed location per catalog, as Databricks recommends, and a schema-level location only when a schema needs its own bucket, for example because it uses a different encryption key.
  • A regional bucket in the workspace’s region, europe-west2, flat namespace, uniform bucket-level access, public access prevention, labels.
  • Landing and tables in separate paths, so that lifecycle rules and Autoclass can apply to landing/ without ever touching a table path. A separate landing bucket is even cleaner if other systems write to it.
  • No metastore-level managed location unless you have a reason. New workspaces enabled for Unity Catalog automatically are created without one, and catalog-level locations make ownership clear.

It also helps to know the resource limits before you design: 1,000 storage and service credentials combined per metastore, 10,000 external locations per metastore, 1,000 catalogs per metastore, 10,000 schemas per catalog, and 10,000 tables and 10,000 volumes per schema. A credential per bucket and an external location per bucket fit comfortably.

What can go wrong?

Most storage problems show up as a failed external location test, a slow or expensive read, or a bill that does not fall when you expect. Here are the ones I see most, with their causes and fixes.

STORAGE TROUBLESHOOTING What you see Likely cause Fix external location test fails: permission denied the credential's service account lacks a role on the bucket grant Storage Legacy Bucket Reader and Storage Object Admin validation fails on file events custom Pub/Sub role or Pub/Sub Publisher on the service agent missing grant both, or force-create the location without file events cannot use a new bucket as an external location it was created with hierarchical namespace, which cannot be turned off create a flat bucket and copy the data across slow reads and data transfer charges the bucket is in another region than the workspace keep buckets in the workspace region (europe-west2) the bill does not drop after DROP TABLE recovery window, purge delay, then soft delete or versioning keep files wait up to ~30 days; tune soft delete on that bucket a table fails: files referenced by the log are gone a lifecycle rule or a person deleted objects under the table no lifecycle deletes on table paths; no direct bucket access for users classic cluster cannot read a default storage catalog classic compute cannot reach default storage use serverless compute for that catalog workspace broken after a project clean-up a workspace storage bucket was deleted not recoverable: label the buckets, restrict storage.buckets.delete

storage troubleshooting for Databricks on Google Cloud: failed external location tests from missing bucket roles or file events permissions, hierarchical namespace buckets that cannot be external locations, cross-region buckets, storage bills that lag behind drops because of the recovery window, purge delay and soft delete, tables broken by lifecycle rules, classic compute that cannot read default storage, and workspaces broken by a deleted workspace storage bucket

  • Deleting a workspace storage bucket breaks the workspace for good. It can look like an unused bucket in your project, so label it and restrict who can delete it.
  • Someone gets “temporary” bucket access for a migration and keeps it. Direct IAM on a managed storage bucket bypasses Unity Catalog’s grants and audit. Use a time-bound IAM Condition, or better, do the work through an external volume.
  • A managed location is set on the wrong catalog. ALTER ... SET MANAGED LOCATION does not move existing tables, so a wrong choice leaves data in two places. Decide locations before the first table is created.
  • An HNS bucket is created by a platform team template. The setting cannot be changed later, so check templates and Terraform modules for hierarchical_namespace before Databricks buckets are created from them.

What would I do now?

  1. Create each Databricks data bucket with the settings in the bucket settings diagram above, and add a check to your infrastructure pipeline that rejects hierarchical namespace, public access and retention policies on those buckets.
  2. Create one storage credential and one external location per bucket, bind them to the right workspaces, grant the two bucket roles and the file events permissions, and grant Unity Catalog privileges to groups only.
  3. Give every production catalog a managed location in a bucket you own, and keep the workspace catalog for experiments.
  4. Find the workspace storage buckets in each workspace project, label them, review their IAM, turn on Data Access audit logs, and disable DBFS root and mounts once nothing depends on them.
  5. Agree a recovery policy: the Unity Catalog recovery period for important schemas and the soft delete duration for each bucket, so that you know how long a dropped table can come back and how long its files are billed.

What did we learn?

  • Cloud Storage gives Delta tables what they need, strong consistency and put-if-absent commits, as long as the bucket is regional in the workspace’s region and has no lifecycle deletes, retention policy or hierarchical namespace on table paths.
  • Hierarchical namespace buckets cannot be Unity Catalog external locations, and Delta does not need them, because a Delta commit creates one log object and never renames a folder.
  • A classic workspace has two or three workspace storage buckets in its project that must never be deleted, and a serverless workspace uses default storage in Databricks’ account instead. Default storage suits catalogs that only serverless compute uses, but classic clusters, BigQuery and other outside engines cannot reach it, so shared or regulated data belongs in your own bucket.
  • Unity Catalog reaches your bucket through a storage credential (a Databricks-generated service account with two roles on the bucket) and an external location (that credential plus a path), and users never need IAM on the bucket.
  • Managed tables and volumes live under a managed location that Unity Catalog controls and resolves from schema to catalog to metastore, while external ones live at paths you choose and keep their files when dropped.
  • Dropping a managed table keeps its files for a 7-day recovery window, then purges them within about 30 days, after which the bucket’s soft delete can keep and bill them for longer.

What comes next?

We now know where every byte of Acme’s lakehouse lives and who can reach it. In Databricks on Google Cloud, Part 6: Governance and security, we go deep into Unity Catalog itself: the privileges and ownership model behind the grants we used here, row filters and column masks, lineage and system tables, customer-managed keys for workspace storage and managed services, secrets, and the audit logs that show who touched what.

Sources

Opinions are my own. Every fact in this post comes from the public documentation linked above. Acme Consumer Brands and its data are fictional.