%3CLINGO-SUB%20id%3D%22lingo-sub-1520506%22%20slang%3D%22en-US%22%3EDistributed%20key%20considerations%20for%20data%20movement%20on%20SQL%20DW%20performance%3C%2FLINGO-SUB%3E%3CLINGO-BODY%20id%3D%22lingo-body-1520506%22%20slang%3D%22en-US%22%3E%3CP%3EI%20was%20working%20on%20a%20few%20performance%20cases%20last%20week.%20So%20I%20thought%20it%20would%20be%20a%20good%20idea%20to%20follow%20my%20plan%20that%20I%20started%20in%202017%20(%20yes%2C%20I%20know%20a%20long%20time)%20and%20discuss%20more%20about%26nbsp%3B%20SQL%20DW%20performance.%3C%2FP%3E%0A%3CP%3EFrom%20the%20time%20tunnel%20my%20post%20from%202017%3A%26nbsp%3B%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-gb%2Farchive%2Fblogs%2Fdataplatform%2Fsql-azure-dw-what-is-it-how-it-works%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noopener%20noreferrer%20noopener%20noreferrer%22%3Ehttps%3A%2F%2Fdocs.microsoft.com%2Fen-gb%2Farchive%2Fblogs%2Fdataplatform%2Fsql-azure-dw-what-is-it-how-it-works.%3C%2FA%3E%3C%2FP%3E%0A%3CP%3EI%20was%20working%20on%20those%20cases%20with%20my%20colleague%20Frederico%20Guimaraes%20which%20is%20a%20person%20that%20has%20a%20lot%20of%20experience%20in%20this%20matter.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EI%20will%20copy%20and%20paste%20a%20few%20concepts%26nbsp%3B%20from%20my%20previous%20post%20to%20give%20us%20some%20ground%3A%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3EMPP%20means...%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3EIt%20is%20%E2%80%9Cdivide%20to%20conquer%E2%80%9D.%26nbsp%3B%20Azure%20DW%20relays%20in%20nodes%20and%20CPUs%2C%20instead%20of%20in%20only%20CPUs%20to%20process%20a%20task.%20Our%20classical%20SQL%20divides%20a%20task%20in%20different%20CPUs%2C%20which%20is%20parallel%20processing.%20Azure%20DW%20process%20a%20task%20in%20CPUs%20running%20in%20different%20nodes%20(computers).%3C%2FP%3E%0A%3CP%3EIn%20order%20to%20achieve%20this%20distributed%20architecture%20DW%20has%3A%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3EControl%20node%3A%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3EThe%20Control%20node%20manages%20and%20optimizes%20queries.%20It%20is%20the%20front%20end%20that%20interacts%20with%20all%20applications%20and%20connections.%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3ECompute%20nodes%3A%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3EThe%20Compute%20nodes%20serve%20as%20the%20power%20behind%20SQL%20Data%20Warehouse.%20They%20are%20SQL%20Databases%20that%20store%20your%20data%20and%20process%20your%20query%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3EStorage%3A%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3EYour%20data%20is%20stored%20in%20Azure%20Blob%20storage.%20When%20Compute%20nodes%20interact%20with%20your%20data%2C%20they%20write%20and%20read%20directly%20to%20and%20from%20blob%20storage%3C%2FP%3E%0A%3CP%3E(%3CA%20href%3D%22https%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fsql-data-warehouse%2Fsql-data-warehouse-overview-what-is%22%20target%3D%22_blank%22%20rel%3D%22noopener%20noopener%20noreferrer%20noopener%20noreferrer%22%20data-linktype%3D%22external%22%3Ehttps%3A%2F%2Fdocs.microsoft.com%2Fen-us%2Fazure%2Fsql-data-warehouse%2Fsql-data-warehouse-overview-what-is%3C%2FA%3E)%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3EDistribution%20columns%3A%3C%2FSTRONG%3E%3C%2FP%3E%0A%3CP%3EBehind%20the%20scenes%2C%20SQL%20Data%20Warehouse%20divides%20your%20data%20into%2060%20databases.%20Each%20individual%20database%20is%20referred%20to%20as%20a%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3E%3CSTRONG%3Edistribution%3C%2FSTRONG%3E.%20When%20data%20is%20loaded%20into%20each%20table%2C%20SQL%20Data%20Warehouse%20has%20to%20know%20how%20to%20divide%20your%20data%20across%20these%2060%20distributions.%3C%2FP%3E%0A%3CP%3ESo%20the%20column%20chooses%20as%20the%20distribution%20key%20will%20be%20used%20to%20distribute%20the%20data%20across%20nodes.%3C%2FP%3E%0A%3CP%3EWe%20have%20two%20types%20of%20distribution%3A%3C%2FP%3E%0A%3COL%3E%0A%3CLI%3E%3CSTRONG%3ERound%20robin%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3Ewhich%20distributes%20data%20evenly%20but%20randomly.%20As%20it%20sounds%20round-robin%20will%20work%20distributing%20the%20data%20in%20round-robin%20fashion.%3C%2FLI%3E%0A%3CLI%3E%3CSTRONG%3EHash%20Distributed%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3Ewhich%20distributes%20data%20based%20on%20hashing%20values%20from%20a%20single%20column.%20Hash%20distributed%20tables%20are%20tables%20that%20are%20divided%20between%20the%20distributed%20databases%20using%20a%20hashing%20algorithm%20on%20a%20single%20column%20that%20you%20select.%26nbsp%3B%3C%2FLI%3E%0A%3C%2FOL%3E%0A%3CP%3EOk%20that%20is%20enough...%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EOnce%20you%20create%20your%20distributed%20table%20and%20defined%20the%20distributed%20key%2C%20keep%20in%20mind%20the%20key%20holds%20the%20secret%20to%20avoid%20data%20movement%20on%20large%20tables.%20I%20mean%20suppose%20you%20will%20join%20large%20tables%2C%20it%20sounds%20a%20good%20idea%20to%20join%20the%20distributed%20tables%20on%20their%20distribution%20keys.%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EFor%20example%3A%26nbsp%3B%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3E%2F******%20Object%3A%20%20Table%20%5Bdbo%5D.%5BMedallion%5D%20%20%20%20Script%20Date%3A%2014%2F07%2F2020%2010%3A13%3A05%20******%2F%0ASET%20ANSI_NULLS%20ON%0AGO%0A%0ASET%20QUOTED_IDENTIFIER%20ON%0AGO%0A%0ACREATE%20TABLE%20%5Bdbo%5D.%5BMedallion%5D%0A(%0A%20%5BMedallionID%5D%20%5Bint%5D%20NOT%20NULL%2C%0A%20%5BMedallionBKey%5D%20%5Bvarchar%5D(50)%20NOT%20NULL%2C%0A%20%5BMedallionCode%5D%20%5Bvarchar%5D(50)%20NULL%0A)%0AWITH%0A(%0A%20DISTRIBUTION%20%3D%20HASH%20(%20%5BMedallionID%5D%20)%2C%0A%20CLUSTERED%20COLUMNSTORE%20INDEX%0A)%0AGO%0A%2F******%20Object%3A%20%20Table%20%5Bdbo%5D.%5BMedallion%5D%20%20%20%20Script%20Date%3A%2014%2F07%2F2020%2010%3A13%3A05%20******%2F%0ASET%20ANSI_NULLS%20ON%0AGO%0A%0ASET%20QUOTED_IDENTIFIER%20ON%0AGO%0A%0ACREATE%20TABLE%20%5Bdbo%5D.%5BMedallion%5D%0A(%0A%20%5BMedallionID%5D%20%5Bint%5D%20NOT%20NULL%2C%0A%20%5BMedallionBKey%5D%20%5Bvarchar%5D(50)%20NOT%20NULL%2C%0A%20%5BMedallionCode%5D%20%5Bvarchar%5D(50)%20NULL%0A)%0AWITH%0A(%0A%20DISTRIBUTION%20%3D%20HASH%20(%20%5BMedallionID%5D%20)%2C%0A%20CLUSTERED%20COLUMNSTORE%20INDEX%0A)%0AGO%0A%0A%0A%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EiT%20is%20pretty%20much%20the%20same%20table%20with%20the%20same%20data%20and%20the%20same%20distribution%20key.%26nbsp%3B%3C%2FP%3E%0A%3CP%3EIf%20my%20query%20does%20not%20include%20the%20key%20for%20the%20join%3A%26nbsp%3B%20MPP%20will%20estimate%20in%20a%20different%20way%20even%20the%20results%20by%20including%20the%20key%20would%20be%20the%20same.%20Let%20me%20explain%20by%20showing%3A%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3ENote%20I%20included%20%3CSTRONG%3Eexplain%20command%3C%2FSTRONG%3E%20to%20get%20the%20query%20plan%20%3A%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3EEXPLAIN%0ASELECT%20m.%5BMedallionID%5D%0A%20%20%20%20%20%20%2CH.%5BMedallionBKey%5D%0A%20%20%20%20%20%20%2Cm.%5BMedallionCode%5D%0A%20%20FROM%20%5Bdbo%5D.%5BMedallion%5D%20m%20%0A%20%20INNER%20JOIN%20Medallion_hash%20h%0A%20%20%20ON%20%20m.MedallionID%20%3D%20h.MedallionID%20%0A%20%20%20AND%20%20m.MedallionBKey%3Dh.MedallionBKey%0A%0A%0A%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3E%3CU%3E%3CSTRONG%3EResult%3A%3C%2FSTRONG%3E%3C%2FU%3E%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3E%0A%3CDSQL_QUERY%20number_nodes%3D%221%22%20number_distributions%3D%2260%22%20number_distributions_per_node%3D%2260%22%3E%0A%3CSQL%3ESELECT%20m.%5BMedallionID%5D%0A%2CH.%5BMedallionBKey%5D%0A%2Cm.%5BMedallionCode%5D%0AFROM%20%5Bdbo%5D.%5BMedallion%5D%20m%20%0AINNER%20JOIN%20Medallion_hash%20h%20on%20m.MedallionID%20%3D%20h.MedallionID%20and%20m.MedallionBKey%3Dh.MedallionBKey%3C%2FSQL%3E%0A%3CDSQL_OPERATIONS%20total_cost%3D%220%22%20total_number_operations%3D%221%22%3E%0A%3CDSQL_OPERATION%20operation_type%3D%22RETURN%22%3E%0A%3CLOCATION%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%3CSELECT%3ESELECT%20%5BT1_1%5D.%5BMedallionID%5D%20AS%20%5BMedallionID%5D%2C%20%5BT1_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%2C%20%5BT1_1%5D.%5BMedallionCode%5D%20AS%20%5BMedallionCode%5D%20FROM%20(SELECT%20%5BT2_2%5D.%5BMedallionID%5D%20AS%20%5BMedallionID%5D%2C%20%5BT2_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%2C%20%5BT2_2%5D.%5BMedallionCode%5D%20AS%20%5BMedallionCode%5D%20FROM%20%5BSQLDW%5D.%5Bdbo%5D.%5BMedallion_hash%5D%20AS%20T2_1%20INNER%20JOIN%0A%5BSQLDW%5D.%5Bdbo%5D.%5BMedallion%5D%20AS%20T2_2%0AON%20((%5BT2_2%5D.%5BMedallionID%5D%20%3D%20%5BT2_1%5D.%5BMedallionID%5D)%20AND%20(%5BT2_2%5D.%5BMedallionBKey%5D%20%3D%20%5BT2_1%5D.%5BMedallionBKey%5D)))%20AS%20T1_1%0AOPTION%20(MAXDOP%202)%3C%2FSELECT%3E%0A%3C%2FDSQL_OPERATION%3E%0A%3C%2FDSQL_OPERATIONS%3E%0A%3C%2FDSQL_QUERY%3E%0A%0A%0A%0A%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3EI%20changed%20the%20query%20without%20including%20my%20distribution%26nbsp%3B%20key%20which%20is%20MedeallionID%20and%20filtered%20by%20MedallionBkey%20only%2C%20now%20I%20have%20data%20movement%3A%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3EEXPLAIN%0ASELECT%20m.%5BMedallionID%5D%0A%20%20%20%20%20%20%2CH.%5BMedallionBKey%5D%0A%20%20%20%20%20%20%2Cm.%5BMedallionCode%5D%0A%20%20FROM%20%5Bdbo%5D.%5BMedallion%5D%20m%20%0A%20%20INNER%20JOIN%20Medallion_hash%20h%0A%20%20%20ON%20%20%20m.MedallionBKey%3Dh.MedallionBKey%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3EResult%20is%3A%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3E%0A%3CDSQL_QUERY%20number_nodes%3D%221%22%20number_distributions%3D%2260%22%20number_distributions_per_node%3D%2260%22%3E%0A%20%20%3CSQL%3ESELECT%20m.%5BMedallionID%5D%0A%20%20%20%20%20%20%2CH.%5BMedallionBKey%5D%0A%20%20%20%20%20%20%2Cm.%5BMedallionCode%5D%0A%20%20FROM%20%5Bdbo%5D.%5BMedallion%5D%20m%20%0A%20%20INNER%20JOIN%20Medallion_hash%20h%20on%20%20%20m.MedallionBKey%3Dh.MedallionBKey%3C%2FSQL%3E%0A%20%20%3CDSQL_OPERATIONS%20total_cost%3D%226.451296%22%20total_number_operations%3D%229%22%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22RND_ID%22%3E%0A%20%20%20%20%20%20%3CIDENTIFIER%3ETEMP_ID_17%3C%2FIDENTIFIER%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22ON%22%3E%0A%20%20%20%20%20%20%3CLOCATION%20permanent%3D%22false%22%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%20%20%20%20%20%20%3CSQL_OPERATIONS%3E%0A%20%20%20%20%20%20%20%20%3CSQL_OPERATION%20type%3D%22statement%22%3ECREATE%20TABLE%20%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_17%5D%20(%5BMedallionBKey%5D%20VARCHAR(50)%20COLLATE%20SQL_Latin1_General_CP1_CI_AS%20NOT%20NULL%20)%20WITH(DISTRIBUTED_MOVE_FILE%3D'')%3B%3C%2FSQL_OPERATION%3E%0A%20%20%20%20%20%20%3C%2FSQL_OPERATIONS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22SHUFFLE_MOVE%22%3E%0A%20%20%20%20%20%20%3COPERATION_COST%20cost%3D%221.749504%22%20accumulative_cost%3D%221.749504%22%20average_rowsize%3D%2232%22%20output_rows%3D%2213668%22%20groupnumber%3D%224%22%3E%3C%2FOPERATION_COST%3E%0A%20%20%20%20%20%20%3CSOURCE_STATEMENT%3ESELECT%20%5BT1_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%20FROM%20%5BSQLDW%5D.%5Bdbo%5D.%5BMedallion_hash%5D%20AS%20T1_1%0AOPTION%20(MAXDOP%202%2C%20MIN_GRANT_PERCENT%20%3D%20%5BMIN_GRANT%5D%2C%20DISTRIBUTED_MOVE(N''))%3C%2FSOURCE_STATEMENT%3E%0A%20%20%20%20%20%20%3CDESTINATION_TABLE%3E%5BTEMP_ID_17%5D%3C%2FDESTINATION_TABLE%3E%0A%20%20%20%20%20%20%3CSHUFFLE_COLUMNS%3EMedallionBKey%3B%3C%2FSHUFFLE_COLUMNS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22RND_ID%22%3E%0A%20%20%20%20%20%20%3CIDENTIFIER%3ETEMP_ID_18%3C%2FIDENTIFIER%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22ON%22%3E%0A%20%20%20%20%20%20%3CLOCATION%20permanent%3D%22false%22%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%20%20%20%20%20%20%3CSQL_OPERATIONS%3E%0A%20%20%20%20%20%20%20%20%3CSQL_OPERATION%20type%3D%22statement%22%3ECREATE%20TABLE%20%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_18%5D%20(%5BMedallionID%5D%20INT%20NOT%20NULL%2C%20%5BMedallionBKey%5D%20VARCHAR(50)%20COLLATE%20SQL_Latin1_General_CP1_CI_AS%20NOT%20NULL%2C%20%5BMedallionCode%5D%20VARCHAR(50)%20COLLATE%20SQL_Latin1_General_CP1_CI_AS%20)%20WITH(DISTRIBUTED_MOVE_FILE%3D'')%3B%3C%2FSQL_OPERATION%3E%0A%20%20%20%20%20%20%3C%2FSQL_OPERATIONS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22SHUFFLE_MOVE%22%3E%0A%20%20%20%20%20%20%3COPERATION_COST%20cost%3D%224.701792%22%20accumulative_cost%3D%226.451296%22%20average_rowsize%3D%2286%22%20output_rows%3D%2213668%22%20groupnumber%3D%223%22%3E%3C%2FOPERATION_COST%3E%0A%20%20%20%20%20%20%3CSOURCE_STATEMENT%3ESELECT%20%5BT1_1%5D.%5BMedallionID%5D%20AS%20%5BMedallionID%5D%2C%20%5BT1_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%2C%20%5BT1_1%5D.%5BMedallionCode%5D%20AS%20%5BMedallionCode%5D%20FROM%20%5BSQLDW%5D.%5Bdbo%5D.%5BMedallion%5D%20AS%20T1_1%0AOPTION%20(MAXDOP%202%2C%20MIN_GRANT_PERCENT%20%3D%20%5BMIN_GRANT%5D%2C%20DISTRIBUTED_MOVE(N''))%3C%2FSOURCE_STATEMENT%3E%0A%20%20%20%20%20%20%3CDESTINATION_TABLE%3E%5BTEMP_ID_18%5D%3C%2FDESTINATION_TABLE%3E%0A%20%20%20%20%20%20%3CSHUFFLE_COLUMNS%3EMedallionBKey%3B%3C%2FSHUFFLE_COLUMNS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22RETURN%22%3E%0A%20%20%20%20%20%20%3CLOCATION%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%20%20%20%20%20%20%3CSELECT%3ESELECT%20%5BT1_1%5D.%5BMedallionID%5D%20AS%20%5BMedallionID%5D%2C%20%5BT1_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%2C%20%5BT1_1%5D.%5BMedallionCode%5D%20AS%20%5BMedallionCode%5D%20FROM%20(SELECT%20%5BT2_2%5D.%5BMedallionID%5D%20AS%20%5BMedallionID%5D%2C%20%5BT2_1%5D.%5BMedallionBKey%5D%20AS%20%5BMedallionBKey%5D%2C%20%5BT2_2%5D.%5BMedallionCode%5D%20AS%20%5BMedallionCode%5D%20FROM%20%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_17%5D%20AS%20T2_1%20INNER%20JOIN%0A%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_18%5D%20AS%20T2_2%0AON%20(%5BT2_1%5D.%5BMedallionBKey%5D%20%3D%20%5BT2_2%5D.%5BMedallionBKey%5D))%20AS%20T1_1%0AOPTION%20(MAXDOP%202%2C%20MIN_GRANT_PERCENT%20%3D%20%5BMIN_GRANT%5D)%3C%2FSELECT%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22ON%22%3E%0A%20%20%20%20%20%20%3CLOCATION%20permanent%3D%22false%22%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%20%20%20%20%20%20%3CSQL_OPERATIONS%3E%0A%20%20%20%20%20%20%20%20%3CSQL_OPERATION%20type%3D%22statement%22%3EDROP%20TABLE%20%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_18%5D%3C%2FSQL_OPERATION%3E%0A%20%20%20%20%20%20%3C%2FSQL_OPERATIONS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%20%20%3CDSQL_OPERATION%20operation_type%3D%22ON%22%3E%0A%20%20%20%20%20%20%3CLOCATION%20permanent%3D%22false%22%20distribution%3D%22AllDistributions%22%3E%3C%2FLOCATION%3E%0A%20%20%20%20%20%20%3CSQL_OPERATIONS%3E%0A%20%20%20%20%20%20%20%20%3CSQL_OPERATION%20type%3D%22statement%22%3EDROP%20TABLE%20%5Bqtabledb%5D.%5Bdbo%5D.%5BTEMP_ID_17%5D%3C%2FSQL_OPERATION%3E%0A%20%20%20%20%20%20%3C%2FSQL_OPERATIONS%3E%0A%20%20%20%20%3C%2FDSQL_OPERATION%3E%0A%20%20%3C%2FDSQL_OPERATIONS%3E%0A%3C%2FDSQL_QUERY%3E%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3ENote%20data%20movement%20is%20happening%20on%20the%20plan%3A%26nbsp%3B%3CSTRONG%3E%3CDSQL_OPERATION%20operation_type%3D%22%26quot%3BSHUFFLE_MOVE%26quot%3B%22%3E%3C%2FDSQL_OPERATION%3E%3C%2FSTRONG%3E.%20Which%20means%20(%20copy%20and%20paste%20again%20from%20my%20previous%20post)%3A%3C%2FP%3E%0A%3CP%3E%3CSTRONG%3ESHUFFLE_MOVE%3C%2FSTRONG%3E%3CSPAN%3E%26nbsp%3B%3C%2FSPAN%3E-%20Redistributes%20a%20distributed%20table.%20The%20redistributed%20table%20has%20a%20different%20distribution%20column%20than%20the%20original%20distributed%20table.%20This%20might%20be%20used%20to%20when%20running%20incompatible%20joins%20or%20incompatible%20aggregations.%3C%2FP%3E%0A%3CP%3ETo%20perform%20this%20operation%2C%20SQL%20DW%20will%20move%20each%20row%20to%20the%20correct%20Compute%20node%20according%20to%20the%20distribution%20column%20of%20the%20destination%20table.%20Rows%20that%20are%20already%20stored%20on%20the%20correct%20Compute%20node%20are%20not%20copied%20during%20this%20operation.%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3ESo%20the%20case%20from%20this%20week%20is%20about%20that.%20There%20was%20a%20data%20movement%20which%20was%20not%20desired%20to%20be%20on%20the%20plan%2C%20for%20that%20we%20took%20some%20actions%3A%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E1)%20Review%20the%20distribution%20keys%20on%20the%20table%3C%2FP%3E%0A%3CP%3E2)%20Review%20the%20stats.%20So%2C%20wrong%20stats%20can%20lead%20to%20MPP%20misestimated%20the%20plan.%26nbsp%3B%3C%2FP%3E%0A%3CP%3EFollow%20a%20simple%20query%20to%20check%20your%20stats%3A%3C%2FP%3E%0A%3CPRE%20class%3D%22lia-code-sample%20language-applescript%22%3E%3CCODE%3ESELECT%20stats_id%2C%20name%20AS%20stats_name%2C%20%0A%20%20%20%20STATS_DATE(object_id%2C%20stats_id)%20AS%20statistics_date%0AFROM%20sys.stats%20s%0A%3C%2FCODE%3E%3C%2FPRE%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3EThat%20is%20it!%20I%20hope%20the%20examples%20provide%20help%20you%20to%20get%20an%20idea%20of%20the%20importance%20while%20defining%20distribution%20keys.%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3ELiliam%20C%20Leme%3C%2FP%3E%0A%3CP%3EUK%20Engineer%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%0A%3CP%3E%26nbsp%3B%3C%2FP%3E%3C%2FLINGO-BODY%3E%3CLINGO-TEASER%20id%3D%22lingo-teaser-1520506%22%20slang%3D%22en-US%22%3E%3CP%3EMPP%20required%20data%20movement.%20Why%3F%3C%2FP%3E%3C%2FLINGO-TEASER%3E%3CLINGO-LABS%20id%3D%22lingo-labs-1520506%22%20slang%3D%22en-US%22%3E%3CLINGO-LABEL%3EInternals%3C%2FLINGO-LABEL%3E%3CLINGO-LABEL%3ESynapse%20SQL%3C%2FLINGO-LABEL%3E%3C%2FLINGO-LABS%3E
Microsoft

I was working on a few performance cases last week. So I thought it would be a good idea to follow my plan that I started in 2017 ( yes, I know a long time) and discuss more about  SQL DW performance.

From the time tunnel my post from 2017: https://docs.microsoft.com/en-gb/archive/blogs/dataplatform/sql-azure-dw-what-is-it-how-it-works.

I was working on those cases with my colleague Frederico Guimaraes which is a person that has a lot of experience in this matter.

 

I will copy and paste a few concepts  from my previous post to give us some ground:

MPP means...

It is “divide to conquer”.  Azure DW relays in nodes and CPUs, instead of in only CPUs to process a task. Our classical SQL divides a task in different CPUs, which is parallel processing. Azure DW process a task in CPUs running in different nodes (computers).

In order to achieve this distributed architecture DW has:

Control node: The Control node manages and optimizes queries. It is the front end that interacts with all applications and connections.

Compute nodes: The Compute nodes serve as the power behind SQL Data Warehouse. They are SQL Databases that store your data and process your query

Storage: Your data is stored in Azure Blob storage. When Compute nodes interact with your data, they write and read directly to and from blob storage

(https://docs.microsoft.com/en-us/azure/sql-data-warehouse/sql-data-warehouse-overview-what-is)

 

Distribution columns:

Behind the scenes, SQL Data Warehouse divides your data into 60 databases. Each individual database is referred to as a distribution. When data is loaded into each table, SQL Data Warehouse has to know how to divide your data across these 60 distributions.

So the column chooses as the distribution key will be used to distribute the data across nodes.

We have two types of distribution:

  1. Round robin which distributes data evenly but randomly. As it sounds round-robin will work distributing the data in round-robin fashion.
  2. Hash Distributed which distributes data based on hashing values from a single column. Hash distributed tables are tables that are divided between the distributed databases using a hashing algorithm on a single column that you select. 

Ok that is enough...

 

Once you create your distributed table and defined the distributed key, keep in mind the key holds the secret to avoid data movement on large tables. I mean suppose you will join large tables, it sounds a good idea to join the distributed tables on their distribution keys. 

 

For example: 

 

/****** Object:  Table [dbo].[Medallion]    Script Date: 14/07/2020 10:13:05 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Medallion]
(
	[MedallionID] [int] NOT NULL,
	[MedallionBKey] [varchar](50) NOT NULL,
	[MedallionCode] [varchar](50) NULL
)
WITH
(
	DISTRIBUTION = HASH ( [MedallionID] ),
	CLUSTERED COLUMNSTORE INDEX
)
GO
/****** Object:  Table [dbo].[Medallion]    Script Date: 14/07/2020 10:13:05 ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[Medallion]
(
	[MedallionID] [int] NOT NULL,
	[MedallionBKey] [varchar](50) NOT NULL,
	[MedallionCode] [varchar](50) NULL
)
WITH
(
	DISTRIBUTION = HASH ( [MedallionID] ),
	CLUSTERED COLUMNSTORE INDEX
)
GO


 

 

iT is pretty much the same table with the same data and the same distribution key. 

If my query does not include the key for the join:  MPP will estimate in a different way even the results by including the key would be the same. Let me explain by showing:

 

Note I included explain command to get the query plan :

 

 

EXPLAIN
SELECT m.[MedallionID]
      ,H.[MedallionBKey]
      ,m.[MedallionCode]
  FROM [dbo].[Medallion] m 
  INNER JOIN Medallion_hash h
   ON  m.MedallionID = h.MedallionID 
   AND  m.MedallionBKey=h.MedallionBKey


 

Result:

 

<?xml version="1.0" encoding="utf-8"?>
<dsql_query number_nodes="1" number_distributions="60" number_distributions_per_node="60">
<sql>SELECT m.[MedallionID]
,H.[MedallionBKey]
,m.[MedallionCode]
FROM [dbo].[Medallion] m 
INNER JOIN Medallion_hash h on m.MedallionID = h.MedallionID and m.MedallionBKey=h.MedallionBKey</sql>
<dsql_operations total_cost="0" total_number_operations="1">
<dsql_operation operation_type="RETURN">
<location distribution="AllDistributions" />
<select>SELECT [T1_1].[MedallionID] AS [MedallionID], [T1_1].[MedallionBKey] AS [MedallionBKey], [T1_1].[MedallionCode] AS [MedallionCode] FROM (SELECT [T2_2].[MedallionID] AS [MedallionID], [T2_1].[MedallionBKey] AS [MedallionBKey], [T2_2].[MedallionCode] AS [MedallionCode] FROM [SQLDW].[dbo].[Medallion_hash] AS T2_1 INNER JOIN
[SQLDW].[dbo].[Medallion] AS T2_2
ON (([T2_2].[MedallionID] = [T2_1].[MedallionID]) AND ([T2_2].[MedallionBKey] = [T2_1].[MedallionBKey]))) AS T1_1
OPTION (MAXDOP 2)</select>
</dsql_operation>
</dsql_operations>
</dsql_query>



 

I changed the query without including my distribution  key which is MedeallionID and filtered by MedallionBkey only, now I have data movement:

 

 

EXPLAIN
SELECT m.[MedallionID]
      ,H.[MedallionBKey]
      ,m.[MedallionCode]
  FROM [dbo].[Medallion] m 
  INNER JOIN Medallion_hash h
   ON   m.MedallionBKey=h.MedallionBKey

 

Result is:

 

 

<?xml version="1.0" encoding="utf-8"?>
<dsql_query number_nodes="1" number_distributions="60" number_distributions_per_node="60">
  <sql>SELECT m.[MedallionID]
      ,H.[MedallionBKey]
      ,m.[MedallionCode]
  FROM [dbo].[Medallion] m 
  INNER JOIN Medallion_hash h on   m.MedallionBKey=h.MedallionBKey</sql>
  <dsql_operations total_cost="6.451296" total_number_operations="9">
    <dsql_operation operation_type="RND_ID">
      <identifier>TEMP_ID_17</identifier>
    </dsql_operation>
    <dsql_operation operation_type="ON">
      <location permanent="false" distribution="AllDistributions" />
      <sql_operations>
        <sql_operation type="statement">CREATE TABLE [qtabledb].[dbo].[TEMP_ID_17] ([MedallionBKey] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL ) WITH(DISTRIBUTED_MOVE_FILE='');</sql_operation>
      </sql_operations>
    </dsql_operation>
    <dsql_operation operation_type="SHUFFLE_MOVE">
      <operation_cost cost="1.749504" accumulative_cost="1.749504" average_rowsize="32" output_rows="13668" GroupNumber="4" />
      <source_statement>SELECT [T1_1].[MedallionBKey] AS [MedallionBKey] FROM [SQLDW].[dbo].[Medallion_hash] AS T1_1
OPTION (MAXDOP 2, MIN_GRANT_PERCENT = [MIN_GRANT], DISTRIBUTED_MOVE(N''))</source_statement>
      <destination_table>[TEMP_ID_17]</destination_table>
      <shuffle_columns>MedallionBKey;</shuffle_columns>
    </dsql_operation>
    <dsql_operation operation_type="RND_ID">
      <identifier>TEMP_ID_18</identifier>
    </dsql_operation>
    <dsql_operation operation_type="ON">
      <location permanent="false" distribution="AllDistributions" />
      <sql_operations>
        <sql_operation type="statement">CREATE TABLE [qtabledb].[dbo].[TEMP_ID_18] ([MedallionID] INT NOT NULL, [MedallionBKey] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, [MedallionCode] VARCHAR(50) COLLATE SQL_Latin1_General_CP1_CI_AS ) WITH(DISTRIBUTED_MOVE_FILE='');</sql_operation>
      </sql_operations>
    </dsql_operation>
    <dsql_operation operation_type="SHUFFLE_MOVE">
      <operation_cost cost="4.701792" accumulative_cost="6.451296" average_rowsize="86" output_rows="13668" GroupNumber="3" />
      <source_statement>SELECT [T1_1].[MedallionID] AS [MedallionID], [T1_1].[MedallionBKey] AS [MedallionBKey], [T1_1].[MedallionCode] AS [MedallionCode] FROM [SQLDW].[dbo].[Medallion] AS T1_1
OPTION (MAXDOP 2, MIN_GRANT_PERCENT = [MIN_GRANT], DISTRIBUTED_MOVE(N''))</source_statement>
      <destination_table>[TEMP_ID_18]</destination_table>
      <shuffle_columns>MedallionBKey;</shuffle_columns>
    </dsql_operation>
    <dsql_operation operation_type="RETURN">
      <location distribution="AllDistributions" />
      <select>SELECT [T1_1].[MedallionID] AS [MedallionID], [T1_1].[MedallionBKey] AS [MedallionBKey], [T1_1].[MedallionCode] AS [MedallionCode] FROM (SELECT [T2_2].[MedallionID] AS [MedallionID], [T2_1].[MedallionBKey] AS [MedallionBKey], [T2_2].[MedallionCode] AS [MedallionCode] FROM [qtabledb].[dbo].[TEMP_ID_17] AS T2_1 INNER JOIN
[qtabledb].[dbo].[TEMP_ID_18] AS T2_2
ON ([T2_1].[MedallionBKey] = [T2_2].[MedallionBKey])) AS T1_1
OPTION (MAXDOP 2, MIN_GRANT_PERCENT = [MIN_GRANT])</select>
    </dsql_operation>
    <dsql_operation operation_type="ON">
      <location permanent="false" distribution="AllDistributions" />
      <sql_operations>
        <sql_operation type="statement">DROP TABLE [qtabledb].[dbo].[TEMP_ID_18]</sql_operation>
      </sql_operations>
    </dsql_operation>
    <dsql_operation operation_type="ON">
      <location permanent="false" distribution="AllDistributions" />
      <sql_operations>
        <sql_operation type="statement">DROP TABLE [qtabledb].[dbo].[TEMP_ID_17]</sql_operation>
      </sql_operations>
    </dsql_operation>
  </dsql_operations>
</dsql_query>

 

 

Note data movement is happening on the plan: <dsql_operation operation_type="SHUFFLE_MOVE">. Which means ( copy and paste again from my previous post):

SHUFFLE_MOVE - Redistributes a distributed table. The redistributed table has a different distribution column than the original distributed table. This might be used to when running incompatible joins or incompatible aggregations.

To perform this operation, SQL DW will move each row to the correct Compute node according to the distribution column of the destination table. Rows that are already stored on the correct Compute node are not copied during this operation.

 

So the case from this week is about that. There was a data movement which was not desired to be on the plan, for that we took some actions:

 

1) Review the distribution keys on the table

2) Review the stats. So, wrong stats can lead to MPP misestimated the plan. 

Follow a simple query to check your stats:

 

SELECT stats_id, name AS stats_name, 
    STATS_DATE(object_id, stats_id) AS statistics_date
FROM sys.stats s

 

 

That is it! I hope the examples help you to get an idea of the importance while defining distribution keys. 

 

Liliam C Leme

UK Engineer