FAQ
Top questions from first-time and existing customers. For deep-dives, see QUICKSTART, E2E-RUNBOOK, and OPERATIONS.
Sizing & cost
How much does a typical deployment cost?
Baseline (always-on): ~$15–$45/month depending on options. The
deployed Bicep emits an estimatedMonthlyCostUsd output with a
per-component breakdown after every deploy.
| Component | Typical cost |
|---|---|
| Key Vault Standard | ~$1/mo |
| Log Analytics workspace | $0.10/GB ingested (first 5 GB free) |
| Control plane Container App (idle) | ~$15/mo |
| Container Apps Environment | $0 (Consumption tier, default) — or ~$120/$240/$480/mo for WorkloadProfile D4/D8/D16 (v0.14.0+, opt-in for >4 GiB containers and faster cold-start) |
| 3× Private endpoints (if enabled) | ~$22/mo |
| ADLS Gen2 storage | usage-based ($0.018/GB Hot, $0.01/GB Cool, $0.002/GB Archive) |
| Container Apps Job executions | $0.000024/vCPU-sec + $0.0000025/GiB-sec |
A first deploy + first extract on a 100-table schema typically lands under $1.
How long does an extract take?
| Schema size | Duration |
|---|---|
| < 10 tables, < 1M rows | 1–2 min |
| 100 tables, ~50M rows | 10–15 min |
| 1,700 tables, ~1 TB | 90–120 min |
Numbers from docs/E2E-RUNBOOK.md benchmark runs. Your mileage will
vary with LOB density, Oracle PROCESSES limit, and chosen
parallelism.
Deployment
Do I need to build the Docker images myself?
No. The publisher’s prod ACR (ksoradlsacrprod.azurecr.io) is
Standard-tier with anonymous pull enabled — the Bicep template’s
default image references work without any auth setup. Only build
your own if you’ve forked the publisher.
Can I deploy without Owner / User Access Administrator?
Yes. Set assignRoles: false in the parameters and have a UAA admin
run the role assignments separately. The deploy still succeeds and
emits a uamiPrincipalId output you hand to your admin to wire up.
See QUICKSTART §4 for the exact commands.
Can I use private endpoints from day 1?
Yes. Set peSubnetResourceId to a /28+ subnet in the same VNet as
the CAE subnet (must be separate — CAE subnet is delegated and can’t
host PEs). The template creates three PEs (blob, dfs, KV) plus the
private DNS zones.
When do I need to flip to Workload Profile? (v0.14.0+)
The default Consumption tier caps containers at 2 vCPU / 4 GiB and
scales to zero (so idle cost is $0). The failure mode for 4 GiB is
silent Linux OOM-kill on the restore Job — the JVM gets killed
before Spark can log a diagnostic, and the execution is marked
Failed with no error in the logs. You’ll hit this around:
- Restoring tables with 500K+ rows at the default
batchSize=10000 - Extracting wide tables (100+ columns) or LOB-heavy schemas at
the default
fetchSize=10000 - Running parallel concurrent jobs that share the same Consumption-tier ceiling
Two ways out:
- Quick / no-redeploy: lower the per-job knob from the UI Advanced
section (
batch_size: 1000on restore,fetch_size: 2000on extract). This works on Consumption. - Production: set
containerAppsEnvironmentTier=WorkloadProfileandworkloadProfileName=D4(or D8/D16) at deploy time. Adds ~$120/mo idle but lifts the container ceiling to 16/32/64 GiB and drops cold-start from ~10 s to ~1 s.
My deploy fails with MissingSubscriptionRegistration
Run ./scripts/register-rps.sh once per subscription. Registers
Microsoft.App, ContainerRegistry, KeyVault, ManagedIdentity,
OperationalInsights, Storage.
First extract
My extract job started but produced zero tables
Three usual causes:
- Wrong schema name in
defaultSchemas. Runscripts/discover-schemas.sh --host ... --service ... --user ... --password ...to see what schemas the connecting user can actually read. - Missing
EXECUTEonSYS.DBMS_FLASHBACK— silently no-ops if the grant was issued asSYSTEMinstead ofSYS AS SYSDBA. Failure mode:ORA-00904: "DBMS_FLASHBACK"."GET_SYSTEM_CHANGE_NUMBER": invalid identifier. Re-issue grants as SYSDBA; seescripts/grant-extractor-perms.shwhich forces the right connection. - Oracle host unreachable from the CAE subnet. Run the preflight
job first:
az containerapp job start -g <rg> -n <prefix>-preflight.
Extract failed with Connection refused to 169.254.169.254
You’re using a legacy auth mode that uses the Azure IMDS endpoint
which doesn’t work inside Container Apps. Set authMode: auto in
parameters (default in v0.5+) — it uses the custom token provider
which works on Container Apps MSI, AKS WIF, VM MSI, SP env vars, and
dev creds without IMDS.
ORA-00942: table or view does not exist
The connecting user doesn’t have SELECT on the schema’s tables.
Either grant SELECT ANY TABLE to the user, or grant explicitly on
the specific schemas you intend to archive.
How do I cancel a running extract?
az containerapp job execution list -g <rg> -n <prefix>-extractor \
--query "[?properties.status=='Running'].name" -o tsv | \
xargs -r -I{} az containerapp job execution stop \
-g <rg> -n <prefix>-extractor --execution-name {}
The extractor has no graceful-shutdown protocol; it’s safe to kill mid-run because the manifest is rewritten after every phase and any in-flight Parquet files are simply orphaned (lifecycle policy will tier and delete them).
Querying archived data
I want to query without restoring to Oracle
After every extract, the manifest produces two SQL files in
<container>/<job_id>/_catalog/:
synapse.sql— paste into a Synapse Serverless SQL poolfabric.sql— paste into a Fabric Lakehouse SQL endpoint
Both expose the archived tables as views via OPENROWSET against Parquet. See SYNAPSE-QUERY-GUIDE.md and FABRIC-QUERY-GUIDE.md.
Can I use Power BI directly against the archive?
Yes, via either Synapse Serverless or Fabric as the query engine. Power BI’s “ADLS Gen2” connector reads Parquet natively too but loses the manifest-driven view abstraction. Use Synapse/Fabric as the SQL layer for production reporting.
Restore
Can I restore to a different Oracle instance?
Yes — that’s the typical case. Set ORACLE_HOST (and ORACLE_USER,
ORACLE_PASSWORD) on the restore Job to the target instance. The
source archive (in ADLS) and the target Oracle don’t need to be
related; you can archive Prod and restore to Dev for testing.
Restore is slower than extract — why?
Oracle’s JDBC INSERT path serializes per table (no parallel writers
without partitioning hints). Restore is typically 2–3× slower than
extract. To speed up: disable indexes during restore
(DISABLE_INDEXES_DURING_RESTORE=true) and recreate them after,
or pre-create the target schema with nologging tables.
Can I restore just one table?
Yes. Set TABLES_INCLUDE=SCHEMA.TABLE on the restore Job.
Compliance
Is the archive immutable?
Only when lifecycleProfile: 'compliance' is set at deploy time —
that enables Azure storage’s time-based immutability for 2557 days
(7 years). The default profile (none) and the standard profile
do not enforce immutability.
Even with compliance, the policy is created unlocked at deploy
time. To make it truly tamper-resistant, you must explicitly lock
the policy afterward (see OPERATIONS §6). Locking is irreversible.
How do I prove the archive wasn’t modified?
Three artifacts together provide the proof:
- The manifest (
<container>/<job_id>/_manifest.json) records the SCN, row counts, and per-table extract status. - The immutability lock state on the container.
- KV access logs + storage access logs in Log Analytics (require
enableDiagnostics: true).
OPERATIONS §6 walks through the exact az commands an auditor will
ask for.
Security
Where is the Oracle password stored?
In the Azure Key Vault created by the template. The Container Apps Job reads it via a Key Vault reference at job start; the value never appears in the deployed Bicep template state or in env-var output.
How do I rotate the Oracle password?
az keyvault secret set --vault-name <kv> --name oracle-password --value '<new>'
No redeploy needed. The next job execution picks up the new secret at container start. See OPERATIONS §1.
Are images signed?
JAR checksums are pinned and verified at build time (see
scripts/verify-jar-checksums.sh). Image-level signing is on the
roadmap — track in CHANGELOG [Unreleased] section.
The control plane returns 401 for every request
Easy Auth wasn’t configured. Run scripts/setup-easyauth.sh to
create the Entra App Registration, then redeploy with the resulting
client ID as easyAuthAppId. The 401 message in the response body
links back to this section.
Operations
How do I tear down without losing the archive?
./scripts/teardown.sh <rg> --preserve-archive
Moves the storage account to a quarantine RG <rg>-preserved-archive
before deleting the rest. The preserved storage keeps the archived
Parquet indefinitely.
What about Key Vault and Log Analytics soft-delete?
KV is soft-deleted for 7 days; LAW for 14 days. The teardown script purges both automatically. If you re-deploy with the same names inside those windows, the redeploy fails — see QUICKSTART §7.
Can I upgrade across versions without losing data?
Yes, within the same major version. Run the new template version’s
deploy.sh against the existing RG — Bicep is idempotent and only
touches changed resources. Existing manifests and archive Parquet
stay put. Breaking changes (rare; major-version bumps only) are
called out under ### Breaking in CHANGELOG.md.