33
44using System ;
55using System . Collections . Generic ;
6- using System . Data . Common ;
76using System . Data . Entity ;
87using System . IO ;
98using System . Linq ;
@@ -26,6 +25,7 @@ public class PackageService : CorePackageService, IPackageService
2625 private readonly ITelemetryService _telemetryService ;
2726 private readonly ISecurityPolicyService _securityPolicyService ;
2827 private readonly IEntitiesContext _entitiesContext ;
28+ private readonly IContentObjectService _contentObjectService ;
2929 private const int packagesDisplayed = 5 ;
3030
3131 public PackageService (
@@ -35,13 +35,15 @@ public PackageService(
3535 IAuditingService auditingService ,
3636 ITelemetryService telemetryService ,
3737 ISecurityPolicyService securityPolicyService ,
38- IEntitiesContext entitiesContext )
38+ IEntitiesContext entitiesContext ,
39+ IContentObjectService contentObjectService )
3940 : base ( packageRepository , packageRegistrationRepository , certificateRepository )
4041 {
4142 _auditingService = auditingService ?? throw new ArgumentNullException ( nameof ( auditingService ) ) ;
4243 _telemetryService = telemetryService ?? throw new ArgumentNullException ( nameof ( telemetryService ) ) ;
4344 _securityPolicyService = securityPolicyService ?? throw new ArgumentNullException ( nameof ( securityPolicyService ) ) ;
4445 _entitiesContext = entitiesContext ?? throw new ArgumentNullException ( nameof ( entitiesContext ) ) ;
46+ _contentObjectService = contentObjectService ?? throw new ArgumentNullException ( nameof ( contentObjectService ) ) ;
4547 }
4648
4749 /// <summary>
@@ -155,8 +157,34 @@ public PackageDependents GetPackageDependents(string id)
155157 }
156158
157159 PackageDependents result = new PackageDependents ( ) ;
158-
159- using ( _entitiesContext . WithQueryHint ( "RECOMPILE" ) )
160+
161+ // We use OPTIMIZE FOR UNKNOWN by default here because there are distinct 2-3 query plans that may be
162+ // selected via SQL Server parameter sniffing. Because SQL Server caches query plans, this means that the
163+ // first parameter plus query combination that SQL sees defines which query plan is selected and cached for
164+ // all subsequent parameter values of the same query. This could result in a non-optimal query plan getting
165+ // cached depending on what package ID is viewed first. Using OPTIMIZE FOR UNKNOWN causes a predictable
166+ // query plan to be cached.
167+ //
168+ // For example, the query plan for Newtonsoft.Json is very good for that specific parameter value since
169+ // there are so many package dependents but the same query plan takes a very long time for packages with few
170+ // or no dependents. The query plan for "UNKNOWN" (that is a package ID with unknown SQL Server statistic)
171+ // behaves somewhat poorly for Newtonsoft.Json (2-5 seconds) but very well for the vast majority of
172+ // packages. Because we have in-memory caching above this layer, OPTIMIZE FOR UNKNOWN is acceptable other
173+ // unconfigured cases similar to Newtonsoft.Json because the extra cost of the non-optimal query plan is
174+ // amortized over many, many page views. For the long tail packages, in-memory caching is less effective
175+ // (low page views) so an optimal query should be selected for this category.
176+ //
177+ // For the cases where RECOMPILE is known to perform the best, the package ID can be added to the query hint
178+ // configuration JSON file from the content object service. This should only be done when the following
179+ // things are true:
180+ //
181+ // 1. The overhead of SQL Server recompile is worth it. We have seen the overhead to be 5-50ms.
182+ // 2. SQL Server has up to date statistics which will lead to the proper query plan being selected.
183+ // 3. SQL Server actually picks the proper query plan. We have observed cases where this does not happen
184+ // even with up-to-date statistics.
185+ //
186+ var useRecompile = _contentObjectService . QueryHintConfiguration . ShouldUseRecompileForPackageDependents ( id ) ;
187+ using ( _entitiesContext . WithQueryHint ( useRecompile ? "RECOMPILE" : "OPTIMIZE FOR UNKNOWN" ) )
160188 {
161189 result . TopPackages = GetListOfDependents ( id ) ;
162190 result . TotalPackageCount = GetDependentCount ( id ) ;
0 commit comments