<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>topic Version 1.1: Data Isolation and Governance within PySpark DataFrame's in Community Articles</title>
    <link>https://community.databricks.com/t5/community-articles/version-1-1-data-isolation-and-governance-within-pyspark/m-p/142602#M898</link>
    <description>&lt;P&gt;&lt;SPAN&gt;See the comments below for a runnable notebook&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Throughout my career I have worked at several companies that handle sensitive data; including PII, PHI, EMR, HIPPA, Class I/II/III FOMC - Internal (FR). One entity I worked at even required a Department Of Justice background check. Engineers handling sensitive data must consider how it may be exposed and develop safeguards to minimize risk.&amp;nbsp; Protecting PII (personally identifiable information) is very important as the number of data breaches and records with sensitive information exposed every day is trending upwards; do you really want your private personal medical details exposed to the internet, or even be partially liable for it due to bad coding and practices?!&amp;nbsp; Per HIPAA, “at rest” and “in motion” must be encrypted; these requirements are the bare minimum and often insufficient, according to&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;the author's opinion. From the perspective of typical Databricks pipeline often that means the cloud storage bucket has default encryption turned on and that’s about it.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;It is the authors opinion that to create truly ethically compliant pipelines there is another requirement: data should be encrypted when “&lt;/SPAN&gt;&lt;STRONG&gt;at rest&lt;/STRONG&gt;&lt;SPAN&gt;”, “&lt;/SPAN&gt;&lt;STRONG&gt;in motion&lt;/STRONG&gt;&lt;SPAN&gt;”, and “&lt;/SPAN&gt;&lt;STRONG&gt;not actively being transformed&lt;/STRONG&gt;&lt;SPAN&gt;”. In other words, if a Databricks Notebook (hereinafter ‘pipeline’) loads patients’ data, does its necessary transformations but none of those transformations are on a patient's first or last name, then at no time during that pipelines lifetime should either of those fields be decrypted.&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Data Governance&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data governance is a framework of policies, processes, roles, and standards that ensures an organization's data is secure, accurate, available, and usable throughout its entire lifecycle, guiding how data is collected, stored, accessed, and disposed of to meet business goals and comply with regulations. It acts as a central system, defining who can do what with data, ensuring data quality, preventing inconsistencies, and building trust in data assets for better decision-making&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Schema Governance&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Ditto, but with Spark schemas. Scala’s Spark implementation has a class called &lt;/SPAN&gt;&lt;A href="https://spark.apache.org/docs/latest/sql-programming-guide.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Dataset[T]&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, it implements dataset schema governance through a combination of robust type-safety mechanisms like case classes and Scala’s strong typing language.&amp;nbsp; Using Dataset[T] in Scala, it is super easy to enforce strict or non-strict compliance as interfaces on methods.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Example Spark StructType defined in JSON&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="javascript"&gt;{
 "fields": [
  {
   "name": "ID",
   "nullable": false,
   "type": "string",
   "metadata": {"PK":true, "comment": "PK", "validation":{"regex_match":"/d{10}"}}
  },
  {
   "name": "FirstName",
   "nullable": false,
   "type": "string",
   "metadata": {“PII”:true}
  },
  {
   "name": "SSN",
   "nullable": false,
   "type": "string",
   "metadata": {“PII”:true}
  }],
 "type": "struct"
}&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Example of Dataset[T] via CaseClass generated via StructType&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="java"&gt;def limitDatasetToPersonName(peopleDS: Dataset[Person]): Dataset[PersonPHI] = {
💪💪💪
}

def transformData(sourceDS: Dataset[SourceData]): Dataset[TargetData] = {
💪💪💪
}&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Unfortunately, neither Python nor PySpark have these superman abilities; implementing compliance into PySpark will be necessary later.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Data Isolation&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data isolation is the practice of separating data sets to prevent unauthorized access, limit the impact of breaches, and maintain system stability, ensuring one user's or application's data remains invisible and inaccessible to others, even on shared infrastructure like the cloud. It's achieved through logical controls (access rules, authentication) or physical separation (dedicated servers) and is crucial for security, performance, and compliance.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;The Data Isolation Spectrum in Databricks&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data Isolation in Databricks is a multi-layered strategy used to separate data and AI assets to ensure security, compliance, and transaction integrity.&amp;nbsp; Think of isolation as layers, not a single switch. The higher you isolate, the smaller the blast radius.&amp;nbsp; HIPPAs legal compliance can be achieved at any of these levels.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;TABLE&gt;&lt;TBODY&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Level&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Strength&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Use Case&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Account&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Strongest&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Workspace&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Recommended&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Network&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Defense-in-depth&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Storage&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Required&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Compute&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Sensitive jobs&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;UC Catalog&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Governance&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;UC Schema&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Organization&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Table&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Least privilege&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;P&gt;&lt;SPAN&gt;Account and Workspace would appear to be the no-brainer choice (these options only became available in DataBricks late 2020).&amp;nbsp; At time of writing according to &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/resources/limits" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Databricks resource limit&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; document. “&lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;a user can't belong to more than 50 Databricks accounts&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;” (fixed), and each account can have 50 workspaces (not fixed). The number of workspaces is tied more to the underlying cloud provider's subscription limits than a hard limit imposed by Databricks.&amp;nbsp; These limits are on resources like vCPUs, network infrastructure, or the number of virtual machines that can be launched.&amp;nbsp; After increasing the limits on the cloud provider, Databricks can then increase those limits on their end.&amp;nbsp; A quick calculation, each customer will most likely need a QA, DEV, and PROD environment. Hence; for 100 customers, we would need 300 workspaces.&amp;nbsp; An astute engineer would quickly realize that they would want an infrastructure-as-code tool like Terraform first and ensure they can provision 300 workflows first.&amp;nbsp; Here is a &lt;/SPAN&gt;&lt;A href="https://www.databricks.com/blog/2022/03/10/functional-workspace-organization-on-databricks.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Databricks blog&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; regarding best practices in platform architecture that is worth reading.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Safest Interpretation&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;One Databricks account, one workspace per customer&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Dedicated cloud resources (VNet/VPC)&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Unity Catalog scoped to that workspace only&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;No shared metastores or storage locations&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;This is the cleanest HIPAA story for auditors&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Following this alone does not necessarily equate to HIPPA Safe Harbor.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;While multiple workspaces are a recommended pattern for strong isolation (especially dev/staging/prod or different business units), they introduce real operational trade-offs compared to a single (or very few) workspace approach — especially when using Unity Catalog for centralized governance.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;TABLE&gt;&lt;TBODY&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;#&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Disadvantage&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Explanation / Impact&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Severity (typical)&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;1&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Increased administrative &amp;amp; operational overhead&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Every workspace needs separate setup, maintenance, user/group management, cluster policies, IP ACLs, PrivateLink config (if used), etc. Manual management becomes painful quickly.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;2&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Harder to automate &amp;amp; higher IaC complexity&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;You &lt;/SPAN&gt;&lt;STRONG&gt;must&lt;/STRONG&gt;&lt;SPAN&gt; use Infrastructure-as-Code (Terraform / Databricks Asset Bundles / APIs) to keep environments consistent — otherwise chaos ensues.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;3&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Worse collaboration &amp;amp; knowledge sharing&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Notebooks, dashboards, experiments, and jobs &lt;/SPAN&gt;&lt;STRONG&gt;cannot&lt;/STRONG&gt;&lt;SPAN&gt; be easily shared/collaborated on across workspaces. Cross-workspace lineage visibility and discovery are more limited.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium–High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;4&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Higher cost (direct + indirect)&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;More workspaces → more small/medium clusters → potentially higher DBU consumption due to less pooling. Also indirect costs from more admin time and setup.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;5&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Workspace resource limits become relevant&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Each workspace has its own per-workspace limits (concurrent clusters, jobs, users, API rate limits, etc.). Heavy usage in one workspace won't affect others, but you hit limits faster per workspace.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;6&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Account-level workspace limit&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Default hard/soft limit ~50 workspaces per account (enterprise tier). Large orgs with dozens of teams → may need multiple Databricks accounts → even more complexity.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium–High (large orgs)&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;7&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Switching friction for users&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Users must constantly switch workspaces in the UI → annoying for people who need to work across environments/teams.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;8&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;More complex monitoring &amp;amp; observability&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Harder to get a unified view of jobs/clusters/costs/performance across many workspaces (requires account-level tools or extra setup).&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;9&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Slower onboarding &amp;amp; higher learning curve for new users&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;New team members need to understand which workspace does what, how permissions differ, etc.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Low–Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;10&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Some features are harder/more limited&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Certain things (e.g. some legacy feature store patterns, cross-workspace notebook collaboration, shared SQL warehouses) become more complicated or impossible.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Low–Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Unity Catalog not workspaces&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Unity Catalog is the real protector of the data; not workspaces. Data can gain visibility where it should not through multiple holes.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;P&gt;&lt;SPAN&gt;Bottom line as of 2025 is that the sweet spot for most organizations is 3–12 workspaces (typically dev / staging / prod + maybe 1–2 sandboxes or department-specific ones).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Going beyond ~20–30 usually signals the necessity to heavily invest in automation and a good full time DevOps engineer, or two, to own it; question whether every new request really needs its own workspace — many times catalog-level isolation + cluster policies + Unity Catalog bindings is enough.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If you're hitting scaling pain with multiple workspaces today, consider consolidating where possible and leaning harder on Unity Catalog + workspace-catalog bindings + Schema Governance + Encryption UDFs for isolation instead of proliferating more workspaces.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;Data Isolation by BYOK Encryption UDFs&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;For all practical purposes, data isolation is achieved when it cannot be read by an unauthorized entity.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Read more here about why &lt;/SPAN&gt;&lt;A href="https://frsecure.com/blog/hipaa-compliance-for-data-encryption/" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;HIPPA Safe Harbor is the gold standard&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;; if you strictly follow the 18-identifier removal list, HIPAA guarantees that the data is legally de-identified, so the entity cannot be penalized for HIPAA violations for using or sharing that data.&amp;nbsp; HIPAA's encryption is a rule under the HITECH Act that exempts organizations from mandatory breach notification if stolen or lost electronic Protected Health Information (ePHI) is rendered unusable, unreadable, or indecipherable through strong encryption, often aligning with NIST standards (like AES), meaning no breach is deemed to have occurred, provided encryption keys aren't compromised. To qualify, all ePHI, both at rest (stored) and in transit (moving), must be encrypted using robust methods, often requiring strong passcodes and key management, to ensure data remains secure even if devices are lost or accessed without authorization. FYI, there are other details too that are important not mentioned here.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;First, we need the ability to have a mechanism to encrypt and decrypt data in a DataFrame, here is a UDF that gets us there. Next, every customer has a service principal identity with one and only one key registered within the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/gcp/en/security/secrets/example-secret-workflow" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;secret store&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. Under this situation, we have encryption level data isolation, even if one customer got another customers data on accident, they still cannot read it since they lack the correct key to decrypt it.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;from cryptography.fernet import Fernet
from pyspark.sql.functions import udf
from pyspark.sql.types import StringType

# Load once - do this in your notebook or job initialization
FERNET_KEY = dbutils.secrets.get(scope="crypto", key=f"{current_user}-fernet-main-key-2025")
fernet_singleton = Fernet(FERNET_KEY.encode())

def encrypt_fernet(plain_text):
    if plain_text is None:
        return None
    return cipher_suite.encrypt(plain_text.encode()).decode()

def decrypt_fernet(encrypted_text):
    if encrypted_text is None:
        return None
    return cipher_suite.decrypt(encrypted_text.encode()).decode()

def decrypt_columns(df: DataFrame, columns: list[str]) -&amp;gt; DataFrame:
    return df.transform(
        lambda d: d.select(
            *[
                decrypt_fernet(col(c)).alias(c) if c in columns else col(c)
                for c in d.columns
            ]
        )
    )

def encrypt_columns(df: DataFrame, columns: list[str]) -&amp;gt; DataFrame:
    return df.transform(
        lambda d: d.select(
            *[
                encrypt_fernet(col(c)).alias(c) if c in columns else col(c)
                for c in d.columns
            ]
        )
    )&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;Schema Governance in PySpark&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;As mentioned above, PySpark does not have Scala’s Dataset class. Here is a minimal implementation of it in PySpark. Scala being a strong static type language will throw an exception at compile time, best PySpark can do is at runtime, unfortunately.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def Dataset(df, schema:StructType, strict=true) -&amp;gt; Dataframe:
    # Emulates Scala’s Dataset
    if strict and len(schema.fieldNames()) != len(df.columns):
        raise Exception("not equal number of columns between the two")

    for field in schema.fields:
        col_name = field.name
        col_type = field.dataType
        col_null = field.nullable

        if col_name in df.columns:

           if not strict:
                 continue

            # check for existence
            if not (col_name in schema.fieldNames()):
                raise Exception(f"column missing: {col_name}")
            elif col_type != df.schema[col_name].dataType:
                raise Exception(
                    f"column type is incorrect for '{col_name}', expected '{col_type}', got '{df.schema[col_name].dataType}'. "
                )

            elif not col_null:
                null_count = df.filter(df[col_name].isNull()).count()
                if null_count &amp;gt; 0:
                    raise Exception("Null Constraint Violation")
        else:
            raise Exception("Schema Enforcement Failed")

    # reorder to match schema
    return df.select(*schema.fieldNames())

Dataset(my_dataframe, “person”) # will throw an exception if it’s violated&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;It is worth noting that neither StructType.fromJson nor spark.load.schema(xxxx).table(“xxx”) properly loads up the metadata from the StructType given to it; this is a known bug in Spark for a long long time. Below is code to get around it.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def fix_schema(schema:StructType) -&amp;gt; StructType:
    # fix the metadata field after StructType.fromJson
    # StructType.fromJson still has a bug in it where it will not load the metadata portion
    fields = []
    for field_data in struct:
        metadata = [k for k in s_json["fields"] if k["name"] == field_data.name][0]
        metadata = metadata["metadata"] if "metadata" in metadata else {}
        new_field = StructField(
            name=field_data.name,
            dataType=field_data.dataType,
            nullable=field_data.nullable,
            metadata=metadata
        )
        fields.append(new_field)
    return StructType(fields)

s_json = …  # our patient json from above
struct = StructType.fromJson(s_json)
struct = fix_schema(struct)&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Now we need a simple helper method to validate our custom data quality rules; brilliant engineers will realize that they can extend PySparks DataFrame by monkey-patch; but this will work for our demonstration.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def validate_dataframe(df, schema:StructType) -&amp;gt; None:
    if isinstance(schema, StructType):
        pass
    elif schema == None:
        schema = df.schema
     else:
        raise ValueError("parameter schema is of an invalid value")

    for field in schema.fields:
        col_name = field.name
        col_type = field.dataType
        col_null = field.nullable
        col_metadata = field.metadata
        # TODO: use a factory design pattern over if-elses 
         if "validation" in col_metadata:
             validation = col_metadata[“validation”]
      # do regex and see if it works on the value as a super simple check
             if “regex_match” in validation:
                   pattern = validation[“regex_match”] 
                   cnt = df.select(F.regexp_count(F.col(col_name), pattern)).collect()[0][0]
                   if cnt &amp;gt; 0:
                      raise Exception(“failed on regex”)
             if “PII” in col_metadata:
                  # TODO: confirm that the field is encrypted
             if “PK” in col_metadata:
                  # TODO: confirm that the row has an unique value&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Now for the goal of this article, &lt;/SPAN&gt;&lt;SPAN&gt;“&lt;/SPAN&gt;&lt;STRONG&gt;not actively being transformed&lt;/STRONG&gt;&lt;SPAN&gt;”. Again, we only want data to be decrypted when and only when it is actively being worked on.&amp;nbsp; FYI, There will be a performance hit because the encryption algo’s are being run on a per row basis. Let’s get that implemented.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def business_logic(df):
    return (
        df
        .filter(col("ssn").isNotNull())
        .select("id", "ssn")
    )

def with_fernet(
    df: DataFrame,
    columns: list[str],
    fn: Callable[[DataFrame], DataFrame],
) -&amp;gt; DataFrame:
    encrypted_columns  = [c for c in df.schema if “PII” in c or “PHI” in c or (“encrypted” in c and c[“encrypted”])]
    return (
        df
        .transform(lambda d: decrypt_columns(d, encrypted_columns)
        .transform(fn)
        .transform(lambda d: encrypt_columns(d, encrypted_columns))
    )
final_df = with_fernet(df, ["ssn"], business_logic)&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Mon, 29 Dec 2025 02:10:59 GMT</pubDate>
    <dc:creator>joseph_in_sf</dc:creator>
    <dc:date>2025-12-29T02:10:59Z</dc:date>
    <item>
      <title>Version 1.1: Data Isolation and Governance within PySpark DataFrame's</title>
      <link>https://community.databricks.com/t5/community-articles/version-1-1-data-isolation-and-governance-within-pyspark/m-p/142602#M898</link>
      <description>&lt;P&gt;&lt;SPAN&gt;See the comments below for a runnable notebook&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Throughout my career I have worked at several companies that handle sensitive data; including PII, PHI, EMR, HIPPA, Class I/II/III FOMC - Internal (FR). One entity I worked at even required a Department Of Justice background check. Engineers handling sensitive data must consider how it may be exposed and develop safeguards to minimize risk.&amp;nbsp; Protecting PII (personally identifiable information) is very important as the number of data breaches and records with sensitive information exposed every day is trending upwards; do you really want your private personal medical details exposed to the internet, or even be partially liable for it due to bad coding and practices?!&amp;nbsp; Per HIPAA, “at rest” and “in motion” must be encrypted; these requirements are the bare minimum and often insufficient, according to&lt;/SPAN&gt;&lt;SPAN&gt;&amp;nbsp;the author's opinion. From the perspective of typical Databricks pipeline often that means the cloud storage bucket has default encryption turned on and that’s about it.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;It is the authors opinion that to create truly ethically compliant pipelines there is another requirement: data should be encrypted when “&lt;/SPAN&gt;&lt;STRONG&gt;at rest&lt;/STRONG&gt;&lt;SPAN&gt;”, “&lt;/SPAN&gt;&lt;STRONG&gt;in motion&lt;/STRONG&gt;&lt;SPAN&gt;”, and “&lt;/SPAN&gt;&lt;STRONG&gt;not actively being transformed&lt;/STRONG&gt;&lt;SPAN&gt;”. In other words, if a Databricks Notebook (hereinafter ‘pipeline’) loads patients’ data, does its necessary transformations but none of those transformations are on a patient's first or last name, then at no time during that pipelines lifetime should either of those fields be decrypted.&amp;nbsp;&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Data Governance&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data governance is a framework of policies, processes, roles, and standards that ensures an organization's data is secure, accurate, available, and usable throughout its entire lifecycle, guiding how data is collected, stored, accessed, and disposed of to meet business goals and comply with regulations. It acts as a central system, defining who can do what with data, ensuring data quality, preventing inconsistencies, and building trust in data assets for better decision-making&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Schema Governance&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Ditto, but with Spark schemas. Scala’s Spark implementation has a class called &lt;/SPAN&gt;&lt;A href="https://spark.apache.org/docs/latest/sql-programming-guide.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Dataset[T]&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;, it implements dataset schema governance through a combination of robust type-safety mechanisms like case classes and Scala’s strong typing language.&amp;nbsp; Using Dataset[T] in Scala, it is super easy to enforce strict or non-strict compliance as interfaces on methods.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Example Spark StructType defined in JSON&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="javascript"&gt;{
 "fields": [
  {
   "name": "ID",
   "nullable": false,
   "type": "string",
   "metadata": {"PK":true, "comment": "PK", "validation":{"regex_match":"/d{10}"}}
  },
  {
   "name": "FirstName",
   "nullable": false,
   "type": "string",
   "metadata": {“PII”:true}
  },
  {
   "name": "SSN",
   "nullable": false,
   "type": "string",
   "metadata": {“PII”:true}
  }],
 "type": "struct"
}&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Example of Dataset[T] via CaseClass generated via StructType&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="java"&gt;def limitDatasetToPersonName(peopleDS: Dataset[Person]): Dataset[PersonPHI] = {
💪💪💪
}

def transformData(sourceDS: Dataset[SourceData]): Dataset[TargetData] = {
💪💪💪
}&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Unfortunately, neither Python nor PySpark have these superman abilities; implementing compliance into PySpark will be necessary later.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;What is Data Isolation&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data isolation is the practice of separating data sets to prevent unauthorized access, limit the impact of breaches, and maintain system stability, ensuring one user's or application's data remains invisible and inaccessible to others, even on shared infrastructure like the cloud. It's achieved through logical controls (access rules, authentication) or physical separation (dedicated servers) and is crucial for security, performance, and compliance.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;The Data Isolation Spectrum in Databricks&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;Data Isolation in Databricks is a multi-layered strategy used to separate data and AI assets to ensure security, compliance, and transaction integrity.&amp;nbsp; Think of isolation as layers, not a single switch. The higher you isolate, the smaller the blast radius.&amp;nbsp; HIPPAs legal compliance can be achieved at any of these levels.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;TABLE&gt;&lt;TBODY&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Level&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Strength&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Use Case&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Account&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Strongest&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Workspace&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Recommended&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Network&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Defense-in-depth&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Storage&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Required&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Compute&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Sensitive jobs&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;UC Catalog&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Governance&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;UC Schema&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Organization&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Table&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":star:"&gt;⭐&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Least privilege&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;P&gt;&lt;SPAN&gt;Account and Workspace would appear to be the no-brainer choice (these options only became available in DataBricks late 2020).&amp;nbsp; At time of writing according to &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/resources/limits" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Databricks resource limit&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; document. “&lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;a user can't belong to more than 50 Databricks accounts&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;” (fixed), and each account can have 50 workspaces (not fixed). The number of workspaces is tied more to the underlying cloud provider's subscription limits than a hard limit imposed by Databricks.&amp;nbsp; These limits are on resources like vCPUs, network infrastructure, or the number of virtual machines that can be launched.&amp;nbsp; After increasing the limits on the cloud provider, Databricks can then increase those limits on their end.&amp;nbsp; A quick calculation, each customer will most likely need a QA, DEV, and PROD environment. Hence; for 100 customers, we would need 300 workspaces.&amp;nbsp; An astute engineer would quickly realize that they would want an infrastructure-as-code tool like Terraform first and ensure they can provision 300 workflows first.&amp;nbsp; Here is a &lt;/SPAN&gt;&lt;A href="https://www.databricks.com/blog/2022/03/10/functional-workspace-organization-on-databricks.html" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;Databricks blog&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt; regarding best practices in platform architecture that is worth reading.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Safest Interpretation&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;One Databricks account, one workspace per customer&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Dedicated cloud resources (VNet/VPC)&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Unity Catalog scoped to that workspace only&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;No shared metastores or storage locations&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;This is the cleanest HIPAA story for auditors&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;Following this alone does not necessarily equate to HIPPA Safe Harbor.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;While multiple workspaces are a recommended pattern for strong isolation (especially dev/staging/prod or different business units), they introduce real operational trade-offs compared to a single (or very few) workspace approach — especially when using Unity Catalog for centralized governance.&lt;BR /&gt;&lt;/SPAN&gt;&lt;/P&gt;&lt;TABLE&gt;&lt;TBODY&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;#&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Disadvantage&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Explanation / Impact&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;STRONG&gt;Severity (typical)&lt;/STRONG&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;1&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Increased administrative &amp;amp; operational overhead&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Every workspace needs separate setup, maintenance, user/group management, cluster policies, IP ACLs, PrivateLink config (if used), etc. Manual management becomes painful quickly.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;2&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Harder to automate &amp;amp; higher IaC complexity&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;You &lt;/SPAN&gt;&lt;STRONG&gt;must&lt;/STRONG&gt;&lt;SPAN&gt; use Infrastructure-as-Code (Terraform / Databricks Asset Bundles / APIs) to keep environments consistent — otherwise chaos ensues.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;3&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Worse collaboration &amp;amp; knowledge sharing&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Notebooks, dashboards, experiments, and jobs &lt;/SPAN&gt;&lt;STRONG&gt;cannot&lt;/STRONG&gt;&lt;SPAN&gt; be easily shared/collaborated on across workspaces. Cross-workspace lineage visibility and discovery are more limited.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium–High&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;4&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Higher cost (direct + indirect)&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;More workspaces → more small/medium clusters → potentially higher DBU consumption due to less pooling. Also indirect costs from more admin time and setup.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;5&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Workspace resource limits become relevant&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Each workspace has its own per-workspace limits (concurrent clusters, jobs, users, API rate limits, etc.). Heavy usage in one workspace won't affect others, but you hit limits faster per workspace.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;6&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Account-level workspace limit&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Default hard/soft limit ~50 workspaces per account (enterprise tier). Large orgs with dozens of teams → may need multiple Databricks accounts → even more complexity.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium–High (large orgs)&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;7&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Switching friction for users&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Users must constantly switch workspaces in the UI → annoying for people who need to work across environments/teams.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;8&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;More complex monitoring &amp;amp; observability&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Harder to get a unified view of jobs/clusters/costs/performance across many workspaces (requires account-level tools or extra setup).&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;9&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Slower onboarding &amp;amp; higher learning curve for new users&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;New team members need to understand which workspace does what, how permissions differ, etc.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Low–Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;10&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Some features are harder/more limited&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Certain things (e.g. some legacy feature store patterns, cross-workspace notebook collaboration, shared SQL warehouses) become more complicated or impossible.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Low–Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;TR&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;11&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Unity Catalog not workspaces&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Unity Catalog is the real protector of the data; not workspaces. Data can gain visibility where it should not through multiple holes.&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;TD&gt;&lt;P&gt;&lt;SPAN&gt;Medium&lt;/SPAN&gt;&lt;/P&gt;&lt;/TD&gt;&lt;/TR&gt;&lt;/TBODY&gt;&lt;/TABLE&gt;&lt;P&gt;&lt;SPAN&gt;Bottom line as of 2025 is that the sweet spot for most organizations is 3–12 workspaces (typically dev / staging / prod + maybe 1–2 sandboxes or department-specific ones).&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Going beyond ~20–30 usually signals the necessity to heavily invest in automation and a good full time DevOps engineer, or two, to own it; question whether every new request really needs its own workspace — many times catalog-level isolation + cluster policies + Unity Catalog bindings is enough.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;If you're hitting scaling pain with multiple workspaces today, consider consolidating where possible and leaning harder on Unity Catalog + workspace-catalog bindings + Schema Governance + Encryption UDFs for isolation instead of proliferating more workspaces.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;Data Isolation by BYOK Encryption UDFs&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;For all practical purposes, data isolation is achieved when it cannot be read by an unauthorized entity.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;Read more here about why &lt;/SPAN&gt;&lt;A href="https://frsecure.com/blog/hipaa-compliance-for-data-encryption/" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;HIPPA Safe Harbor is the gold standard&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;; if you strictly follow the 18-identifier removal list, HIPAA guarantees that the data is legally de-identified, so the entity cannot be penalized for HIPAA violations for using or sharing that data.&amp;nbsp; HIPAA's encryption is a rule under the HITECH Act that exempts organizations from mandatory breach notification if stolen or lost electronic Protected Health Information (ePHI) is rendered unusable, unreadable, or indecipherable through strong encryption, often aligning with NIST standards (like AES), meaning no breach is deemed to have occurred, provided encryption keys aren't compromised. To qualify, all ePHI, both at rest (stored) and in transit (moving), must be encrypted using robust methods, often requiring strong passcodes and key management, to ensure data remains secure even if devices are lost or accessed without authorization. FYI, there are other details too that are important not mentioned here.&lt;/SPAN&gt;&lt;/P&gt;&lt;P&gt;&lt;SPAN&gt;First, we need the ability to have a mechanism to encrypt and decrypt data in a DataFrame, here is a UDF that gets us there. Next, every customer has a service principal identity with one and only one key registered within the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/gcp/en/security/secrets/example-secret-workflow" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;secret store&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;. Under this situation, we have encryption level data isolation, even if one customer got another customers data on accident, they still cannot read it since they lack the correct key to decrypt it.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;from cryptography.fernet import Fernet
from pyspark.sql.functions import udf
from pyspark.sql.types import StringType

# Load once - do this in your notebook or job initialization
FERNET_KEY = dbutils.secrets.get(scope="crypto", key=f"{current_user}-fernet-main-key-2025")
fernet_singleton = Fernet(FERNET_KEY.encode())

def encrypt_fernet(plain_text):
    if plain_text is None:
        return None
    return cipher_suite.encrypt(plain_text.encode()).decode()

def decrypt_fernet(encrypted_text):
    if encrypted_text is None:
        return None
    return cipher_suite.decrypt(encrypted_text.encode()).decode()

def decrypt_columns(df: DataFrame, columns: list[str]) -&amp;gt; DataFrame:
    return df.transform(
        lambda d: d.select(
            *[
                decrypt_fernet(col(c)).alias(c) if c in columns else col(c)
                for c in d.columns
            ]
        )
    )

def encrypt_columns(df: DataFrame, columns: list[str]) -&amp;gt; DataFrame:
    return df.transform(
        lambda d: d.select(
            *[
                encrypt_fernet(col(c)).alias(c) if c in columns else col(c)
                for c in d.columns
            ]
        )
    )&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;&lt;H2&gt;&lt;SPAN&gt;Schema Governance in PySpark&lt;/SPAN&gt;&lt;/H2&gt;&lt;P&gt;&lt;SPAN&gt;As mentioned above, PySpark does not have Scala’s Dataset class. Here is a minimal implementation of it in PySpark. Scala being a strong static type language will throw an exception at compile time, best PySpark can do is at runtime, unfortunately.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def Dataset(df, schema:StructType, strict=true) -&amp;gt; Dataframe:
    # Emulates Scala’s Dataset
    if strict and len(schema.fieldNames()) != len(df.columns):
        raise Exception("not equal number of columns between the two")

    for field in schema.fields:
        col_name = field.name
        col_type = field.dataType
        col_null = field.nullable

        if col_name in df.columns:

           if not strict:
                 continue

            # check for existence
            if not (col_name in schema.fieldNames()):
                raise Exception(f"column missing: {col_name}")
            elif col_type != df.schema[col_name].dataType:
                raise Exception(
                    f"column type is incorrect for '{col_name}', expected '{col_type}', got '{df.schema[col_name].dataType}'. "
                )

            elif not col_null:
                null_count = df.filter(df[col_name].isNull()).count()
                if null_count &amp;gt; 0:
                    raise Exception("Null Constraint Violation")
        else:
            raise Exception("Schema Enforcement Failed")

    # reorder to match schema
    return df.select(*schema.fieldNames())

Dataset(my_dataframe, “person”) # will throw an exception if it’s violated&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;It is worth noting that neither StructType.fromJson nor spark.load.schema(xxxx).table(“xxx”) properly loads up the metadata from the StructType given to it; this is a known bug in Spark for a long long time. Below is code to get around it.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def fix_schema(schema:StructType) -&amp;gt; StructType:
    # fix the metadata field after StructType.fromJson
    # StructType.fromJson still has a bug in it where it will not load the metadata portion
    fields = []
    for field_data in struct:
        metadata = [k for k in s_json["fields"] if k["name"] == field_data.name][0]
        metadata = metadata["metadata"] if "metadata" in metadata else {}
        new_field = StructField(
            name=field_data.name,
            dataType=field_data.dataType,
            nullable=field_data.nullable,
            metadata=metadata
        )
        fields.append(new_field)
    return StructType(fields)

s_json = …  # our patient json from above
struct = StructType.fromJson(s_json)
struct = fix_schema(struct)&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Now we need a simple helper method to validate our custom data quality rules; brilliant engineers will realize that they can extend PySparks DataFrame by monkey-patch; but this will work for our demonstration.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def validate_dataframe(df, schema:StructType) -&amp;gt; None:
    if isinstance(schema, StructType):
        pass
    elif schema == None:
        schema = df.schema
     else:
        raise ValueError("parameter schema is of an invalid value")

    for field in schema.fields:
        col_name = field.name
        col_type = field.dataType
        col_null = field.nullable
        col_metadata = field.metadata
        # TODO: use a factory design pattern over if-elses 
         if "validation" in col_metadata:
             validation = col_metadata[“validation”]
      # do regex and see if it works on the value as a super simple check
             if “regex_match” in validation:
                   pattern = validation[“regex_match”] 
                   cnt = df.select(F.regexp_count(F.col(col_name), pattern)).collect()[0][0]
                   if cnt &amp;gt; 0:
                      raise Exception(“failed on regex”)
             if “PII” in col_metadata:
                  # TODO: confirm that the field is encrypted
             if “PK” in col_metadata:
                  # TODO: confirm that the row has an unique value&lt;/LI-CODE&gt;&lt;P&gt;&lt;SPAN&gt;Now for the goal of this article, &lt;/SPAN&gt;&lt;SPAN&gt;“&lt;/SPAN&gt;&lt;STRONG&gt;not actively being transformed&lt;/STRONG&gt;&lt;SPAN&gt;”. Again, we only want data to be decrypted when and only when it is actively being worked on.&amp;nbsp; FYI, There will be a performance hit because the encryption algo’s are being run on a per row basis. Let’s get that implemented.&lt;/SPAN&gt;&lt;/P&gt;&lt;LI-CODE lang="python"&gt;def business_logic(df):
    return (
        df
        .filter(col("ssn").isNotNull())
        .select("id", "ssn")
    )

def with_fernet(
    df: DataFrame,
    columns: list[str],
    fn: Callable[[DataFrame], DataFrame],
) -&amp;gt; DataFrame:
    encrypted_columns  = [c for c in df.schema if “PII” in c or “PHI” in c or (“encrypted” in c and c[“encrypted”])]
    return (
        df
        .transform(lambda d: decrypt_columns(d, encrypted_columns)
        .transform(fn)
        .transform(lambda d: encrypt_columns(d, encrypted_columns))
    )
final_df = with_fernet(df, ["ssn"], business_logic)&lt;/LI-CODE&gt;&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
      <pubDate>Mon, 29 Dec 2025 02:10:59 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/version-1-1-data-isolation-and-governance-within-pyspark/m-p/142602#M898</guid>
      <dc:creator>joseph_in_sf</dc:creator>
      <dc:date>2025-12-29T02:10:59Z</dc:date>
    </item>
    <item>
      <title>Re: Version 1.1: Data Isolation and Governance within PySpark DataFrame's</title>
      <link>https://community.databricks.com/t5/community-articles/version-1-1-data-isolation-and-governance-within-pyspark/m-p/142702#M909</link>
      <description>&lt;P&gt;We just failed a HIPAA audit, they asked why our pipelines had patients names in the if the pipeline didnt need that info, they recommended it to be encrypted. We thought S3 encryption was good enough.&lt;/P&gt;&lt;P&gt;We implemented row level encryption by extending the dataframe in Scala.&lt;/P&gt;&lt;P&gt;Samantha Givings, CTO&lt;/P&gt;</description>
      <pubDate>Tue, 30 Dec 2025 16:05:13 GMT</pubDate>
      <guid>https://community.databricks.com/t5/community-articles/version-1-1-data-isolation-and-governance-within-pyspark/m-p/142702#M909</guid>
      <dc:creator>SamanthaGivings</dc:creator>
      <dc:date>2025-12-30T16:05:13Z</dc:date>
    </item>
  </channel>
</rss>

