Index Tuning Report – RockyPC / SQLStorm – 2026-06-14

Tuning Goal: Index Tuning
Server
RockyPC
Database
SQLStorm
Version
SQL Server 2022 (16.x), 16.0.1180.1, RTM-GDR
Edition / Engine
Developer Edition / Enterprise capabilities available
Online index operations available Page compression available Columnstore available FORCE_LAST_GOOD_PLAN enabled

Executive summary

3
High-priority actions
18
Untrusted foreign keys
0
Duplicate/overlapping indexes to drop
>98%
Observed page density on major indexes
  1. Create a filtered nonclustered index on dbo.PostHistory(PostId, CreationDate) for PostHistoryTypeId = 10. This is the strongest index opportunity because multiple expensive Query Store statements join posts to close-history rows using PH.PostHistoryTypeId = 10 and PH.PostId = P.Id. A filtered design keeps storage modest while targeting a highly repeated access pattern. Confidence: High.
  2. Broaden the existing IX_Votes_Bounty8_PostId to also include VoteTypeId. The overlap signal shows a perfect key match on PostId, and many slow statements aggregate votes by post while branching on vote type. Reusing the existing index name avoids adding another nearly redundant structure. Confidence: High.
  3. Re-trust all untrusted foreign keys. This is a broad optimizer-quality fix. Untrusted foreign keys prevent join elimination and reduce cardinality quality in a schema that is heavily join-driven. Because the workload is largely analytical/read-heavy and current write pressure is effectively zero in the sample, the validation scan cost is usually worth it. Confidence: High.
  • No missing-index DMV rows were present, so recommendations are driven primarily by Query Store workload, overlap signals, cardinality, and existing index layout.
  • No duplicate or left-prefix overlapping indexes were detected; no index drops are recommended.
  • Page density and fragmentation are already excellent; no rebuild or fill factor changes are justified.
  • No forced plan failures were detected.
  • FORCE_LAST_GOOD_PLAN is already enabled and correctly aligned with desired state.
  • Columnstore is not recommended at this time: no table exceeds the stated 1,000,000-row threshold, and the workload is mixed rather than clearly warehouse-only.

Environment

Area Observation Assessment
Workload mix Mostly large analytical CTEs, aggregates, ranking functions, and repeated joins across Posts, Votes, Comments, Badges, and PostHistory. Indexing should target common join/aggregation paths rather than isolated singleton lookups.
Writes vs reads Reported user_updates = 0 for indexes in scope. Additional nonclustered indexes have unusually low observed maintenance risk in this sample.
Compression Most larger indexes already use PAGE compression. Keep PAGE compression on new/rebuilt indexes for consistency and storage efficiency.
Statistics settings AUTO_CREATE_STATS = ON, AUTO_UPDATE_STATS = ON, async off. Good baseline; targeted stats refresh is still worthwhile after structural changes.
Version posture SQL Server 2022 RTM-GDR build 16.0.1180.1. Functional, but older than current CU lines; monitor for optimizer fixes outside this report’s index scope.

Visual overview

Index tuning focus areas Diagram showing Posts as the hub table joined to Votes, Comments, Badges, and PostHistory, with index recommendations on Votes and PostHistory and a schema-wide constraint trust recommendation. dbo.Posts Hub for most expensive queries dbo.Votes Modify existing PostId index dbo.PostHistory Add filtered close-event index Foreign keys Re-trust 18 untrusted constraints

Diagram: Most costly statements center on Posts with repeated joins to Votes and PostHistory; the best physical design gains are concentrated there, while foreign-key trust improves optimizer behavior across the whole schema.

Detailed prioritized recommendations

1) Add a filtered index for close-history lookups on dbo.PostHistory

  • Recommendation: Create IX_PostHistory_PostId_CreationDate_CloseType10 on (PostId, CreationDate) including (Comment) with filter WHERE PostHistoryTypeId = 10.
  • Why: Query text patterns repeatedly use JOIN PostHistory PH ON P.Id = PH.PostId AND PH.PostHistoryTypeId = 10, often also returning PH.CreationDate and PH.Comment.
  • Evidence: Overlap signals repeatedly point to missing access paths on PostHistory(PostId), PostHistory(PostId, CreationDate), and PostHistoryTypeId-based lookups on a table with 847,593 rows and a clustered-only design.
  • Benefit: Reduces full clustered scans of an 820 MB table for close-event workloads; improves joins, date retrieval, and close reason retrieval with much lower storage than a broad unfiltered index.
  • Risk: Low in this sample because write activity is zero; medium operationally due to index build cost.
  • Expected beneficiaries:
    • Query Hash FE52206CED094538: JOIN PostHistory PH ON P.Id = PH.PostId AND PH.PostHistoryTypeId = 10
    • Query Hash 2FB54B903DE9F146 and other post-ranking queries that pull recent post metadata and correlated activity
    • Queries using close-history or moderation timelines from PostHistory
  • Confidence: High.

2) Modify the existing dbo.Votes post-based index to cover vote-type aggregations

  • Recommendation: Rebuild IX_Votes_Bounty8_PostId with the same key (PostId) but include (VoteTypeId, BountyAmount).
  • Why: The overlap signal shows a perfect key match for a candidate index on [PostId] including [VoteTypeId] with redundancy score 100. Instead of adding another near-duplicate index, extend the existing one.
  • Evidence: Slow statements repeatedly aggregate post votes using expressions like SUM(CASE WHEN v.VoteTypeId = 2 THEN 1 ELSE 0 END) and SUM(CASE WHEN v.VoteTypeId = 3 THEN 1 ELSE 0 END).
  • Benefit: Better support for post-level vote counts, less clustered scanning on Votes, and consolidation into the existing index footprint.
  • Risk: Low. The table has 926,084 rows, but observed write penalty is zero and the existing index already exists.
  • Expected beneficiaries:
    • Query Hash 558D53CC7558B81F: aggregates votes per PostId for up/down totals
    • Query Hash EE4BAB640222BBF0: repeated correlated counts on Votes by PostId and VoteTypeId
    • Query Hash 6211E6BFDACEAE1E, 779F0850AFC28347, CE7337517B419CA4: all show high logical reads around post vote summaries
  • Confidence: High.

3) Re-trust all untrusted foreign keys

  • Recommendation: Validate and re-trust all 18 untrusted foreign keys using ALTER TABLE ... WITH CHECK CHECK CONSTRAINT ....
  • Why: Untrusted constraints block important optimizer transformations such as join elimination and can degrade cardinality and plan quality across a schema dominated by joins.
  • Evidence: Untrusted FKs exist on Badges, Comments, PostHistory, PostLinks, Posts, Tags, and Votes.
  • Benefit: Better plan quality without adding new indexes everywhere; especially useful in queries that join dimensions/reference tables such as VoteTypes, PostTypes, and self-referencing Posts.
  • Risk: Medium because validation scans can be expensive on larger tables and will fail if orphaned rows exist.
  • Validation note: Execute during a maintenance window. If any statement fails, data cleanup is required before the constraint can become trusted.
  • Confidence: High.

4) Apply focused query rewrites before adding broader indexes on Posts, Comments, or Badges

  • Recommendation: Prefer code changes over more wide indexes for the current top queries.
  • Why: Many expensive statements are dominated by:
    • large CTE stacks,
    • row multiplication from joining Posts to Votes, Comments, and Badges before aggregation,
    • correlated subqueries for counts,
    • window functions over large intermediate sets,
    • non-sargable tag matching using p.Tags LIKE '%' + t.TagName + '%'.
  • Code changes with highest value:
    • Pre-aggregate Votes by PostId once, then join the small aggregate result to Posts.
    • Pre-aggregate Comments by PostId once, instead of repeated correlated SELECT COUNT(*) subqueries.
    • Use vote type IDs directly where known instead of joining VoteTypes only to test IDs 2 and 3.
    • Replace LIKE '%' + t.TagName + '%' with normalized tag mapping, parsed tags table, or full-text/search-specific design; a regular b-tree index cannot rescue leading-wildcard searches.
  • Examples from workload:
    • Query Hash 872C7773968C6AAF: tag matching via LIKE '%'+TagName+'%' is the major problem, not a missing b-tree.
    • Query Hash EE4BAB640222BBF0 and 6211E6BFDACEAE1E: repeated correlated vote/comment counts create multi-million read patterns.
    • Query Hash 57CAE2375FFE5085, B56CC620F24BC24F, 83371E96D4DD244A: joins and COUNT(DISTINCT) over exploded rowsets likely dominate cost.
  • Confidence: High.

5) Refresh statistics after structural changes

  • Recommendation: Update statistics on Posts, Votes, PostHistory, Comments, and Badges after index changes and FK re-validation.
  • Why: Query patterns are aggregation-heavy; better histograms help memory grants and join order choices. The workload shows very large grants and, in one case, spills.
  • Evidence: Query Hash 558D53CC7558B81F had Max Spills = 26,525 and a 165,768 KB grant.
  • Risk: Low to medium depending on scan depth; acceptable in maintenance windows.
  • Confidence: Medium-High.

Workload evidence

Query hash Observed pattern Relevant recommendation
558D53CC7558B81F LEFT JOIN (SELECT PostId, SUM(CASE WHEN vt.Id = 2 ...), SUM(CASE WHEN vt.Id = 3 ... ) FROM Votes ... GROUP BY PostId) Modify IX_Votes_Bounty8_PostId to include VoteTypeId; also rewrite to avoid unnecessary VoteTypes join if IDs are stable.
FE52206CED094538 LEFT JOIN Votes V ON U.Id = V.UserId and JOIN PostHistory PH ON P.Id = PH.PostId AND PH.PostHistoryTypeId = 10 Filtered PostHistory close-event index; FK trust restoration.
EE4BAB640222BBF0 Repeated correlated subqueries against Comments and Votes by PostId Votes index modification plus query rewrite to pre-aggregate once.
6211E6BFDACEAE1E Correlated vote/comment counts per recent post with 5.5M logical reads Votes index change; code rewrite more important than adding more broad indexes.
872C7773968C6AAF Posts p JOIN Tags t ON p.Tags LIKE '%' + t.TagName + '%' No conventional index recommendation; redesign tag search path.

The top Query Store durations are dominated by analytical query shape and join explosion. Indexing helps on Votes and PostHistory, but code patterns remain the main source of extreme runtime.

Query Store forced-plan review

No forced plan failures were detected.

  • Affected query_id / plan_id list: none reported.
  • Failure classes: none reported.
  • Immediate action: none required.
  • Follow-up: continue monitoring Query Store after index and constraint changes to confirm plan stabilization and lower logical reads.

Automatic tuning review

Option Desired Actual Assessment
FORCE_LAST_GOOD_PLAN ON ON Correctly configured. No action recommended.

Scripts

Recommendation 1: Create filtered index to accelerate close-history lookups on dbo.PostHistory

USE [SQLStorm];
GO
CREATE NONCLUSTERED INDEX [IX_PostHistory_PostId_CreationDate_CloseType10]
ON [dbo].[PostHistory] ([PostId] ASC, [CreationDate] ASC)
INCLUDE ([Comment])
WHERE [PostHistoryTypeId] = 10
WITH (
    ONLINE = ON,
    DATA_COMPRESSION = PAGE,
    SORT_IN_TEMPDB = ON
);
GO

Recommendation 2: Rebuild the existing dbo.Votes PostId index to include VoteTypeId and improve post vote aggregations

USE [SQLStorm];
GO
CREATE NONCLUSTERED INDEX [IX_Votes_Bounty8_PostId]
ON [dbo].[Votes] ([PostId] ASC)
INCLUDE ([VoteTypeId], [BountyAmount])
WITH (
    DROP_EXISTING = ON,
    ONLINE = ON,
    DATA_COMPRESSION = PAGE,
    SORT_IN_TEMPDB = ON
);
GO

Recommendation 3: Re-trust all currently untrusted foreign keys so the optimizer can use them fully

USE [SQLStorm];
GO
ALTER TABLE [dbo].[Badges]      WITH CHECK CHECK CONSTRAINT [FK__Badges__UserId__6477ECF3];
ALTER TABLE [dbo].[Comments]    WITH CHECK CHECK CONSTRAINT [FK__Comments__PostId__4CA06362];
ALTER TABLE [dbo].[Comments]    WITH CHECK CHECK CONSTRAINT [FK__Comments__UserId__4D94879B];
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__PostH__5070F446];
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__PostI__5165187F];
ALTER TABLE [dbo].[PostHistory] WITH CHECK CHECK CONSTRAINT [FK__PostHisto__UserI__52593CB8];
ALTER TABLE [dbo].[PostLinks]   WITH CHECK CHECK CONSTRAINT [FK__PostLinks__LinkT__571DF1D5];
ALTER TABLE [dbo].[PostLinks]   WITH CHECK CHECK CONSTRAINT [FK__PostLinks__PostI__5535A963];
ALTER TABLE [dbo].[PostLinks]   WITH CHECK CHECK CONSTRAINT [FK__PostLinks__Relat__5629CD9C];
ALTER TABLE [dbo].[Posts]       WITH CHECK CHECK CONSTRAINT [FK__Posts__AcceptedA__48CFD27E];
ALTER TABLE [dbo].[Posts]       WITH CHECK CHECK CONSTRAINT [FK__Posts__LastEdito__47DBAE45];
ALTER TABLE [dbo].[Posts]       WITH CHECK CHECK CONSTRAINT [FK__Posts__OwnerUser__46E78A0C];
ALTER TABLE [dbo].[Posts]       WITH CHECK CHECK CONSTRAINT [FK__Posts__ParentId__49C3F6B7];
ALTER TABLE [dbo].[Posts]       WITH CHECK CHECK CONSTRAINT [FK__Posts__PostTypeI__45F365D3];
ALTER TABLE [dbo].[Tags]        WITH CHECK CHECK CONSTRAINT [FK__Tags__ExcerptPos__59FA5E80];
ALTER TABLE [dbo].[Tags]        WITH CHECK CHECK CONSTRAINT [FK__Tags__WikiPostId__5AEE82B9];
ALTER TABLE [dbo].[Votes]       WITH CHECK CHECK CONSTRAINT [FK__Votes__UserId__5EBF139D];
ALTER TABLE [dbo].[Votes]       WITH CHECK CHECK CONSTRAINT [FK__Votes__VoteTypeI__5DCAEF64];
GO

Recommendation 4: Refresh statistics on the primary workload tables after index and constraint changes

USE [SQLStorm];
GO
UPDATE STATISTICS [dbo].[Posts]       WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Votes]       WITH FULLSCAN;
UPDATE STATISTICS [dbo].[PostHistory] WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Comments]    WITH FULLSCAN;
UPDATE STATISTICS [dbo].[Badges]      WITH FULLSCAN;
GO