Skip to content

Commit 0c99ee0

Browse files
committed
Add Lab 17: Case study on securing a retail semantic-search workload in Azure SQL
1 parent 81c3288 commit 0c99ee0

2 files changed

Lines changed: 219 additions & 0 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
---
2+
lab:
3+
title: 'Lab 17 – Case study: Secure a retail semantic-search workload on Azure SQL'
4+
module: Plan and Secure Azure SQL for AI Workloads
5+
description: "You'll work through a retail semantic-search case study to complete the five pre-production tasks a DBA owns for an AI-enabled database. By the end, you'll understand how to plan and secure Azure SQL for AI workloads."
6+
duration: 20 # duration in minutes
7+
level: 300 # 100 basic concepts, 200 foundations, 300 practical usage, 400 advanced scenarios, 500 expert design
8+
islab: true # if this is not a lab that should be listed in the catalog, set to false
9+
status: 'in-development' # in-development or released
10+
targetDate: '2026-07-01' # Set to the future date when you expect an in-development lab to be released
11+
---
12+
13+
# Plan and secure Azure SQL for AI workloads
14+
15+
**Estimated Time: 20 minutes**
16+
17+
A retail company wants to add semantic product search to its catalog. The catalog has 8 million SKUs. The initial embedding plan is 1,536 dimensions at single precision, refreshed nightly by an elastic job. Application traffic queries those embeddings during search.
18+
19+
In this exercise, you work through the five pre-production decisions every DBA owns for a workload like this: a sizing worksheet, managed identity setup, external model creation, role assignment, and audit configuration. This is a design walkthrough, not a hands-on deployment — you reason about each decision and review the code it produces, but you don't need an Azure subscription or a running database to complete it.
20+
21+
> [!NOTE]
22+
> The Transact-SQL and Azure CLI snippets in this exercise are **reference examples** that show what each decision looks like in practice. You don't run them. Read each one to confirm it matches the decision you'd make, then adapt the placeholder names (`sql-retail-prod`, `<openai-resource>`, and so on) when you apply the pattern to a real workload.
23+
24+
| Decision | Why it matters |
25+
|---|---|
26+
| **Size the vector workload** | Vector columns and their indexes consume far more storage than typical relational data. Estimating the footprint up front lets you pick the right service tier and avoid costly resizing or throttling after go-live. |
27+
| **Enable system-assigned managed identity** | Authenticating with a managed identity means no API key is stored, rotated, or leaked. Azure Entra ID issues the credential and RBAC governs access, removing the most common secret-handling risk. |
28+
| **Create the external model** | The external model object pins the database to a specific Azure OpenAI deployment and API version. Callers reference one model object, so you update the endpoint in a single place instead of in every query. |
29+
| **Apply least-privilege roles** | Granting each principal only what it needs keeps the interactive query path away from the external endpoint. This blocks unauthorized access routes and caps unexpected cost from runaway embedding calls. |
30+
| **Enable audit and Defender** | Auditing external model calls and turning on Microsoft Defender for SQL give you a verifiable record of AI operations and proactive threat alerts, which are essential for compliance and incident response. |
31+
32+
## Decision 1: Size the vector workload
33+
34+
Fill in this worksheet for the scenario:
35+
36+
| Input | Value |
37+
|---|---|
38+
| Rows | 8,000,000 |
39+
| Dimensions | 1,536 |
40+
| Precision | float32 |
41+
| Per-row vector size | 6 KB |
42+
| Total vector column storage | **?** |
43+
| Estimated vector index overhead (~50%) | **?** |
44+
| Total AI-related storage | **?** |
45+
| Recommended tier | **?** |
46+
47+
### Ask Copilot to estimate the sizing
48+
49+
Rather than work the math by hand, you can ask GitHub Copilot in SSMS (or Copilot in the Azure portal) to do it. In the Copilot chat, paste a prompt that includes the known inputs:
50+
51+
> "I'm planning a vector column in Azure SQL. I have 8,000,000 rows, each storing a 1,536-dimension embedding at float32 (4 bytes per dimension). Estimate the per-row vector size, the total vector column storage, vector index overhead at about 50%, and the total AI-related storage. Then recommend an Azure SQL service tier for a read-heavy nightly-refresh-plus-search workload."
52+
53+
Copilot returns the per-row size, the totals, and a tier recommendation with its reasoning. Treat the result as a starting point and validate the numbers against the worked math below.
54+
55+
> [!TIP]
56+
> Always confirm Copilot's arithmetic and assumptions. Ask a follow-up such as "Show your calculation step by step" so you can check each figure, and "How would your tier recommendation change if the catalog grew to 50 million rows?" to pressure-test the design.
57+
58+
Work the math to verify Copilot's estimate:
59+
60+
- **Total vector column:** 8,000,000 × 6 KB ≈ **48 GB**.
61+
- **Vector index overhead at roughly 50% of the column:****24 GB**.
62+
- **Total AI-related storage:****72 GB**.
63+
- **Recommended tier:** **General Purpose, 40-vCore** fits the storage footprint and the read-heavy query pattern for nightly refresh plus interactive search. Choose **Hyperscale** instead if the catalog is expected to grow past roughly 50 million SKUs within 12 months, because Hyperscale decouples storage growth from compute scaling.
64+
65+
## Decision 2: Authenticate with a managed identity
66+
67+
The logical server needs an identity Azure OpenAI accepts. The design turns on the system-assigned managed identity and grants it the right RBAC role on the Azure OpenAI resource. The configuration looks like this:
68+
69+
```bash
70+
az sql server update \
71+
--name sql-retail-prod \
72+
--resource-group rg-retail \
73+
--identity-type SystemAssigned
74+
```
75+
76+
```bash
77+
SQL_MI_ID=$(az sql server show \
78+
--name sql-retail-prod \
79+
--resource-group rg-retail \
80+
--query identity.principalId -o tsv)
81+
82+
az role assignment create \
83+
--assignee $SQL_MI_ID \
84+
--role "Cognitive Services OpenAI User" \
85+
--scope <azure-openai-resource-id>
86+
```
87+
88+
With this configuration, no secret has been created and no API key exists. The server identity is the entire authentication path to Azure OpenAI.
89+
90+
## Decision 3: Define the external model objects
91+
92+
Inside the database, three objects support the external model: the master key (if one doesn't already exist), the credential bound to the managed identity, and the external model object itself. The script that defines them looks like this:
93+
94+
```sql
95+
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '<strong-password>';
96+
97+
-- The credential name must be the protocol + FQDN of the endpoint the model calls.
98+
CREATE DATABASE SCOPED CREDENTIAL [https://<openai-resource>.openai.azure.com]
99+
WITH IDENTITY = 'Managed Identity',
100+
SECRET = '{"resourceid": "https://cognitiveservices.azure.com"}';
101+
102+
CREATE EXTERNAL MODEL Ada2Embeddings
103+
WITH (
104+
LOCATION = 'https://<openai-resource>.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2023-05-15',
105+
API_FORMAT = 'Azure OpenAI',
106+
MODEL_TYPE = EMBEDDINGS,
107+
MODEL = 'text-embedding-ada-002',
108+
CREDENTIAL = [https://<openai-resource>.openai.azure.com]
109+
);
110+
```
111+
112+
The `LOCATION` value pins the model to a specific deployment and API version. If you change the deployment, you update the model object, not every caller's code.
113+
114+
## Decision 4: Apply least-privilege role grants
115+
116+
The role design calls for two principals, each granted exactly what it needs and no more. The grants look like this:
117+
118+
```sql
119+
-- Refresh service account
120+
CREATE USER [embedding_refresh_svc] WITHOUT LOGIN;
121+
GRANT EXECUTE ON EXTERNAL MODEL::Ada2Embeddings TO [embedding_refresh_svc];
122+
GRANT REFERENCES ON DATABASE SCOPED CREDENTIAL::[https://<openai-resource>.openai.azure.com] TO [embedding_refresh_svc];
123+
GRANT INSERT, UPDATE ON dbo.ProductEmbeddings TO [embedding_refresh_svc];
124+
125+
-- Query user: NO external endpoint permissions
126+
CREATE USER [app_query_user] WITHOUT LOGIN;
127+
GRANT SELECT ON dbo.ProductEmbeddings TO [app_query_user];
128+
```
129+
130+
The query user has no path to Azure OpenAI. Search queries read the stored vectors directly. Only the nightly refresh job touches the external endpoint.
131+
132+
## Decision 5: Enable audit and Defender
133+
134+
The final decision turns on SQL Audit for the external model and enables Microsoft Defender for SQL on the server:
135+
136+
```sql
137+
CREATE SERVER AUDIT [AuditAIOperations]
138+
TO URL (PATH = 'https://<staudit>.blob.core.windows.net/<container>');
139+
140+
ALTER SERVER AUDIT [AuditAIOperations] WITH (STATE = ON);
141+
142+
CREATE DATABASE AUDIT SPECIFICATION [AIDatabaseAudit]
143+
FOR SERVER AUDIT [AuditAIOperations]
144+
ADD (EXECUTE ON EXTERNAL MODEL::[Ada2Embeddings] BY [public])
145+
WITH (STATE = ON);
146+
```
147+
148+
```bash
149+
az security pricing create --name SqlServers --tier Standard
150+
```
151+
152+
> [!TIP]
153+
> When you apply this pattern for real, validate the end-to-end flow with a single embedding generation before declaring the security baseline complete. If `AI_GENERATE_EMBEDDINGS` returns successfully **and** the audit log captures the call from `embedding_refresh_svc`, the baseline works. If the audit row is missing, fix that before you let the nightly job run for real.
154+
155+
You've now worked through all five pre-production decisions — sizing the workload, configuring the identity path, defining the external model, scoping permissions, and turning on auditing — that every DBA owns for an AI-enabled database.
156+
157+
## Validate the design decisions
158+
159+
Answer the following questions based on the retail semantic-search case study. Select an answer for each question, then expand **Show answer** to check your reasoning.
160+
161+
**1. The logical server authenticates to Azure OpenAI using a system-assigned managed identity. Why is this preferred over storing an API key in the database?**
162+
163+
- **A.** Managed identities make the embedding queries run faster.
164+
- **B.** There's no secret to store, rotate, or leak — Azure Entra ID issues and rotates the credential automatically, and access is governed by RBAC.
165+
- **C.** API keys aren't supported by Azure SQL Database.
166+
167+
<details markdown="1">
168+
<summary>Show answer</summary>
169+
170+
**Correct answer: B.** There's no secret to store, rotate, or leak — Azure Entra ID issues and rotates the credential automatically, and access is governed by RBAC.
171+
172+
With a managed identity, no API key exists anywhere in the database or application. The server identity is the entire authentication path, so there's nothing to leak in source control or a connection string, and you grant or revoke access by changing an RBAC role assignment. Managed identities don't change query performance, and API keys are technically supported — they're just a weaker security posture.
173+
174+
</details>
175+
176+
**2. In the role design, the `app_query_user` that serves interactive search is granted `SELECT` on `dbo.ProductEmbeddings` but no permission on the external model or credential. Why?**
177+
178+
- **A.** Search queries read the stored vectors directly, so the query user never needs to call the external endpoint — only the nightly refresh job does.
179+
- **B.** Granting `SELECT` automatically includes external model access.
180+
- **C.** The query user can't be granted external model permissions in Azure SQL.
181+
182+
<details markdown="1">
183+
<summary>Show answer</summary>
184+
185+
**Correct answer: A.** Search queries read the stored vectors directly, so the query user never needs to call the external endpoint — only the nightly refresh job does.
186+
187+
This is least privilege in action. Embeddings are generated once per night by `embedding_refresh_svc` and stored in the table. Interactive search compares against those stored vectors with a simple `SELECT`, so the query user has no path to Azure OpenAI. Limiting external-endpoint permission to the single refresh account shrinks the attack surface and the potential cost exposure.
188+
189+
</details>
190+
191+
**3. The database scoped credential is named `[https://<openai-resource>.openai.azure.com]`. What rule does this naming follow?**
192+
193+
- **A.** The name can be any descriptive string the DBA chooses.
194+
- **B.** The name must be the protocol plus the fully qualified domain name (FQDN) of the endpoint the model calls.
195+
- **C.** The name must match the Azure OpenAI deployment name.
196+
197+
<details markdown="1">
198+
<summary>Show answer</summary>
199+
200+
**Correct answer: B.** The name must be the protocol plus the fully qualified domain name (FQDN) of the endpoint the model calls.
201+
202+
Azure SQL matches the credential to an outbound call by URL, so the credential name has to be the protocol and FQDN of the target endpoint (for example, `https://<openai-resource>.openai.azure.com`). The specific deployment and API version are pinned in the external model's `LOCATION`, not in the credential name.
203+
204+
</details>
205+
206+
**4. The catalog is 8 million SKUs today, sized comfortably on a General Purpose 40-vCore tier. Under what condition does the case study recommend Hyperscale instead?**
207+
208+
- **A.** Whenever the database uses vector search at all.
209+
- **B.** Only if the workload needs real-time embedding generation on every query.
210+
- **C.** If the catalog is expected to grow past roughly 50 million SKUs within 12 months, because Hyperscale decouples storage growth from compute scaling.
211+
212+
<details markdown="1">
213+
<summary>Show answer</summary>
214+
215+
**Correct answer: C.** If the catalog is expected to grow past roughly 50 million SKUs within 12 months, because Hyperscale decouples storage growth from compute scaling.
216+
217+
General Purpose fits the current ~72 GB AI-related footprint and the read-heavy nightly-refresh-plus-search pattern. Hyperscale becomes the better choice when storage is expected to grow rapidly, because it lets storage scale independently of compute. Vector search alone doesn't require Hyperscale, and the design here stores embeddings rather than generating them on every query.
218+
219+
</details>

Instructions/Labs/17-design-machine-learning-solution-case-study.md renamed to Instructions/Labs/18-design-machine-learning-solution-case-study.md

File renamed without changes.

0 commit comments

Comments
 (0)