{"id":2251,"date":"2018-04-10T16:30:00","date_gmt":"2018-04-10T14:30:00","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=2251"},"modified":"2018-03-18T15:25:03","modified_gmt":"2018-03-18T13:25:03","slug":"memory-grant-feedback-and-data-skew","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2018\/04\/10\/memory-grant-feedback-and-data-skew\/","title":{"rendered":"Memory Grant Feedback and data skew"},"content":{"rendered":"<p>The new adaptive query processing features in SQL Server 2017 are useful for fixing performance problems that were previously very hard to fix. They\u2019re not perfect though, and one of the problems with memory grant feedback in particular is that it\u2019s sensitive to data skew.<\/p>\n<p>Before I get into why, let\u2019s look at what adaptive memory grant does in the first place.<\/p>\n<p>Queries request memory for operations like sorts, hash joins, hash aggregates and a few other operators. This is not TempDB space (ideally), it\u2019s just memory. The amount requested is based on the optimiser\u2019s guesses as to the size of the data that will be hashed\/sorted, and that\u2019s based off statistics and parameter values. Hence, there\u2019s a chance for the guess to be wrong, and when it is, we get things like this:<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/PlanWithSpill.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; display: inline;\" title=\"PlanWithSpill\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/PlanWithSpill_thumb.png\" alt=\"PlanWithSpill\" width=\"484\" height=\"121\" border=\"0\" \/><\/a><\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/SpillDetails.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; display: inline;\" title=\"SpillDetails\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/SpillDetails_thumb.png\" alt=\"SpillDetails\" width=\"168\" height=\"364\" border=\"0\" \/><\/a><\/p>\n<p>When spill happen, the intermediate resultsets (or parts of them) do get written to TempDB. And read back. And potentially written and read back again, and maybe a few more times. This can be horribly slow.<\/p>\n<p>Of course, there\u2019s a chance that the estimate will be wrong in the other direction. Too large. It\u2019s not as obviously bad, but it can limit the throughput of the system. Instead of the query running really slowly, it may have to wait before it runs at all, waiting for the memory to be granted. (RESOURCE_SEMAPHORE).<\/p>\n<p>These were really hard problems to fix. There isn\u2019t a query hint to request more or less memory than the estimates would allocate (though you can specify, as a percentage of the resource pool, the max and min memory to be allocated), so fixes had to be creative, typically tricking the optimiser into thinking there were more or fewer rows than there really were, or that the rows were wider (there are some lovely tricks that can be done with CROSS APPLY for example)<\/p>\n<p>Adaptive memory grants don\u2019t do anything to correct the optimiser\u2019s mis-estimates. What they do, is allow the query processor to learn from the mistakes. If a query\u2019s memory grant is significantly over or under what is needed, then a note is made of that, somewhere in memory, and the next time the query runs, the memory grant is adjusted to a value based on what the previous execution needed.<\/p>\n<p>So, if we run the example from above a second time, making absolutely no changes in the process, the spills are gone.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/SpillGone.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; display: inline;\" title=\"SpillGone\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/SpillGone_thumb.png\" alt=\"SpillGone\" width=\"484\" height=\"151\" border=\"0\" \/><\/a><\/p>\n<p>This is great, unless you have a particular pattern in your workload, where one query will sometimes have a small number of rows flowing through it, and sometimes a large number. This is not a problem specific to Memory Grant Feedback. It\u2019s been around for a long time, we call it bad parameter sniffing in many cases.<\/p>\n<p>So let\u2019s try a test of running the same query multiple times, alternating between parameter values that return small row counts and parameter values that return large row counts. The plan is the same in all cases, it\u2019s a reused cached plan, and it\u2019s one that\u2019s not bad for the larger row counts (hash join, hash aggregate), so we don\u2019t have the typical bad parameter sniffing problem, but the memory grant will oscillate, being based upon the previous query\u2019s execution. I\u2019m going to execute the stored procedure 200 times.<\/p>\n<p>And I should mention that this is an extreme case. I specifically constructed a scenario where the memory grant required by one execution would be completely inappropriate for the next one. This is not (I hope) something that would happen in the real world.<\/p>\n<p>I monitored what was happening with Extended Events, with the memory_grant_updated_by_feedback and memory_grant_feedback_loop_disabled events.<\/p>\n<p>The results were kinda as expected.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image_thumb.png\" alt=\"image\" width=\"484\" height=\"86\" border=\"0\" \/><\/a><\/p>\n<p>And then something interesting happened. I didn\u2019t clear the cache or anything, this was as the procedures executed in a loop.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image-1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image_thumb-1.png\" alt=\"image\" width=\"484\" height=\"62\" border=\"0\" \/><\/a><\/p>\n<p>After 8 executions, each with a memory grant update, both the execution count and the count of updates to the grant reset to 1.<\/p>\n<p>This happened again 8 executions later<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image-2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image_thumb-2.png\" alt=\"image\" width=\"484\" height=\"64\" border=\"0\" \/><\/a><\/p>\n<p>And again 8 executions later<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image-3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image_thumb-3.png\" alt=\"image\" width=\"484\" height=\"52\" border=\"0\" \/><\/a><\/p>\n<p>Then, finally, after 32 executions, the update is disabled.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image-4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-top: 0px; padding-left: 0px; margin: 0px; display: inline; padding-right: 0px; border-width: 0px;\" title=\"image\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/03\/image_thumb-4.png\" alt=\"image\" width=\"244\" height=\"75\" border=\"0\" \/><\/a><\/p>\n<p>The procedure then went on to execute a further 168 times, with the same memory grant each time, equal to the last updated value.<\/p>\n<p>So what can we conclude from this?<\/p>\n<p>Firstly, there seems to be a re-evaluation of the memory grant feedback process every 8 modifications, deciding whether to continue adjusting. Second, it will stop adjusting memory grants at some point, though the conditions aren\u2019t documented and I can\u2019t tell from the test I ran what the conditions are. Since they\u2019re not documented, they will probably change in future CUs\/versions without notice.<\/p>\n<p>Once the feedback cycle stops, the last memory grant value is what will be used for that query until its plan is removed from cache, at which point the adjustment cycle starts over from scratch.<\/p>\n<p>If you\u2019re working with a system that has this kind of query, with wide differences in optimal memory grant, I would suggest not relying on memory grant feedback, and changing the code so that the grant needed is more constant. This may require splitting procedures up, optimise hints or other fixes for bad parameter sniffing.<\/p>\n<p>I suggest that because the feedback works great for \u2018dialling in\u2019 a good value for needed memory grant, but not for cases where the optimal grant is constantly changing. The 200 executions above took 4 minutes total without memory grant feedback, but 12 minutes with memory grant feedback.<\/p>\n<p>It\u2019s a great solution when the original estimate doesn\u2019t match what the query needs, but it\u2019s sub-optimal for queries with constantly changing memory needs. Procedures with widely changing memory needs should be fixed with other methods, including but not limited to multiple procedures, dynamic SQL, plan forcing, or other query hints.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The new adaptive query processing features in SQL Server 2017 are useful for fixing performance problems that were previously very hard to fix. They\u2019re not perfect though, and one of the problems with memory grant feedback in particular is that&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2018\/04\/10\/memory-grant-feedback-and-data-skew\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"New blog post: Memory Grant Feedback and data skew","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[25,15],"tags":[],"class_list":["post-2251","post","type-post","status-publish","format-standard","hentry","category-performance","category-sql-server"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-Aj","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2251","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=2251"}],"version-history":[{"count":4,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2251\/revisions"}],"predecessor-version":[{"id":2258,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2251\/revisions\/2258"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=2251"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=2251"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=2251"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}