惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Apple Machine Learning Research
Apple Machine Learning Research
爱范儿
爱范儿
博客园_首页
博客园 - 【当耐特】
V
Visual Studio Blog
博客园 - 叶小钗
月光博客
月光博客
美团技术团队
J
Java Code Geeks
小众软件
小众软件
Y
Y Combinator Blog
博客园 - Franky
Martin Fowler
Martin Fowler
博客园 - 聂微东
Microsoft Azure Blog
Microsoft Azure Blog
IT之家
IT之家
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
Microsoft Security Blog
Microsoft Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
酷 壳 – CoolShell
酷 壳 – CoolShell
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
云风的 BLOG
云风的 BLOG

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Materialized view patterns, trade-offs, and when to use e...
Hossein Esmati · 2026-06-26 · via DEV Community

Hossein Esmati

What do we mean by “materialized view” on Azure SQL?

SQL Server/Azure SQL doesn’t have Oracle-style materialized views; the closest native feature is an Indexed View (schema-bound view with a clustered index). In practice, teams use three flavors:

Summary and key points

  • Materialized read models (indexed views or custom projection tables) absolutely help large-scale pagination by removing runtime work and enabling perfect indexes for your cursor pattern.
  • Use custom read models + outbox for maximum control and minimal read latency; use indexed views when you want SQL Server to maintain a specific aggregation/join.
  • Keep seek/cursor pagination—it’s the core scalability lever. Materialization just makes each page cheaper.
  • Add Redis page/window caching for super-hot lists, and move faceted text search to Azure AI Search/Elasticsearch if needed.

1. Indexed View (native)

  • What it is: A VIEW ... WITH SCHEMABINDING + clustered index that stores view rows physically.
  • When it shines: Expensive joins/aggregations that are stable and heavily reused.
  • Impact on pagination: You paginate over the view with a covering index aligned to your sort key, so the engine skips the big base-table joins at runtime.
  • Costs/constraints: Write penalty (maintained on every insert/update), schema rules (determinism, no *, etc.).

2. Materialized Read Model Table (custom)

  • What it is: A denormalized table that you keep in sync (CQRS projection). Often called a “projection,” “read model,” or “summary table.”
  • Sync options:

    • Streaming via outbox + background dispatcher (near-real-time, strong control).
    • CDC/Change Tracking + ETL job (near-real-time or batch).
  • Impact on pagination: You tailor the table to the exact API shape, add a clustered index on (SortKey, Id) and include only the columns needed by the list page → extremely fast keyset pagination.

  • Costs: Extra storage + write/update path. You must design rebuild/backfill procedures.

3. Precomputed Page Cache (ephemeral)

  • What it is: Cache page windows (e.g., first 50, next 50 cursors) in Redis keyed by filter + sort + cursor.
  • Impact: Removes repeat read costs for hot feeds and “first page” traffic.
  • Costs: Cache invalidation; combinatorial explosion for many filter combos (use selectively).

Do they improve pagination performance?

Yes, by shrinking the query work per page:

  • No/less joining at runtime.
  • Narrow, page-friendly rows (no wide payload).
  • Perfectly aligned indexes for your sort/filter.
  • Fewer logical reads, lower CPU, and better P95/P99.

But remember: seek/cursor pagination is still required for huge data. Materialization won’t fix OFFSET/FETCH’s deep-page slowness.

Design playbook (10M+ rows)

1) Choose your read model

  • If your list endpoint needs multiple joins, computed fields, or rollups: Use a custom read model table or indexed view.
  • If writes are heavy and latency tolerance is low (OLTP): Prefer a read model table updated asynchronously (outbox/CQRS). Indexed views add write latency.

2) Shape for pagination

  • Store exactly what the endpoint needs (no N+1 lookups).
  • Clustered index: (SortKey, Id) in the same direction you present (often DESC for newest-first).
  • Covering index: If the clustered key differs (for example, you cluster by Id for other reasons), add a nonclustered index on (SortKey, Id) INCLUDE (<DTO columns>).

3) Keep it fresh

  • Outbox + projector (recommended):

    • On each domain write, append to Outbox.
    • A background worker consumes and upserts the read model.
    • Great observability and backpressure; deterministic.
  • CDC/Change Tracking:

    • Poll changes; apply upserts.
    • Simpler to add later; a bit more laggy.

4) Paginate with a cursor (seek)

  • Stable order (e.g., CreatedAtUtc DESC, Id DESC).
  • Seek predicate using last item’s (SortKey, Id) from a signed pageToken.
  • Projection to DTO in the query (don’t materialize entities).

5) Validate filters & align indexes

  • Whitelist filter fields.
  • If you commonly filter by Status, build (Status, SortKey, Id) index and INCLUDE the display columns.

Alternatives & complements

A) Indexed View vs Read Model Table

  • Indexed View: Zero custom sync code; SQL Server maintains it. Great for deterministic aggregates. But it taxes writes and is harder to evolve.
  • Read Model Table: Maximum control, cheapest reads, and you can store denormalized JSON or precomputed projections. Needs a projector (worker) and backfill logic.

B) Search engine for faceted filtering

  • For free-text + many facets, push to Azure AI Search/Elasticsearch.

    • Use search cursors (continuation tokens) and rely on their inverted indexes.
    • Keep SQL for exact transactional lists; use search for discovery/exploration.

C) Partitioning & storage options

  • Range partitioning on date can keep working sets small (monthly tables or partition function).
  • Columnstore is excellent for analytics scans, but not ideal for cursor pagination of OLTP feeds; prefer rowstore + narrow covering indexes.

D) Cosmos DB

  • If your data is already in Cosmos, create a projection container tailored to the list shape; use SDK continuation tokens and ORDER BY createdAt, id with partition-aligned queries.

When not to use materialization

  • If the base list is already a single table with a perfect covering index and no computed fields, adding a read model won’t move the needle much—the seek query is already optimal.
  • If write throughput is extreme and the projection would add unacceptable write amplification, prefer on-the-fly with careful indexing or consider eventual consistency projections for read paths that can tolerate lag.

Practical .NET implementation sketch

Outbox + projector (EF Core 9)

  1. In your command handler, write domain changes and append OutboxEvent in the same transaction.
  2. A hosted service (or Function/Worker) polls unsent events and UPSERTs the ReadModel_Items table.
  3. The list endpoint queries ReadModel_Items with the seek pattern and emits a signed nextPageToken.

SQL for read model

CREATE TABLE ReadModel_Items
(
  Id UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
  CreatedAtUtc DATETIME2(3) NOT NULL,
  Status TINYINT NOT NULL,
  Name NVARCHAR(200) NOT NULL,
  -- ...other columns the DTO needs only
  -- optional: a JSON column for flexible metadata
);

-- Fast seek for newest-first
CREATE UNIQUE CLUSTERED INDEX CIX_ReadModel_Items_Created_Id
ON ReadModel_Items (CreatedAtUtc DESC, Id DESC);

-- Common filter: by Status
CREATE INDEX IX_ReadModel_Items_Status_Created_Id
ON ReadModel_Items (Status, CreatedAtUtc DESC, Id DESC)
INCLUDE (Name /*, other DTO columns */);

Endpoint (pseudo)

// Decode pageToken -> (lastCreatedAtUtc, lastId)
var q = db.ReadModelItems.AsNoTracking();

q = q.OrderByDescending(x => x.CreatedAtUtc).ThenByDescending(x => x.Id);

if (cursor != null)
    q = q.Where(x => x.CreatedAtUtc < cursor.CreatedAtUtc
                  || (x.CreatedAtUtc == cursor.CreatedAtUtc && x.Id < cursor.Id));

if (statusFilter is not null)
    q = q.Where(x => x.Status == statusFilter);

var items = await q
    .Select(x => new ItemDto(x.Id, x.Name, x.Status, x.CreatedAtUtc))
    .Take(pageSize)
    .ToListAsync(ct);

// Emit nextPageToken from last item’s keys (signed)