<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Neeraj Agarwal</title>
    <description>The latest articles on DEV Community by Neeraj Agarwal (@neeagl_algoscale).</description>
    <link>https://dev.to/neeagl_algoscale</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3886190%2Fe28bec7e-20bc-4399-b682-7969850c0559.jpg</url>
      <title>DEV Community: Neeraj Agarwal</title>
      <link>https://dev.to/neeagl_algoscale</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neeagl_algoscale"/>
    <language>en</language>
    <item>
      <title>Hybrid Row-Level Security: AWS + Power BI</title>
      <dc:creator>Neeraj Agarwal</dc:creator>
      <pubDate>Wed, 29 Apr 2026 17:02:16 +0000</pubDate>
      <link>https://dev.to/neeagl_algoscale/hybrid-row-level-security-aws-power-bi-3857</link>
      <guid>https://dev.to/neeagl_algoscale/hybrid-row-level-security-aws-power-bi-3857</guid>
      <description>&lt;p&gt;Most “hybrid cloud” case studies are really just migration stories. This one isn’t. A US-based physical security and fire systems integrator had 14+ subsidiary companies worth of data in AWS, thousands of field technicians in Azure Active Directory, and a board that wanted three distinct views of the same data - enforced by who the user was, not by which dashboard they clicked.&lt;/p&gt;

&lt;p&gt;Here is the architecture we stood up, the access pattern underneath it, and the two gotchas that will cost you a week if you replicate the stack without knowing about them. The full engagement is written up as a &lt;a href="https://algoscale.com/case-study/aws-data-warehouse-modernization-security-integrator/" rel="noopener noreferrer"&gt;case study on the data-warehouse modernization we delivered for this integrator&lt;/a&gt;; this post is the access-layer deep-dive for the practitioners.&lt;/p&gt;

&lt;p&gt;The starting point: one data estate, thousands of field workers, three audiences&lt;br&gt;
The customer is a security and fire systems integrator grown through acquisition - 14+ subsidiary operating companies spanning integration, security, and fire divisions. Each subsidiary arrived with its own ERP instance (Microsoft Business Central, mostly, with a long tail), its own Dynamics 365 CRM tenant or local variant, and its own definition of “customer” and “project.” The pre-consolidation data estate looked like fourteen independent companies pretending to be one.&lt;/p&gt;

&lt;p&gt;At the user layer there were three distinct reporting personas:&lt;/p&gt;

&lt;p&gt;Executives - consolidated rollup across all 14+ subsidiaries. Division-level (integration / security / fire) comparisons, quarter-over-quarter, revenue and operational KPIs at the group level. No drill to individual techs or jobs.&lt;br&gt;
Regional / subsidiary managers (RMs) - their subsidiary and territory only. Branch-level detail, job pipeline, inspection compliance, same dashboard structure across every subsidiary so execs could compare like-for-like.&lt;br&gt;
Field technicians and inspectors - thousands of them, on-the-job, doing fire system inspections and security installs. Each sees only their own work: assigned jobs, their completion rates, their compliance scores. Emphatically not their peers’ numbers by name.&lt;br&gt;
On the technology side:&lt;/p&gt;

&lt;p&gt;Data lived in AWS. S3 as the landing zone, Glue + PySpark for the 20+ ETL jobs, Step Functions orchestrating the daily pipeline, AWS DMS streaming CDC from the operational ERPs/CRMs, and Lake Formation governing the catalog.&lt;br&gt;
Identity lived in Azure AD - the HRMS (UKG) pushed new hires, role changes, and terminations into AD as the system of truth; everything else read from it.&lt;br&gt;
Reporting was Power BI. 20+ KPI dashboards across Finance, Operations, HR, and Sales. Licensing was already paid under the M365 agreement; the user population was already in AD; switching to QuickSight was never on the table.&lt;br&gt;
Asking the customer to move data to Azure was off the table - multi-year AWS contract. Asking them to replicate identity into AWS IAM was off the table - two sources of truth for “who works here” is a governance disaster, especially with thousands of field workers rotating through. So we had to make the two clouds cooperate.&lt;/p&gt;

&lt;p&gt;The architecture, top to bottom&lt;br&gt;
Azure AD (identity)&lt;br&gt;
     │&lt;br&gt;
     │ SAML 2.0 / OIDC federation&lt;br&gt;
     ▼&lt;br&gt;
AWS IAM (role-based trust)&lt;br&gt;
     │&lt;br&gt;
     │ STS AssumeRoleWithSAML&lt;br&gt;
     ▼&lt;br&gt;
Lake Formation (authorization)&lt;br&gt;
     │&lt;br&gt;
     ├── Row-level filters by user attribute&lt;br&gt;
     ├── Column-level masking for PII&lt;br&gt;
     └── Tag-based access control&lt;br&gt;
     │&lt;br&gt;
     ▼&lt;br&gt;
Athena (query layer)&lt;br&gt;
     │&lt;br&gt;
     ▼&lt;br&gt;
Power BI (presentation)&lt;br&gt;
     │&lt;br&gt;
     ├── Executive workspace&lt;br&gt;
     ├── Regional workspace (RLS on region)&lt;br&gt;
     └── Field workspace (RLS on store)&lt;br&gt;
Each arrow carries user identity forward - so that by the time a query hits a table, Lake Formation knows exactly who is asking and what they are allowed to see.&lt;/p&gt;

&lt;p&gt;Identity: Azure AD as the root of trust&lt;br&gt;
Nothing in this architecture works if the AD groups don’t reflect reality. UKG pushed Subsidiary, Territory, and TechnicianId as AD user attributes on every hire, transfer, and termination. Dynamic AD groups then materialized off those attributes:&lt;/p&gt;

&lt;p&gt;rg-executives - static group, holdco leadership&lt;br&gt;
rg-subsidiary-managers - dynamic rule matching ops-leader job titles across all 14+ subsidiaries&lt;br&gt;
rg-field-technicians - dynamic rule matching field-job titles (inspector, installer, service tech)&lt;br&gt;
The Subsidiary, Territory, and TechnicianId attributes flowed through to the federation layer as SAML assertions. That is the single most important implementation detail in the whole stack. Without accurate AD attributes you have no row filters; without dynamic groups you spend every Monday reassigning permissions when field techs transfer between subsidiaries - and with thousands of them rotating through, that is a full-time job you do not want to create.&lt;/p&gt;

&lt;p&gt;The federation bridge: Azure AD to AWS IAM&lt;br&gt;
AWS has had SAML federation for years; the modern path is to register Azure AD as a SAML 2.0 identity provider in IAM and then create IAM roles that trust it. A condensed version of the role trust policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Principal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Federated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:saml-provider/AzureAD"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sts:AssumeRoleWithSAML"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Condition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"StringEquals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"SAML:aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://signin.aws.amazon.com/saml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three IAM roles were created, each mapped to one AD group via the Enterprise Applications claim rules in Azure:&lt;/p&gt;

&lt;p&gt;LakeFormation-Executive (full catalog read across all 14+ subsidiaries)&lt;br&gt;
LakeFormation-SubsidiaryManager (Lake Formation enforces subsidiary + territory filter)&lt;br&gt;
LakeFormation-FieldTechnician (Lake Formation enforces per-technician filter)&lt;br&gt;
When a user signs in to AWS via the Azure AD app tile, Azure issues a SAML assertion, AWS STS validates it, and the session credentials carry PrincipalTag/Subsidiary, PrincipalTag/Territory, and PrincipalTag/TechnicianId claims that Lake Formation reads later. Those claims are the whole key to the row-level filter.&lt;/p&gt;

&lt;p&gt;Lake Formation: where permissions actually live&lt;br&gt;
This is the hop that most “AWS + Power BI” tutorials skip. Giving a user an IAM role with athena:* is not the same as giving them data - Lake Formation sits between. LF permissions are set on the Glue Catalog (databases, tables, columns) and they override coarse IAM permissions for tables it governs.&lt;/p&gt;

&lt;p&gt;The data warehouse we built had three layers in Glue Catalog:&lt;/p&gt;

&lt;p&gt;raw_* - bronze landing tables, no user-facing grants&lt;br&gt;
conformed_* - silver joins, accessible to analysts only&lt;br&gt;
reporting_* - gold, the only layer Power BI queries&lt;br&gt;
Lake Formation permissions on reporting_* were set via tag-based access control (LF-TBAC) rather than explicit table grants. Every table in reporting_* was tagged with domain=sales, sensitivity=standard|pii, etc. The IAM roles got grants against those tags instead of individual tables - so when a new table shipped under the sales domain, the existing permissions flowed to it automatically. One less deployment friction.&lt;/p&gt;

&lt;p&gt;The row-level filter&lt;br&gt;
The piece that does the actual filtering work is a Lake Formation data filter. A data filter is a named expression stored on a table that LF applies to every query, transparently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Filter name: subsidiary_scope&lt;/span&gt;
&lt;span class="c1"&gt;-- Target table: reporting_jobs_daily&lt;/span&gt;
&lt;span class="c1"&gt;-- Filter expression:&lt;/span&gt;
&lt;span class="n"&gt;subsidiary_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;current_user_attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Subsidiary'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current_user_attribute('Subsidiary') function reads the session tag AWS propagates from the SAML assertion. The result: a subsidiary manager running SELECT * FROM reporting_jobs_daily sees only their subsidiary’s rows. No WHERE clause in the query, no report-level filter to forget, no report author needs to remember anything. The filter runs in Lake Formation before Athena returns results.&lt;/p&gt;

&lt;p&gt;Field technicians got a second data filter keyed on technician_id - each tech sees their own assigned jobs and their own compliance scores, but not their peers’. Executives had no filter. Data analysts got a third filter that masked PII columns (customer_email redacted, customer_phone redacted) while leaving other columns alone.&lt;/p&gt;

&lt;p&gt;Connecting Power BI: the two-hop problem&lt;br&gt;
Here is the first non-obvious thing. Power BI does not natively speak SAML. Its Athena connector authenticates with AWS IAM credentials (access key + secret), not with a user’s federated SAML session. So the natural instinct - “Power BI signs in as the user” - doesn’t work out of the box.&lt;/p&gt;

&lt;p&gt;The pattern we landed on:&lt;/p&gt;

&lt;p&gt;Create a dedicated IAM role PowerBI-AthenaService that can query reporting_* tables&lt;br&gt;
Power BI workspaces connect to Athena as that service role (credentials stored in the Power BI data gateway)&lt;br&gt;
Row-level security moves into the Power BI semantic model, not Lake Formation&lt;br&gt;
Wait - what happened to the whole Lake Formation row filter setup? It still runs, but now it guards direct Athena access (analysts running ad-hoc SQL, scripts assuming the user’s SAML role). The reporting pipeline uses the service role; RLS for reports is enforced at the Power BI layer using the user’s UPN.&lt;/p&gt;

&lt;p&gt;This sounds like a downgrade. It isn’t - it’s just a different layer for a different access path. The Power BI semantic model has a [Subsidiary] dimension and defines an RLS rule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Subsidiary] = LOOKUPVALUE(Users[Subsidiary], Users[UPN], USERPRINCIPALNAME())

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;USERPRINCIPALNAME() returns the Azure AD UPN of the signed-in Power BI user. The Users dimension is refreshed nightly from AD (same UKG-sourced attributes). When a subsidiary manager opens their dashboard, Power BI filters every visual to their subsidiary automatically. A field technician gets a narrower rule keyed on TechnicianId that ends up in the same dataset but only returns their own jobs.&lt;/p&gt;

&lt;p&gt;Why both layers&lt;br&gt;
A single RLS layer is a risk surface. Lake Formation alone means anyone with the Power BI service role can bypass RLS. Power BI semantic model alone means anyone with Athena access can bypass RLS. Running both means a query has to pass both checks to surface data - and every access path (SQL-direct via Athena, Power BI report) lands in the same answer.&lt;/p&gt;

&lt;p&gt;For regulated data specifically (healthcare, finance), regulators often require defense in depth on access controls - not “the RLS is set up in Power BI, promise.” Two enforcement layers, both independently auditable, is the posture they want to see. Pair this with our data governance consulting practice if auditor-ready controls are part of the engagement.&lt;/p&gt;

&lt;p&gt;Three Power BI workspaces, not three reports&lt;br&gt;
A common mistake: building one workspace with three different reports for three audiences. That design puts all the RLS logic in the report filters, duplicates dataset refreshes, and creates cross-visibility risk - any user who can open the workspace can (usually) see all artifacts inside it.&lt;/p&gt;

&lt;p&gt;We built three workspaces, each with its own Power BI app:&lt;/p&gt;

&lt;p&gt;Executive workspace - one dataset, rollup-only dashboards spanning all 14+ subsidiaries. Executives are added as workspace viewers. No RLS, because the rolled-up data has no row-level privacy.&lt;br&gt;
Subsidiary-managers workspace - a separate dataset with RLS on [Subsidiary]. All SMs are viewers. Each sees only their subsidiary when they open the app.&lt;br&gt;
Field-workforce workspace - a third dataset with RLS on [TechnicianId]. Every inspector and installer is a viewer. Each sees their own assigned jobs and compliance scorecard.&lt;br&gt;
Why three workspaces instead of three apps off one dataset? Because RLS in Power BI is per-dataset. If a subsidiary manager is accidentally added as a workspace admin (not just a viewer) on the executive workspace, RLS does nothing for them - admins bypass RLS. Separate workspaces isolate admin access and prevent cross-audience leakage through over-granted roles. When the customer’s internal audit showed up six months later, this separation is the architectural choice they asked about first.&lt;/p&gt;

&lt;p&gt;What we shipped&lt;br&gt;
Concrete outcomes from the engagement (the full write-up is on the case study page):&lt;/p&gt;

&lt;p&gt;14+ subsidiary ERPs unified into a single governed data lake on AWS S3, with AWS DMS streaming CDC from every operational source&lt;br&gt;
20+ real-time Power BI KPI dashboards across Finance, Operations, HR, and Sales - split across the three workspaces described above&lt;br&gt;
70% reduction in reporting preparation time - finance stopped rebuilding the weekly rollup in Excel and started trusting the pipeline&lt;br&gt;
100% automated daily pipeline - Step Functions orchestrating Glue + PySpark; zero manual reruns after the initial backfill&lt;br&gt;
Custom MDM ledger in RDS PostgreSQL - a 75+ brand dictionary resolving customer identities across the 14+ subsidiaries, without which the consolidated revenue rollup would have double-counted everything&lt;br&gt;
Thousands of field technicians authenticated via existing Azure AD credentials - no new passwords, no new portal&lt;br&gt;
3 IAM roles + 1 Power BI service role - the entire access matrix fits on one whiteboard&lt;br&gt;
Audit outcome: the customer’s annual security audit the following quarter cleared the data platform without findings. Two independent RLS enforcement layers, attribute flow traceable from HR system (UKG) to dashboard, and no shadow service accounts was the specific combination the audit team called out.&lt;/p&gt;

&lt;p&gt;Two gotchas worth knowing about&lt;br&gt;
The enterprise engineering team was sharp and still hit both of these.&lt;/p&gt;

&lt;p&gt;Gotcha 1: Power BI’s “Azure AD” is not always the same tenant as your AWS federation’s “Azure AD.” Large enterprises often run multiple AD tenants - one for M365 workloads, one for developer tools, one left over from an acquisition. Verify that the tenant Power BI is licensed against is the same tenant issuing SAML assertions to AWS. If not, UPNs will not match across layers and your RLS lookups silently return nothing (not an error - just empty datasets). Pre-flight check: pick three users across personas and verify their UPN is identical in Power BI and in the AD attributes that feed AWS SAML claims.&lt;/p&gt;

&lt;p&gt;Gotcha 2: Lake Formation data filters have per-table quotas (check the current AWS limit - at time of writing it is 1,000 data filters per table). If your filter design requires a different filter per regional manager, you hit that ceiling fast. The fix is what we did: one parameterized data filter that reads a session attribute, not N hard-coded filters. The temptation to create filter_region_northeast, filter_region_southeast, etc., is strong and leads to an unmaintainable mess.&lt;/p&gt;

&lt;p&gt;Where the pattern has been validated at larger scale&lt;br&gt;
This is not a novel architecture. BMW Group runs a structurally identical pattern across their data mesh - federated identity feeding Lake Formation’s fine-grained access control, attribute-based filters gating access to data product tables, and downstream analytics tools reading through that enforcement layer. Their public AWS case study on Lake Formation fine-grained access control walks through the same federated-identity-to-LF-filter design applied at considerably larger scale (thousands of data products across hundreds of teams, versus 14+ subsidiaries here).&lt;/p&gt;

&lt;p&gt;The takeaway for enterprises evaluating the pattern: the plumbing is proven. The work is in the AD attributes flowing cleanly from your HR system and the discipline to keep enforcement in Lake Formation (not scattered across reports). BMW’s writeup and this engagement arrive at the same answer from opposite ends of the scale spectrum.&lt;/p&gt;

&lt;p&gt;When this pattern fits&lt;br&gt;
The hybrid AWS + Azure AD + Power BI pattern is right when:&lt;/p&gt;

&lt;p&gt;Data already lives in AWS and moving it is not an option&lt;br&gt;
Identity lives in Azure AD (standard for M365 shops)&lt;br&gt;
Row-level security matters at scale - typically regulated industries, multi-brand retail, or distributed field operations with thousands of workers&lt;br&gt;
Power BI is already the reporting tool (avoid a BI migration unless you have another reason)&lt;br&gt;
If any of those constraints don’t apply, simpler patterns exist. If all of them do, this is the path of least regret - and the one that cleared the customer’s audit the first time.&lt;/p&gt;

&lt;p&gt;For a deeper architectural read on multi-cloud identity flow, the hybrid cloud engagement playbook walks through the governance layer in detail. If the Power BI side is where you’re stuck, Microsoft Power BI consulting covers the semantic model and RLS patterns specifically. And if you arrived here because consolidating multiple acquired companies’ data is what’s actually driving the access-control complexity, the 180-day M&amp;amp;A data consolidation playbook is the companion piece to this one.&lt;/p&gt;

</description>
      <category>fabric</category>
      <category>azure</category>
    </item>
    <item>
      <title>Fabric OneLake shortcuts vs ADLS Gen2 mounts: what actually works in production</title>
      <dc:creator>Neeraj Agarwal</dc:creator>
      <pubDate>Sat, 18 Apr 2026 15:15:55 +0000</pubDate>
      <link>https://dev.to/neeagl_algoscale/fabric-onelake-shortcuts-vs-adls-gen2-mounts-what-actually-works-in-production-a3e</link>
      <guid>https://dev.to/neeagl_algoscale/fabric-onelake-shortcuts-vs-adls-gen2-mounts-what-actually-works-in-production-a3e</guid>
      <description>&lt;p&gt;If you've just moved a team onto Microsoft Fabric, the "how do I read our existing ADLS Gen2 data?" question comes up in the first week. Two patterns are on the table:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;OneLake shortcuts&lt;/strong&gt; - Fabric's newer, metadata-only references that make external data appear as a Lakehouse table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ADLS Gen2 mounts&lt;/strong&gt; - the &lt;code&gt;mssparkutils&lt;/code&gt;/&lt;code&gt;notebookutils&lt;/code&gt; pattern that's been around since Synapse&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The docs describe them as roughly interchangeable options. They aren't. We've now shipped both across ~15 Fabric projects and the failure modes are &lt;em&gt;completely&lt;/em&gt; different.&lt;/p&gt;

&lt;p&gt;Here's the field report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick refresher - the two patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Shortcut (the new way)
&lt;/h3&gt;

&lt;p&gt;Create via UI, REST API, or Fabric CLI. Zero-copy reference to an external location, exposed as a table or folder inside a Lakehouse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://api.fabric.microsoft.com/v1/workspaces/{wsid}/items/{lakehouseid}/shortcuts"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "path": "Tables",
    "name": "orders_raw",
    "target": {
      "adlsGen2": {
        "location": "https://prodstorage.dfs.core.windows.net",
        "subpath": "/raw/orders",
        "connectionId": "1234abcd-..."
      }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once created, Spark and SQL treat it as a normal Lakehouse table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lakehouse.orders_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mount (the familiar way)
&lt;/h3&gt;

&lt;p&gt;In a Fabric notebook, create a logical mount path backed by an ADLS Gen2 container using a LinkedService:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;notebookutils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abfss://raw@prodstorage.dfs.core.windows.net/orders&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/orders_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;linkedService&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prod_adls_linked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;spark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parquet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/synfs/nb_resource/orders_raw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both read the same bytes. The differences start everywhere else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where shortcuts are magic
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Direct Lake mode only works with shortcuts
&lt;/h3&gt;

&lt;p&gt;This is the big one. If you want your Power BI semantic models to query Lakehouse data with zero import latency via Direct Lake, the table &lt;em&gt;must&lt;/em&gt; live in the Lakehouse - which means shortcut or physical copy. Mounts don't count.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Direct Lake reads  →  Lakehouse table  →  shortcut OR Delta files in /Tables
Direct Lake reads  ✗  Mount path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Direct Lake is in your architecture diagram (and on Fabric it usually should be), this alone settles the argument.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. ACLs inherit from the source
&lt;/h3&gt;

&lt;p&gt;Shortcuts honor the ACL model of the underlying storage. A user who can read &lt;code&gt;/raw/orders&lt;/code&gt; in ADLS via POSIX ACL or RBAC can read it through the shortcut - Fabric doesn't re-auth.&lt;/p&gt;

&lt;p&gt;Mounts run under the LinkedService's identity, which is usually a service principal with broad access. You've just given every notebook in that workspace the same blast radius.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Zero operational overhead at mount time
&lt;/h3&gt;

&lt;p&gt;Shortcut creation is metadata only. A 40TB dataset becomes queryable in under a second. Mounts require the runtime to resolve the path at session start - fine for small containers, annoying when you're re-starting sessions dozens of times a day across a team.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. SQL endpoint sees them
&lt;/h3&gt;

&lt;p&gt;A Lakehouse's SQL analytics endpoint exposes shortcut tables for free. Warehouses can cross-query them with three-part names. Mounts are invisible to the SQL endpoint - you can only read them from a notebook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Works on a shortcut, fails on a mount&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MyLakehouse&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;dbo&lt;/span&gt;&lt;span class="p"&gt;].[&lt;/span&gt;&lt;span class="n"&gt;orders_raw&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;order_ts&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="s1"&gt;'2026-01-01'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where shortcuts silently break
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Cross-region reads are expensive and slow
&lt;/h3&gt;

&lt;p&gt;Shortcuts don't copy data. If your ADLS account is in &lt;code&gt;westus2&lt;/code&gt; and your Fabric capacity is in &lt;code&gt;eastus&lt;/code&gt;, every query pulls bytes across the region boundary - at egress pricing, at cross-region latency.&lt;/p&gt;

&lt;p&gt;We had one client where a dashboard that ran in 4 seconds against a co-located Lakehouse took 38 seconds through a shortcut to a foreign-region ADLS. Nobody's documentation flagged this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; shortcut target and Fabric capacity home region should match. If they can't, plan for a daily materialized copy or move the capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Connection credentials expire in ways you won't see
&lt;/h3&gt;

&lt;p&gt;When you create a shortcut via UI, Fabric prompts for credentials once and stores them as a Connection object. If that connection uses a SAS token, it expires. When it does, the shortcut starts returning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Error: [FabricOneLakeShortcutAuthFailure] Connection refused
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not at creation time. Not logged anywhere obvious. Users just see empty tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; use workspace identity or a managed-identity-backed connection, not SAS. If you must use SAS, alert on expiry explicitly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. ADLS Gen1 is not supported
&lt;/h3&gt;

&lt;p&gt;We know, you thought it was fully deprecated. We had a client with a Gen1 account still humming along for a batch job. Fabric won't shortcut it - no workaround. You're migrating to Gen2 first.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Shortcuts to Hive-partitioned data don't auto-discover partitions
&lt;/h3&gt;

&lt;p&gt;If your external ADLS Gen2 layout is &lt;code&gt;/sales/year=2025/month=04/&lt;/code&gt;, a shortcut exposes it as a folder shortcut - not a partitioned table. You either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shortcut the root and read in Spark (&lt;code&gt;spark.read.option("basePath", …)&lt;/code&gt;) - loses the Lakehouse table abstraction&lt;/li&gt;
&lt;li&gt;Convert to Delta first on the ADLS side, then shortcut (the "right" answer, but it's work)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mounts have the same issue, but at least there's no expectation of table-like behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance we measured
&lt;/h2&gt;

&lt;p&gt;We ran the same Spark query - a 200M-row aggregation over Parquet sitting in ADLS Gen2 - through both access patterns, from a Fabric F64 capacity. Five cold runs, five warm runs, averaged:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Access path&lt;/th&gt;
&lt;th&gt;Cold (first run)&lt;/th&gt;
&lt;th&gt;Warm (cached)&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Delta files directly in Lakehouse &lt;code&gt;/Tables/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;11.2 s&lt;/td&gt;
&lt;td&gt;2.1 s&lt;/td&gt;
&lt;td&gt;Baseline. No shortcut, no mount.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shortcut to same-region ADLS (Delta)&lt;/td&gt;
&lt;td&gt;12.8 s&lt;/td&gt;
&lt;td&gt;2.4 s&lt;/td&gt;
&lt;td&gt;~15% overhead cold, negligible warm&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mount to same-region ADLS (Parquet)&lt;/td&gt;
&lt;td&gt;14.1 s&lt;/td&gt;
&lt;td&gt;3.0 s&lt;/td&gt;
&lt;td&gt;Lacks Delta stats; CBO worse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shortcut to cross-region ADLS (Delta)&lt;/td&gt;
&lt;td&gt;38.4 s&lt;/td&gt;
&lt;td&gt;6.7 s&lt;/td&gt;
&lt;td&gt;Egress + latency dominates&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same-region shortcuts are essentially free&lt;/li&gt;
&lt;li&gt;Cross-region shortcuts are a trap dressed up as convenience&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to use which - decision matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;Mount&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Direct Lake mode from Power BI&lt;/td&gt;
&lt;td&gt;✅ required&lt;/td&gt;
&lt;td&gt;❌ not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL analytics endpoint access&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-level ACL enforcement&lt;/td&gt;
&lt;td&gt;✅ honored from source&lt;/td&gt;
&lt;td&gt;❌ runs as SPN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External Iceberg tables (preview)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read ad-hoc files (CSV dumps, logs)&lt;/td&gt;
&lt;td&gt;⚠️ overkill&lt;/td&gt;
&lt;td&gt;✅ just mount and read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-region source storage&lt;/td&gt;
&lt;td&gt;❌ egress pain&lt;/td&gt;
&lt;td&gt;❌ egress pain - at least you know it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADLS Gen1&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Write back to external storage from Fabric&lt;/td&gt;
&lt;td&gt;❌ shortcuts are read-focused&lt;/td&gt;
&lt;td&gt;✅ mount supports write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick ETL staging in a notebook&lt;/td&gt;
&lt;td&gt;⚠️&lt;/td&gt;
&lt;td&gt;✅ simpler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The pattern we land on
&lt;/h2&gt;

&lt;p&gt;On every new Fabric engagement now we default to this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;All production table access → shortcuts.&lt;/strong&gt; No exceptions. Direct Lake, SQL endpoint access, and source-ACL inheritance are non-negotiable in an enterprise deployment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shortcuts target same-region ADLS.&lt;/strong&gt; If source is elsewhere, we budget a one-way replication with a scheduled pipeline into same-region staging, then shortcut that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connections use workspace identity or MI-backed Connection objects.&lt;/strong&gt; SAS is banned unless there's a specific third-party constraint, and if it is, we alert on token expiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mounts only for notebook-level ETL utility work&lt;/strong&gt; - reading a one-off CSV, writing a temp file back to a scratch container. Never for anything a dashboard depends on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ADLS Gen1 migrates to Gen2 before Fabric migration.&lt;/strong&gt; Non-negotiable.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The TL;DR for teams coming from Synapse: you're going to reach for mounts because they're familiar. Resist. Shortcuts are the primitive Fabric is built around - learn them well, and most of Fabric's headline features (Direct Lake, SQL endpoint cross-query, cross-item data reuse) fall into your lap for free.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're mid-migration and want a second set of eyes on shortcut architecture, the team at &lt;a href="https://algoscale.com/data-lake-consulting-services/" rel="noopener noreferrer"&gt;Algoscale&lt;/a&gt; runs these assessments regularly. The full write-up of the migration playbook including the connection-identity pattern lives &lt;a href="https://algoscale.com/go/blog/fabric-shortcuts-vs-adls-mounts/" rel="noopener noreferrer"&gt;on our blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dataengineering</category>
      <category>azure</category>
      <category>tutorial</category>
      <category>microsoft</category>
    </item>
  </channel>
</rss>
