-
Notifications
You must be signed in to change notification settings - Fork 12
URLs Full URL Shrink Upgrade
This release converts dbo.urls.full_url to nvarchar(850) and adds a non-clustered index IX_urls_full_url, plus snapshot-refresh migrations. They run automatically as part of the normal solution upgrade and are designed to be safe on customer databases with very large urls tables (tens of millions of rows), but on large tables the upgrade can take a long time. Read this page before you run the upgrade.
Why
nvarchar, notvarchar? An earlier build shipped this column asvarchar(1700), which stores only a single code page and silently corrupted non-Latin (e.g. Greek) SharePoint URLs to?— for example…/Καλημέρα κόσμε.pdfbecame…/?a??µ??a ??sµe.pdf. The column is nownvarchar(850):nvarcharholds the full Unicode range, and 850 Unicode characters = 1700 bytes = the SQL Server non-clustered index-key limit, so the column stays index-seekable. Both supported upgrade-from states reachnvarchar(850)losslessly and preserve Greek URLs (see §1).
The same information is intended for operators / admins with some technical background but who aren't SQL specialists.
Want the plain-English version of what happens to long URLs when data is imported (which links get shortened, and how)? See How URLs are shortened.
EF migrations against the urls table. Which ones run depends on the build you're upgrading from:
-
202606011710001_ShrinkUrlsFullUrlColumn— does the real work: convertsfull_urltonvarchar(850)and creates the supporting non-clustered indexIX_urls_full_url. It is idempotent and converts from any earlier shape ((n)varchar(max)or the supersededvarchar(1700)). This is the migration that runs when you upgrade from a build whosefull_urlis still(n)varchar(max). -
202606011739254_UrlFullUrlVarcharMapping—Up/Downare empty (superseded — it was the snapshot refresh for the oldvarchar(1700)form). Runs in milliseconds; ignore it for capacity planning. -
202606141000001_UrlFullUrlNvarchar— replays the idempotent converter and refreshes the EF Code-First model snapshot tonvarchar(850)(so theUrl.FullUrlmapping doesn't triggerAutomaticDataLossExceptionat everyAnalyticsEntitiesContextconstruction). For databases already on the earliervarchar(1700)build this is the migration that performs thevarchar(1700) → nvarchar(850)conversion; for everyone else it's a millisecond no-op.
Two supported upgrade-from states, both lossless — Greek/Unicode URLs are preserved:
-
From a pre-shrink build (
full_url=(n)varchar(max)):ShrinkUrlsFullUrlColumnwidens straight tonvarchar(850). A pure Unicode-preserving change. -
From the
varchar(1700)build:UrlFullUrlNvarcharconvertsvarchar(1700) → nvarchar(850)— a lossless widen. (Anything that reached that build had already passed the old build's representability check, so itsvarchardata is fully representable and converts faithfully.)
urls.full_url is the join / de-duplication key for every staging-table merge in the importers (Migrate Hits Import into Hits.sql, Insert Activity from Staging Table.sql, the Copilot SharePoint variant, and the App Insights clicks variant). As an (n)varchar(max) LOB column it cannot be a B-tree index key, so every one of those joins is forced into a full scan of the (largest dimension) urls table with LOB string comparisons. 1700 bytes is the SQL Server single-column non-clustered index-key limit, and the column must be nvarchar (not varchar) so it can hold any Unicode URL (e.g. Greek) without corruption — nvarchar is 2 bytes/char, so nvarchar(850) (= 1700 bytes) is the widest indexable Unicode URL column.
For each step the actual time depends on how many rows are in urls.
-
Skip-if-already-done check. If the column is already
nvarchar(850)andIX_urls_full_urlalready exists, the migration writes a one-line message and exits in milliseconds. Safe to re-run. -
Pre-flight data check. The migration scans
urlsonce before changing anything for too-long URLs — rows whereLEN(full_url) > 850, which wouldn't fit the new column and would otherwise be truncated. There is no longer any "lossy conversion" check: the target isnvarchar, which represents every Unicode character, so Greek (and any other script) converts faithfully. The representability abort that blocked Greek customers on the oldvarcharbuild is gone.If the scan finds rows, the migration lists up to 50 of them (
id+full_url), prints the exact diagnostic query, and aborts without changing anything. See "If the migration aborts" below. -
Convert the column.
ALTER COLUMN full_url nvarchar(850) NOT NULL. This is a size-of-data rewrite: SQL Server has to read every URL and re-store it inline in the clustered index leaf pages. It holds a schema-modification lock onurlsfor its full duration. This is the dominant cost. -
Build the index.
CREATE NONCLUSTERED INDEX IX_urls_full_url ON urls(full_url). On Enterprise / Azure SQL DB / Azure SQL MI this is builtONLINE = ON, meaning normal reads and writes continue while it's building. On Standard / Web / Express / Developer SQL Server editions it's built offline (writes wait).
All steps stream live progress + per-step timing via RAISERROR ... WITH NOWAIT — watch them in the SSMS / Azure Data Studio Messages tab or in the SQL session running the migration.
| Before | After |
|---|---|
urls.full_url is nvarchar(MAX) (LOB column, can't be an index key) |
urls.full_url is nvarchar(850) NOT NULL (Unicode-safe) |
No supporting index on full_url
|
IX_urls_full_url (non-clustered) |
Every staging-merge full-scans urls with LOB comparisons |
Staging merges seek IX_urls_full_url
|
| EF emits parameters that don't match the column → implicit conversion would defeat the index |
Url.FullUrl is mapped as nvarchar(850) → seeks the index |
- It does not delete any rows. If pre-flight finds offending rows it aborts the whole migration — the operator fixes the data and re-runs. It will never silently truncate or corrupt URLs.
-
It does not touch
hits,event_meta_sharepoint, or any other table that referencesurls(id)viaurl_id. Foreign-key references continue to point at the sameurls.idrows. -
It is not reversible without losing the index. A
Downmigration is provided (drops the index, widens the column back tonvarchar(MAX)), but there is no data to undo because nothing about row identity was changed.
The upgrade was benchmarked against a freshly seeded database in a controlled environment. These figures were measured on the superseded varchar(1700) form of the migration. The operation for nvarchar(850) is the same shape, with two differences: (1) the lossy/representability pre-flight scan no longer runs, so the pre-flight is faster; and (2) nvarchar stores 2 bytes/char, so for typical (mostly-ASCII) URLs the rewritten column and index are larger — expect somewhat more transaction log, tempdb and final on-disk size than the numbers below, and budget headroom accordingly. Treat the timings as a representative order-of-magnitude guide, not a precise prediction.
| Property | Value |
|---|---|
urls row count |
10,000,000 |
| Distribution | All clean ASCII URLs, average 152 characters each (realistic SharePoint paths). None over 850 chars. |
Pre-migration urls size on disk |
3.27 GB (LOB-dominated) |
Post-migration urls size on disk |
8.19 GB (clustered PK now holds the URLs inline; new NC index = 1.68 GB) |
| SQL Server | SQL Server 2025 (RTM-GDR, 17.0.1115.1), Standard Developer Edition (EngineEdition = 2), default localhost instance |
| Host | Windows VM, Standard HDD disks (~500 IOPS, ~60 MB/s, ~10 ms latency — the slowest Azure storage tier) |
ONLINE index? |
No — EngineEdition = 2 doesn't qualify, so IX_urls_full_url was built offline. This is the pessimistic / non-Enterprise customer case. |
Total ShrinkUrlsFullUrlColumn elapsed |
6 min 6 sec (366 sec) |
| Per-row cost | ~36.6 µs/row (4.3 µs scans + 13.3 µs ALTER + 19.0 µs CREATE INDEX) |
| Post-state | Column is nvarchar(850) NOT NULL, IX_urls_full_url present, row count unchanged |
| Step | Duration | % of total |
|---|---|---|
Pre-flight "too long" scan (LEN(full_url) > 850) |
2.5 sec | <1% |
ALTER COLUMN full_url nvarchar(850) NOT NULL |
2 min 12.7 sec | ~41% |
CREATE INDEX IX_urls_full_url (OFFLINE) |
3 min 9.9 sec | ~58% |
| Total | ~5 min 25 sec | 100% |
The original
varchar(1700)benchmark also ran a "lossy varchar" pre-flight scan (40.9 sec) that no longer exists —nvarcharneeds no representability check — so removing it brought the measured total down from 6 min 6 sec to ~5 min 25 sec. TheALTER COLUMN/CREATE INDEXdurations shown are from thevarcharrun; onnvarchar(850)they will be somewhat longer because the rewritten data and index hold 2 bytes/char.
Why this is a "best-case at 10M, worst-case at 100M+" baseline. The test VM has Standard HDD storage, which is the slowest disk type Azure offers — about 500 IOPS and 60 MB/s with high (~10 ms) latency. At 10M rows the pre-migration
urlstable is only ~3 GB and fits comfortably in the SQL Server buffer pool, so the pre-flight scans were served almost entirely from memory and the slow HDD barely mattered. As the table grows past what fits in memory (somewhere between 25M and 50M rows on a typical SQL instance), the Standard HDD becomes the bottleneck and the per-row cost gets dramatically worse. Note also that on Standard Developer Edition the index is built offline — Enterprise / Azure SQL DB / Azure SQL MI build itONLINEand finish in a comparable wall-clock time but without blocking writers. All Azure SQL DB tiers (even the cheapest) use SSD, so most Azure SQL DB customers will see times in line with — or better than — the Standard tier table in §4 below.
Before you look at the time estimates, run this against your database to see which row in the tables you should be looking at. It also surfaces how many URLs would cause the migration to abort, and whether the column/index are already in place (so the upgrade would be a no-op).
-- Instant estimate (reads metadata only, no table scan - safe on any DB size)
SELECT
OBJECT_NAME(p.object_id) AS table_name,
SUM(p.rows) AS estimated_row_count
FROM sys.partitions p
WHERE p.object_id = OBJECT_ID('dbo.urls')
AND p.index_id IN (0, 1) -- heap or clustered index only
GROUP BY p.object_id;
-- Already-applied check - if both come back 1, the upgrade is a no-op
SELECT
(SELECT CASE WHEN t.name = 'nvarchar' AND c.max_length = 1700 THEN 1 ELSE 0 END
FROM sys.columns c
INNER JOIN sys.types t ON c.user_type_id = t.user_type_id
WHERE c.object_id = OBJECT_ID('dbo.urls') AND c.name = 'full_url') AS column_already_nvarchar850,
(SELECT COUNT(*) FROM sys.indexes
WHERE object_id = OBJECT_ID('dbo.urls') AND name = 'IX_urls_full_url') AS index_already_present;
-- (nvarchar(850) reports max_length = 1700 bytes, i.e. 850 chars x 2 bytes.)
-- Will the migration abort? (full scan - may take a while on huge tables;
-- skip if you just want time estimates and trust that your URLs are well-formed)
-- Only one condition can abort the upgrade: a URL longer than 850 characters.
-- (There is no "representable as varchar" check any more - nvarchar stores all Unicode,
-- so Greek and other non-Latin URLs are preserved, not rejected.)
SELECT COUNT_BIG(*) AS urls_longer_than_850_chars
FROM dbo.urls WHERE LEN(full_url) > 850;What to do with the numbers:
-
urlsestimated_row_count→ pick the corresponding row in §3 (if you self-host SQL on a VM) or §4 (if you're on Azure SQL DB). -
column_already_nvarchar850 = 1ANDindex_already_present = 1→ the migration will detect this and exit in seconds. No action needed. -
urls_longer_than_850_chars> 0 → fix the offending rows before kicking off the upgrade; otherwise the migration will abort (safely, without changing anything) and you'll have to fix them and re-run. See §6.
The work the upgrade does is essentially linear in the number of rows in urls — as long as the table fits in memory. Once it doesn't, every page has to be read from disk and the per-row cost balloons on slow storage. The pre-migration urls table is roughly (avg URL length + 30) × rowcount bytes for the data and a constant ~50% overhead for the LOB structures.
urls rows |
Estimated upgrade time | Notes |
|---|---|---|
| 1,000,000 (1M) | ~40 sec | Fits in memory |
| 5,000,000 (5M) | ~3 min | Fits in memory |
| 10,000,000 (10M) | ~6 min (measured) | Fits in memory; HDD largely bypassed |
| 25,000,000 (25M) | ~15 – 25 min | Starting to spill from memory |
| 50,000,000 (50M) | ~45 min – 1.5 hr | HDD becomes painful |
| 100,000,000 (100M) | ~3 – 6 hr | Mostly disk-bound; 500-IOPS / 60 MB/s HDD is the bottleneck |
| 250,000,000 (250M) | ~10 – 18 hr | Sequential scan throughput-limited by HDD |
| 500,000,000 (500M) | ~1.5 – 2 days | HDD throughput is the wall; not realistic without better storage |
If you're running SQL Server on an Azure VM and you have 25M+
urls, the single highest-impact change you can make is to switch the data disk from Standard HDD to Premium SSD (P30 or above) before applying the migration. This alone would shave 5–10× off the times above. Even Standard SSD would be a major improvement over Standard HDD for tables that no longer fit in memory.
If you're on Standard / Web / Express / Developer Edition the new index is built offline (the
CREATE INDEXline above). On Enterprise / Azure SQL DB / Azure SQL MI that same step runsONLINE = ON— wall-clock is similar, but other reads and writes againsturlsaren't blocked.
Good news for Azure SQL DB customers: every Azure SQL DB tier — even the cheapest — uses SSD storage under the hood. So the "table doesn't fit in memory" cliff that hits hard on a Standard HDD test VM is much gentler in Azure SQL DB. The dominant cost on Azure SQL DB is the DTU cap on CPU and log throughput (because both ALTER COLUMN and CREATE INDEX are fully logged), not raw disk IO.
These are estimates with a wide error margin (±50%). Real-world numbers depend on how busy your DB is, what's already cached, log throttling, and other concurrent workload. Treat them as orders of magnitude.
| Tier | DTUs | Max log throughput | Max data IOPS | Notes |
|---|---|---|---|---|
| S3 | 100 | ~2.5 MB/s | ~320 |
ALTER COLUMN log writes will be the bottleneck |
| S4 | 200 | ~5 MB/s | ~640 | |
| S6 | 400 | ~10 MB/s | ~1,280 | A reasonable sweet spot for a one-off upgrade |
| S7 | 800 | ~20 MB/s | ~2,560 | Recommended if you have 50M+ rows |
For context: the 10M-row baseline writes roughly 6 GB of transaction log (3.3 GB ALTER COLUMN row rewrite + 1.7 GB index leaf + overhead). This scales linearly with urls rowcount.
| Rows | S3 (100 DTU) | S4 (200 DTU) | S6 (400 DTU) | S7 (800 DTU) |
|---|---|---|---|---|
| 1M | ~5 – 8 min | ~3 – 5 min | ~1 – 2 min | <1 min |
| 10M | ~45 – 70 min | ~25 – 40 min | ~12 – 20 min | ~6 – 10 min |
| 50M | ~4 – 6 h | ~2 – 3 h | ~1 – 1.5 h | ~30 – 50 min |
| 100M | ~8 – 12 h | ~4 – 6 h | ~2 – 3 h | ~1 – 1.5 h |
| 250M | ~20 – 30 h | ~10 – 15 h | ~5 – 7 h | ~2.5 – 3.5 h |
| 500M | ~1.5 – 2.5 days | ~20 – 30 h | ~10 – 14 h | ~5 – 7 h |
How to read this table: if you're seeing hours-on-end (let alone days), that's a strong signal to scale up before the upgrade — see §5. The cost of running on a higher tier for a few hours is trivial compared to a multi-day outage window.
Premium tier is dramatically faster. If your
urlstable is in the 50M+ range, scaling temporarily to Premium (P2 / P4 / P6) is the best move. Premium has order-of-magnitude better IOPS (P2 = 4,000 IOPS, P6 = 16,000 IOPS) and log throughput. As a rough guide, P2 will finish the upgrade in roughly half the time of S7, and P6 in roughly a quarter — and you can scale back down immediately afterwards.
Do these before you start the upgrade, in this order:
The web jobs continuously hit urls from the staging-table merges. Leaving them running during the migration will (a) make the ALTER COLUMN much slower because of the schema-modification lock contention, and (b) almost certainly cause the web jobs themselves to time out and fail.
- In the Azure Portal, navigate to your App Service / Function App and stop the analytics web jobs (
WebJob.Office365ActivityImporter,WebJob.AppInsightsImporter, and any other importer jobs). - Wait ~30 seconds for any in-flight batches to finish committing.
A Power BI refresh against a busy urls/hits/audit_events workload during the migration will:
- Compete for IO with the
ALTER COLUMNrewrite and theCREATE INDEXsort. - Probably fail anyway because the new index/column type temporarily change query plans.
Open the dataset(s) in the Power BI Service, disable scheduled refresh (or change the schedule to skip the upgrade window), and cancel any refresh that's already in progress.
This migration is storage-intensive. Post-migration urls grows because the URLs move from LOB pages into the clustered PK leaf pages and are now stored as 2-byte-per-char nvarchar. On the superseded varchar(1700) form this measured ~2.5× the pre-migration size; for nvarchar(850) budget roughly 3.5–4× (the benchmark figures elsewhere on this page were measured on varchar). Before kicking off:
-
Data file: ensure the data file (or auto-grow) has headroom for ~
4 × current urls size. Example: a 30 GBurlstable can need ~120 GB total post-migration, so ~90 GB free in the data file (or in the volume the data file can grow into). -
Transaction log: allow free log space on the order of
2–3 × urls data size. On SIMPLE recovery the log auto-truncates between steps; on FULL recovery take a log backup before the upgrade and another straight after. -
tempdb: the
CREATE INDEXsort spills to tempdb. Make sure tempdb has at least ~2 GB free per 10M URLs being sorted.
If your DB is on a lower DTU tier (S3 / S4 / S6) and you have more than ~25M urls, scale up before the upgrade. The cost of running S7 (or even Premium) for a few hours is trivial compared to the time saved.
A reasonable pattern:
- Scale up to S9 (1600 DTU) or a Premium P2 / P4 tier just before starting.
- Apply the migration.
- Verify the new column type and index are present (see §7).
- Scale back down to your normal tier.
Scaling Azure SQL DB up or down is online — your DB stays available during the resize, with a brief (single-digit-second) reconnect at the end. Plan to do the scale-up at least a few minutes before kicking off the migration so it's settled in.
Azure SQL DB takes automatic backups, but confirm the most recent backup is recent (last few hours) before starting. If anything goes wrong you want a clean rollback point.
Use the estimates in §3 and §4 and multiply by 2 when picking the maintenance window. Things you don't want to discover at the 90% mark:
- The DB was busier than you thought.
- A long-running query held a lock on
urlsand theALTER COLUMNwas waiting on it. - The auto-generated tempdb / log file growth was throttled.
- The pre-flight scans found offending URLs and aborted, and now you need to fix them and re-run.
If the pre-flight scan finds any URL that would be truncated (>850 chars), the migration aborts before changing anything and prints up to 50 offending rows on the SQL session messages. The database is left in exactly the state it was in before — there is no partial-apply state to clean up. (Non-Latin characters such as Greek do not abort the migration — nvarchar(850) stores them faithfully; only length matters.)
To find every offending row:
-- Too long: would be truncated to 850 chars
SELECT id, LEN(full_url) AS length, full_url
FROM dbo.urls
WHERE LEN(full_url) > 850
ORDER BY length DESC;For each offending row, decide whether to:
-
Delete the URL row (and the
hits/event_meta_sharepoint/ etc. rows that reference it viaurl_id). Usually appropriate for obsolete or test URLs. - Correct the URL — e.g. trim the query string that pushed it past 850 chars.
Then re-run the upgrade. The migration is idempotent: it will pick up where the previous run left off.
- Watch the SQL session running the migration. The script uses
RAISERROR ... WITH NOWAITto surface live progress — you'll see messages like:ShrinkUrlsFullUrlColumn: dbo.urls row estimate = 10000000 ... ShrinkUrlsFullUrlColumn: pre-flight "too long" scan completed in 2511ms; 0 offending row(s). ShrinkUrlsFullUrlColumn: altering full_url to nvarchar(850) NOT NULL... ShrinkUrlsFullUrlColumn: ALTER COLUMN completed in 132690ms. ShrinkUrlsFullUrlColumn: EngineEdition=2, ONLINE=0. Creating [IX_urls_full_url]... ShrinkUrlsFullUrlColumn: [IX_urls_full_url] created in 189933ms. ShrinkUrlsFullUrlColumn: finished in 325136ms. - It is safe to leave it running, however long it takes. The installer's EF migration runner sets
CommandTimeout = 0(infinite), so the upgrade will not give up part-way throughALTER COLUMNeven if it runs for many hours. The migration also runs outside the EF migration transaction (suppressTransaction: true), so each step commits as it completes. If the connection drops mid-way throughCREATE INDEX, you can simply re-run the upgrade — the early steps are idempotent (they check whether they've already been done) and SQL Server will rebuild any partially-built index from scratch.
Run this against your DB:
SELECT
(SELECT COUNT_BIG(*) FROM dbo.urls) AS urls_total,
(SELECT t.name + '(' + CASE WHEN c.max_length = -1 THEN 'max'
WHEN t.name LIKE 'n%' THEN CAST(c.max_length / 2 AS varchar(10))
ELSE CAST(c.max_length AS varchar(10)) END + ')'
FROM sys.columns c
INNER JOIN sys.types t ON c.user_type_id = t.user_type_id
WHERE c.object_id = OBJECT_ID('dbo.urls') AND c.name = 'full_url'
) AS full_url_type,
-- should be 'nvarchar(850)'
(SELECT COUNT(*) FROM sys.indexes
WHERE object_id = OBJECT_ID('dbo.urls') AND name = 'IX_urls_full_url'
) AS ix_present,
-- should be 1
(SELECT COUNT_BIG(*) FROM dbo.urls WHERE LEN(full_url) > 850) AS still_too_long;
-- should be 0Expected: full_url_type = 'nvarchar(850)', ix_present = 1, still_too_long = 0, urls_total unchanged from before.
Then check that the IX_urls_full_url index is actually being used by the staging merges (this is the whole reason for the migration). After the first importer run completes, the plan for the staging-merge join should show an index seek on IX_urls_full_url rather than a clustered-index scan. The simplest way to confirm:
SELECT TOP 5
s.user_seeks, s.user_scans, s.last_user_seek, s.last_user_scan
FROM sys.dm_db_index_usage_stats s
INNER JOIN sys.indexes i ON s.object_id = i.object_id AND s.index_id = i.index_id
WHERE s.database_id = DB_ID()
AND i.name = 'IX_urls_full_url';user_seeks should be incrementing every time the App Insights / Activity / Copilot importers run.
Once that's confirmed:
- Re-enable Power BI scheduled refresh.
- Restart the web jobs.
- Scale the DB back down to its normal tier.
-
What changes:
urls.full_urlbecomesnvarchar(850) NOT NULL(Unicode-safe — preserves Greek and any non-Latin URLs) and gets a new non-clustered indexIX_urls_full_url. Row count is identical before and after. -
Companion migrations:
UrlFullUrlVarcharMapping(empty, superseded) andUrlFullUrlNvarchar(refreshes the EF model snapshot tonvarchar(850)). Both are millisecond no-ops — except when upgrading from the oldvarchar(1700)build, whereUrlFullUrlNvarcharperforms thevarchar(1700) → nvarchar(850)conversion. - Time: ~37 µs per row when the table fits in memory; significantly worse once it doesn't on slow storage like Standard HDD. Azure SQL DB (all tiers use SSD) degrades much more gracefully.
-
Storage cost: post-migration
urlsgrows because URLs move from LOB pages into the clustered PK leaf pages and are stored as 2-bytenvarchar. Budget ~3.5–4× currenturlssize in data-file headroom (the ~2.5× figure elsewhere on this page was measured on the oldvarchar(1700)form). - Worst-case time: ~2 days for 500M rows on S3 (100 DTU), or longer on a Standard HDD VM. Scale up.
-
Best ROI tweak:
- Azure SQL DB customers: temporarily scale to S7 or Premium (P2 / P4 / P6) for the upgrade — the runtime drops several-fold.
- VM-hosted SQL customers: if your data disk is Standard HDD or Standard SSD and you have 25M+ rows, switch the data disk to Premium SSD before the upgrade.
- Before: stop web jobs, disable Power BI refresh, free up data-file headroom, scale up, confirm backup, check for URLs longer than 850 chars so the migration doesn't abort (non-Latin / Greek URLs are fine — only length matters).
- During: watch the SQL session messages; safe to leave running for as long as it takes — the installer's EF migration command timeout is infinite, so it won't abort mid-rewrite.
-
After: verify
full_urlisnvarchar(850),IX_urls_full_urlis present, row count is unchanged, then scale back and re-enable jobs.
Based on a controlled benchmark of DatabaseUpgrader.CheckDbUpgraded against a freshly seeded database with 10,000,000 urls rows (avg URL 152 chars) on an Azure VM with Standard HDD disks and SQL Server 2025 Standard Developer Edition (the offline-index case). The benchmark was measured on the superseded varchar(1700) form (total 6 min 6 sec, including a ~41 sec "lossy varchar" scan that no longer runs). The nvarchar(850) operation is the same shape, with somewhat larger storage / log because nvarchar is 2 bytes/char.
- Home
- What data is collected
- The web portal
- Licence activity
- Copilot data & stats
- Architecture & costs
- App registrations setup
- Install with the installer
- Manual installation
- Private endpoints (optional)
- Certificate authentication (optional)
- Enable CSP for AITracker
- Verify the deployment
- Legacy SPO web setup