Forum Discussion

vedant_9's avatar
vedant_9
New Member
2 days ago

Reading on-prem SQL Server / Oracle from a Fabric notebook: is the gateway really not an option?

Hi all,

We’re moving our ingestion framework from Databricks to Fabric and have run into a connectivity problem.

The setup is metadata-driven. A single PySpark notebook reads a control table in a lakehouse and loops through about 200 source tables. For each one it builds a JDBC connection, gets the credentials from Key Vault, pulls only the new rows using a watermark, and appends them to a Delta table in our bronze lakehouse. It’s worked well for years on Databricks, and in Fabric it’s fine for our Azure SQL and public sources.

The trouble is our on-prem databases (SQL Server, Oracle and a couple of MySQL). From the notebook, a simple read like this:

df = (spark.read.format("jdbc")
      .option("url", "jdbc:sqlserver://erp-sql.corp.local:1433;databaseName=ERP;encrypt=true;trustServerCertificate=true")
      .option("dbtable", "(SELECT * FROM sales.Orders WHERE RowVer > 1000 AND RowVer <= 2000) src")
      .option("user", user)
      .option("password", pwd)
      .load())

just times out: “The TCP/IP connection to the host erp-sql.corp.local, port 1433 has failed… connect timed out”.

We do have an on-premises data gateway (two nodes). It works fine from pipelines and Dataflows, but as far as I can tell notebooks can’t route through it. I found an idea on the Ideas forum asking for exactly this, still in “New” status.

The two options I can see:

  1. Use a Copy activity through the gateway to land each table as Parquet in the lakehouse Files area, then have the notebook pick it up from there. This works, but it means a ForEach with a Copy activity per table. I’m not sure how well that scales to ~200 tables, or how best to pass each table’s incremental window (lower/upper watermark) into the source query.
  2. Put a Private Link Service in front of the on-prem databases (we have connectivity via ExpressRoute) and create a managed private endpoint from the workspace, so Spark can connect over JDBC directly. I’ve seen a blog post about this, but parts of it seemed to be in preview.

A few questions for anyone who’s been through this:

  • Is gateway support for notebooks (Spark or Python) on the roadmap, or is there already a supported way I’m missing?
  • Has anyone run option 2 in production against on-prem SQL Server or Oracle? Was it reliable, and did you notice slower Spark session start-up once managed private endpoints were in place?
  • If you went with option 1 at this kind of scale, did you use a single parameterised Copy activity inside a ForEach, or Copy job? How did you handle incremental loads per table?
  • Roughly, which ended up cheaper in CUs for you?

We’re on an F64 capacity with schema-enabled lakehouses, if that matters.

Thanks in advance, any pointers appreciated!

3 Replies

  • Hi, yes that's right, notebooks can't use the on-prem gateway, so the JDBC call times out.

    What I'd suggest:
    1. Use Copy job to land the tables in the Lakehouse. It supports on-prem SQL Server and Oracle through the gateway and does watermark based incremental loads for you.
    2. Then run your notebook on top of that for the bronze logic.

    Just keep in mind watermark loads won't catch deletes, CDC would be needed for that.

    Managed private endpoint to on-prem is also possible now (Private Link Service + VPN/ExpressRoute), but it's a proper networking setup. I'd only go that way if you really need direct access from the notebook.

  • Hi vedant_9​ 

    Yeah, Fabric notebooks can't connect to on prem gateways :( 

     

    The typical solution here is to use a pipeline with a copy activity to stage the data in a lakehouse, and then notebooks can pick it up from there. 

    I'd also encourage you to look at using a SQL database for the metadata control database, it will support transactional operations so if you're logging stuff back to it you won't run into concurrency problems. 

  • Hi vedant_9​ , We came across a similar architectural consideration while working with Microsoft Fabric ingestion patterns at DataToBiz. The key distinction is that a gateway connection configured for pipelines and Dataflows does not automatically extend to Spark executors running JDBC reads. I’d separate the connectivity problem from the ingestion design before changing the existing framework.

    1. Direct JDBC connectivity through Managed Private Endpoints

    If preserving the metadata-driven PySpark framework is important, I’d first validate whether direct connectivity can be established through a supported Managed Private Endpoint and Azure Private Link Service configuration.

    With ExpressRoute already in place, verify private DNS resolution, routing, firewall rules, load-balancer configuration, endpoint approval and TLS requirements. ExpressRoute alone does not guarantee that Fabric Spark can reach an on-premises database.

    If connectivity works reliably, you can retain the existing JDBC extraction, Key Vault credentials, watermark logic and Delta writes without introducing an additional staging step.

    1. Gateway-based ingestion for approximately 200 tables

    If direct connectivity isn't viable, I'd use a metadata-driven Data Factory pipeline with a Lookup activity followed by a parameterised Copy activity inside a ForEach. Each iteration receives the source connection, schema, table name and incremental boundaries.

    The source query would follow this pattern:

    SELECT * 
    FROM sales.Orders 
    WHERE RowVer > <lower_watermark> 
      AND RowVer <= <upper_watermark>;

    The placeholders are illustrative, not executable SQL. Use supported parameterisation and validate identifiers and values. For SQL Server, rowversion is a binary version value, not a timestamp.

    The pipeline copies each table through the gateway into a table-specific staging location in the lakehouse Files area. A subsequent notebook validates and processes the staged data into bronze Delta tables.

    The control flow should be:

    • Read the last successful watermark from the control table.
    • Capture and persist the upper watermark for the current run.
    • Execute the Copy activity through the gateway.
    • Validate the extracted data and write it to Delta.
    • Advance the watermark only after successful processing.
    • Log row counts, execution duration, errors and retries.

    Retries must be idempotent to prevent duplicate rows, and overlapping runs for the same table should be prevented. Watermark-based extraction also does not automatically capture deletes; use CDC or a separate deletion-tracking strategy when required.

    1. Keep the notebook focused on Delta processing

    Once the Copy activity succeeds, the notebook can read the staged Parquet files and write them into the bronze lakehouse:

    from pyspark.sql import functions as F 
    
    staging_path = "Files/staging/Orders/current_run/" 
    target_table = "bronze.Orders" 
    
    df = spark.read.parquet(staging_path) 
    
    df = ( df.withColumn("_ingested_at", F.current_timestamp()) 
                 .withColumn("_run_id", F.lit("current_run")) 
    ) 
    
    df.write.format("delta").mode("append").saveAsTable(target_table)

    This is illustrative code; the path and run ID should come from pipeline parameters. A plain append is not retry-safe, so production implementations should use a run-level commit strategy or an appropriate Delta MERGE to prevent duplicates after failures.

    1. Copy activities versus Copy jobs

    I'd start with a parameterised Copy activity inside a ForEach because it offers explicit control over table-specific queries, watermark boundaries, staging and notebook dependencies. Copy jobs are also worth evaluating, but verify connector support, incremental-state management and custom-query capabilities first.

    At 200 tables, avoid running every extraction concurrently by default. Tune the ForEach batch count according to gateway throughput, source connection limits and database load.

    1. Performance and CU consumption

    I wouldn't assume either approach is cheaper on an F64 capacity without benchmarking. Test representative small, medium and large tables and compare end-to-end duration, source database load, gateway throughput, Spark startup latency, retry overhead and Fabric capacity consumption.

    For direct JDBC, excessive partitioning can overload source databases. For gateway-based ingestion, excessive pipeline concurrency can saturate the gateway cluster. Measure both under realistic workloads before committing to an architecture.