sparkforensics-server 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/package.json +3 -3
  2. package/public/assets/{AutoscalingChurn-9Zo6IMGS.js → AutoscalingChurn-8t3U3isq.js} +1 -1
  3. package/public/assets/{CacheUtilization-MBDfMsKC.js → CacheUtilization-CJs9tTum.js} +1 -1
  4. package/public/assets/CachingOpportunity-D1NcHs0V.js +1 -0
  5. package/public/assets/{ColdStart-BtS12b6_.js → ColdStart-CdRJ9gD8.js} +1 -1
  6. package/public/assets/{ConfigAudit-DNPV6EQg.js → ConfigAudit-B5Gbe5js.js} +1 -1
  7. package/public/assets/CoreUsageArea-a--NTRGY.js +1 -0
  8. package/public/assets/{Dashboard-CC_BXrNy.js → Dashboard-C1hNeX49.js} +2 -2
  9. package/public/assets/DisclosureContext-ZpE6CkHx.js +1 -0
  10. package/public/assets/{DuplicatePlanSubtree-vN3zqRsi.js → DuplicatePlanSubtree-dobXdz7E.js} +1 -1
  11. package/public/assets/{ExecutorUtilization-CZObSLIj.js → ExecutorUtilization-D9grwi7t.js} +1 -1
  12. package/public/assets/{GcPressure-ByJlLOua.js → GcPressure-8-G2ri10.js} +1 -1
  13. package/public/assets/{IncompleteRun-MzqV4W8y.js → IncompleteRun-B91lky7a.js} +1 -1
  14. package/public/assets/{JobFailures-LbMvAwAb.js → JobFailures-BsWvBMyJ.js} +1 -1
  15. package/public/assets/{MemoryUtilization-DH4jYvJK.js → MemoryUtilization-BM5schZI.js} +1 -1
  16. package/public/assets/{OverBroadcast-euk6HKg4.js → OverBroadcast-DdN8JIJH.js} +1 -1
  17. package/public/assets/{PartitionHint-DThaMMRT.js → PartitionHint-CUvbnnxy.js} +1 -1
  18. package/public/assets/{PartitionSizing-yIN-iZzx.js → PartitionSizing-CY5HDYtp.js} +1 -1
  19. package/public/assets/{PlanExplorer-Dd9WPK4S.js → PlanExplorer-DgHCDBD_.js} +1 -1
  20. package/public/assets/PlanGraphRoute-DH3KJb8j.js +1 -0
  21. package/public/assets/{RetryWaste-CXH4D-xH.js → RetryWaste-DOnE9vGF.js} +1 -1
  22. package/public/assets/{ShuffleIO-CW7U68ph.js → ShuffleIO-MMnDESkF.js} +1 -1
  23. package/public/assets/{Skew-PL79TppX.js → Skew-BmZZJ5ms.js} +1 -1
  24. package/public/assets/{SlowHost-GBO73hCi.js → SlowHost-CsJzfEqo.js} +1 -1
  25. package/public/assets/{SmallFiles-B5cUjEav.js → SmallFiles-BoK3R1Ex.js} +1 -1
  26. package/public/assets/{SpeculationWaste-mxi3jgb0.js → SpeculationWaste-CnVxB5AJ.js} +1 -1
  27. package/public/assets/Spill-BhNicyBy.js +1 -0
  28. package/public/assets/{StageFailed-YvxC11ii.js → StageFailed-mAhHU4l_.js} +1 -1
  29. package/public/assets/StageFindingGroup-BEtPWcT0.js +1 -0
  30. package/public/assets/{StageHeader-BMNmUy-s.js → StageHeader-DJkYkrsU.js} +1 -1
  31. package/public/assets/{StageShape-DO3mQ6NS.js → StageShape-BDxRknLp.js} +1 -1
  32. package/public/assets/{StageSlowness-SwRxfAEx.js → StageSlowness-BPRw-otN.js} +1 -1
  33. package/public/assets/{Straggler-DHLTiBY-.js → Straggler-CZCbnOWP.js} +1 -1
  34. package/public/assets/{TaskFailures-Bj0QxiOc.js → TaskFailures-C57YcLFQ.js} +1 -1
  35. package/public/assets/{TinyTask-COUQQ5bQ.js → TinyTask-Bi84qU7n.js} +1 -1
  36. package/public/assets/{UnderBroadcast-RAUbQ6vw.js → UnderBroadcast-DdQV7htE.js} +1 -1
  37. package/public/assets/{finding-anchor-Dl8mIHiU.js → finding-anchor-Cl6_5hfV.js} +1 -1
  38. package/public/assets/{impact-sort-Eihua9dY.js → impact-sort-CssZ6MT2.js} +1 -1
  39. package/public/assets/index-2h_TgUAb.css +2 -0
  40. package/public/assets/index-B-03mG7K.js +14 -0
  41. package/public/assets/parser-worker-CA5RxqKc.js +122 -0
  42. package/public/assets/{plan-graph-vendor-B0sEms9Z.js → plan-graph-vendor-BYl-AzN5.js} +1 -1
  43. package/public/assets/{react-vendor-BAKt3vny.js → react-vendor-DEdfrRTJ.js} +1 -1
  44. package/public/assets/{select-DkjFHlXR.js → select-CQjNhprm.js} +1 -1
  45. package/public/assets/{usePagedRows-Bo-aSztx.js → usePagedRows-Dyl6T8Ok.js} +1 -1
  46. package/public/assets/useRecentFiles-XZEnLlal.js +11 -0
  47. package/public/assets/{useSortMode-lDedD5a3.js → useSortMode-Dhvit8dt.js} +1 -1
  48. package/public/docs/404.html +2 -2
  49. package/public/docs/assets/{app.yjczoibf.js → app.eYTPqTeq.js} +1 -1
  50. package/public/docs/assets/chunks/@localSearchIndexroot.u_e5bQz4.js +1 -0
  51. package/public/docs/assets/chunks/{VPLocalSearchBox.DOBbPfix.js → VPLocalSearchBox.DsZrQKQR.js} +1 -1
  52. package/public/docs/assets/chunks/{theme.DFcAl8Ws.js → theme.DsTNE2pE.js} +2 -2
  53. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.YQcj5EI4.js +1 -0
  54. package/public/docs/assets/{contributor-guide_architecture_board-widgets.md.D0Kkk_r5.lean.js → contributor-guide_architecture_board-widgets.md.YQcj5EI4.lean.js} +1 -1
  55. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.js +1 -0
  56. package/public/docs/assets/contributor-guide_architecture_board-widgets_caching-and-memory.md.8bH1HnZp.lean.js +1 -0
  57. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.js +1 -0
  58. package/public/docs/assets/contributor-guide_architecture_board-widgets_plan-advisor.md.RlzvUmNm.lean.js +1 -0
  59. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.js +1 -0
  60. package/public/docs/assets/contributor-guide_architecture_board-widgets_visual-system-and-templating.md.DYgUpIJB.lean.js +1 -0
  61. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.js +1 -0
  62. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.wj6Et6UD.lean.js +1 -0
  63. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.js +1 -0
  64. package/public/docs/assets/contributor-guide_architecture_detector-contract_plan-attribution.md.yNex4bys.lean.js +1 -0
  65. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.js +1 -0
  66. package/public/docs/assets/contributor-guide_architecture_detector-contract_threshold-tables.md.CKjWG56Y.lean.js +1 -0
  67. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.js +1 -0
  68. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CqZPrdo9.lean.js +1 -0
  69. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.js +1 -0
  70. package/public/docs/assets/contributor-guide_architecture_drill-down_plan-graph.md.DwOozIEs.lean.js +1 -0
  71. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.js +1 -0
  72. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.CZv_ZGOp.lean.js +1 -0
  73. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.js +1 -0
  74. package/public/docs/assets/contributor-guide_architecture_impact-estimation_caveats-tuning-and-coverage.md.BUucRc0A.lean.js +1 -0
  75. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.js +1 -0
  76. package/public/docs/assets/contributor-guide_architecture_impact-estimation_stage-union-rollup.md.DdCGtAGu.lean.js +1 -0
  77. package/public/docs/assets/contributor-guide_architecture_index.md.tymxf1Fh.js +1 -0
  78. package/public/docs/assets/{contributor-guide_architecture_index.md.B9yZieYL.lean.js → contributor-guide_architecture_index.md.tymxf1Fh.lean.js} +1 -1
  79. package/public/docs/assets/contributor-guide_architecture_overview.md.C1sSvm9i.js +1 -0
  80. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.DyCBdjuM.js +1 -0
  81. package/public/docs/assets/{contributor-guide_architecture_state-and-history.md.w4lwybdh.lean.js → contributor-guide_architecture_state-and-history.md.DyCBdjuM.lean.js} +1 -1
  82. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.js +1 -0
  83. package/public/docs/assets/contributor-guide_architecture_state-and-history_history-server-intake.md.C298hrHZ.lean.js +1 -0
  84. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.js +1 -0
  85. package/public/docs/assets/contributor-guide_architecture_state-and-history_run-comparison-internals.md.CZA0JLtV.lean.js +1 -0
  86. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.js +1 -0
  87. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.BOZ1azXG.lean.js +1 -0
  88. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.js +1 -0
  89. package/public/docs/assets/contributor-guide_architecture_widget-rendering_findings-tab.md.BQXycK6G.lean.js +1 -0
  90. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.js +1 -0
  91. package/public/docs/assets/contributor-guide_architecture_widget-rendering_investigation-routing.md.BGdiyElu.lean.js +1 -0
  92. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.js +1 -0
  93. package/public/docs/assets/contributor-guide_architecture_widget-rendering_list-sort-mode.md.BuHKgH0C.lean.js +1 -0
  94. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.js +1 -0
  95. package/public/docs/assets/contributor-guide_architecture_widget-rendering_reference-section.md.DWqC80bN.lean.js +1 -0
  96. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.js +1 -0
  97. package/public/docs/assets/contributor-guide_architecture_widget-rendering_row-expand-contract.md.ZT89u9f1.lean.js +1 -0
  98. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.js +1 -0
  99. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.BAnhi2-d.lean.js +1 -0
  100. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.js +1 -0
  101. package/public/docs/assets/contributor-guide_architecture_worker-protocol_cli-and-mcp.md.DC1YqXBp.lean.js +1 -0
  102. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.js +1 -0
  103. package/public/docs/assets/contributor-guide_architecture_worker-protocol_core-and-event-validation.md.DSVFPpWb.lean.js +1 -0
  104. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.js +6 -0
  105. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-availability.md.CjimIDBY.lean.js +1 -0
  106. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.js +1 -0
  107. package/public/docs/assets/contributor-guide_architecture_worker-protocol_evidence-report.md.DVGZOWvW.lean.js +1 -0
  108. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.js +4 -0
  109. package/public/docs/assets/user-guide_alternative-log-retrieval.md.CJpYvHGo.lean.js +1 -0
  110. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.js +4 -0
  111. package/public/docs/assets/user-guide_alternative-log-retrieval_managed-platforms.md.DvDwHNtC.lean.js +1 -0
  112. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.js +1 -0
  113. package/public/docs/assets/user-guide_alternative-log-retrieval_ssh-bastion.md.Gszzlplv.lean.js +1 -0
  114. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.js +1 -0
  115. package/public/docs/assets/user-guide_getting-started.md.DYHHHkQR.lean.js +1 -0
  116. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.js +23 -0
  117. package/public/docs/assets/user-guide_getting-started_ci-and-automation.md.OUQqw3mI.lean.js +1 -0
  118. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.js +1 -0
  119. package/public/docs/assets/user-guide_getting-started_reading-the-dashboard.md.Obf3W3__.lean.js +1 -0
  120. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.js +1 -0
  121. package/public/docs/assets/user-guide_getting-started_regression-metric-keys.md.B_gzr2Mw.lean.js +1 -0
  122. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.js +4 -0
  123. package/public/docs/assets/user-guide_getting-started_tuning-thresholds.md.B_MyjyZm.lean.js +1 -0
  124. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.js +16 -0
  125. package/public/docs/assets/user-guide_mcp-tools.md.C65SjWUL.lean.js +1 -0
  126. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.js +56 -0
  127. package/public/docs/assets/user-guide_mcp-tools_compare-runs.md.Cc_gLoix.lean.js +1 -0
  128. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.js +23 -0
  129. package/public/docs/assets/user-guide_mcp-tools_diagnose-run.md.BP1vnHA5.lean.js +1 -0
  130. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.js +26 -0
  131. package/public/docs/assets/user-guide_mcp-tools_evaluate-budgets.md.BB0nDY3Z.lean.js +1 -0
  132. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.js +14 -0
  133. package/public/docs/assets/user-guide_mcp-tools_get-finding-documentation.md.BtTTHNV6.lean.js +1 -0
  134. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.js +20 -0
  135. package/public/docs/assets/user-guide_mcp-tools_get-finding-evidence.md.rKyu4wts.lean.js +1 -0
  136. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.js +5 -0
  137. package/public/docs/assets/user-guide_mcp-tools_get-reference-doc.md.Yac4tH3u.lean.js +1 -0
  138. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.js +15 -0
  139. package/public/docs/assets/user-guide_mcp-tools_get-run-summary.md.DpxZ4ZNp.lean.js +1 -0
  140. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.js +12 -0
  141. package/public/docs/assets/user-guide_mcp-tools_list-runs.md.DpQPeCM3.lean.js +1 -0
  142. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.js +1 -0
  143. package/public/docs/assets/user-guide_run-comparison.md.BUALkohi.lean.js +1 -0
  144. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.js +1 -0
  145. package/public/docs/assets/user-guide_run-comparison_how-stages-are-matched.md.DSXGu2tA.lean.js +1 -0
  146. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.js +1 -0
  147. package/public/docs/assets/user-guide_understanding-findings.md.C9YRruMT.lean.js +1 -0
  148. package/public/docs/contributor-guide/architecture/board-widgets/caching-and-memory.html +30 -0
  149. package/public/docs/contributor-guide/architecture/board-widgets/plan-advisor.html +30 -0
  150. package/public/docs/contributor-guide/architecture/board-widgets/visual-system-and-templating.html +30 -0
  151. package/public/docs/contributor-guide/architecture/board-widgets.html +5 -5
  152. package/public/docs/contributor-guide/architecture/detector-contract/plan-attribution.html +30 -0
  153. package/public/docs/contributor-guide/architecture/detector-contract/threshold-tables.html +30 -0
  154. package/public/docs/contributor-guide/architecture/detector-contract.html +5 -5
  155. package/public/docs/contributor-guide/architecture/drill-down/plan-graph.html +30 -0
  156. package/public/docs/contributor-guide/architecture/drill-down.html +5 -5
  157. package/public/docs/contributor-guide/architecture/impact-estimation/caveats-tuning-and-coverage.html +30 -0
  158. package/public/docs/contributor-guide/architecture/impact-estimation/stage-union-rollup.html +30 -0
  159. package/public/docs/contributor-guide/architecture/impact-estimation.html +5 -5
  160. package/public/docs/contributor-guide/architecture/index.html +5 -5
  161. package/public/docs/contributor-guide/architecture/overview.html +5 -5
  162. package/public/docs/contributor-guide/architecture/state-and-history/history-server-intake.html +30 -0
  163. package/public/docs/contributor-guide/architecture/state-and-history/run-comparison-internals.html +30 -0
  164. package/public/docs/contributor-guide/architecture/state-and-history.html +5 -5
  165. package/public/docs/contributor-guide/architecture/widget-rendering/findings-tab.html +30 -0
  166. package/public/docs/contributor-guide/architecture/widget-rendering/investigation-routing.html +30 -0
  167. package/public/docs/contributor-guide/architecture/widget-rendering/list-sort-mode.html +30 -0
  168. package/public/docs/contributor-guide/architecture/widget-rendering/reference-section.html +30 -0
  169. package/public/docs/contributor-guide/architecture/widget-rendering/row-expand-contract.html +30 -0
  170. package/public/docs/contributor-guide/architecture/widget-rendering.html +5 -5
  171. package/public/docs/contributor-guide/architecture/worker-protocol/cli-and-mcp.html +30 -0
  172. package/public/docs/contributor-guide/architecture/worker-protocol/core-and-event-validation.html +30 -0
  173. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-availability.html +35 -0
  174. package/public/docs/contributor-guide/architecture/worker-protocol/evidence-report.html +30 -0
  175. package/public/docs/contributor-guide/architecture/worker-protocol.html +5 -10
  176. package/public/docs/contributor-guide/contributing.html +4 -4
  177. package/public/docs/contributor-guide/development-setup.html +4 -4
  178. package/public/docs/contributor-guide/testing.html +4 -4
  179. package/public/docs/hashmap.json +1 -1
  180. package/public/docs/index.html +3 -3
  181. package/public/docs/tuning-reference/anti-patterns.html +3 -3
  182. package/public/docs/tuning-reference/aqe.html +3 -3
  183. package/public/docs/tuning-reference/bottleneck-broadcast-sizing.html +3 -3
  184. package/public/docs/tuning-reference/bottleneck-cold-start.html +3 -3
  185. package/public/docs/tuning-reference/bottleneck-duplicate-plan-subtree.html +3 -3
  186. package/public/docs/tuning-reference/bottleneck-failures.html +3 -3
  187. package/public/docs/tuning-reference/bottleneck-gc.html +3 -3
  188. package/public/docs/tuning-reference/bottleneck-job-failure-rate.html +3 -3
  189. package/public/docs/tuning-reference/bottleneck-memory-utilization.html +3 -3
  190. package/public/docs/tuning-reference/bottleneck-retry-waste.html +3 -3
  191. package/public/docs/tuning-reference/bottleneck-shuffle.html +3 -3
  192. package/public/docs/tuning-reference/bottleneck-skew.html +3 -3
  193. package/public/docs/tuning-reference/bottleneck-slow-host.html +3 -3
  194. package/public/docs/tuning-reference/bottleneck-small-files.html +3 -3
  195. package/public/docs/tuning-reference/bottleneck-spill.html +3 -3
  196. package/public/docs/tuning-reference/bottleneck-straggler.html +3 -3
  197. package/public/docs/tuning-reference/bottleneck-tiny-tasks.html +3 -3
  198. package/public/docs/tuning-reference/bottleneck-utilization.html +3 -3
  199. package/public/docs/tuning-reference/caching.html +3 -3
  200. package/public/docs/tuning-reference/cluster-config.html +3 -3
  201. package/public/docs/tuning-reference/config.html +3 -3
  202. package/public/docs/tuning-reference/data-formats.html +3 -3
  203. package/public/docs/tuning-reference/index.html +3 -3
  204. package/public/docs/tuning-reference/intro.html +3 -3
  205. package/public/docs/tuning-reference/joins.html +3 -3
  206. package/public/docs/tuning-reference/memory-model.html +3 -3
  207. package/public/docs/tuning-reference/metrics.html +3 -3
  208. package/public/docs/tuning-reference/partitioning.html +3 -3
  209. package/public/docs/tuning-reference/pyspark.html +3 -3
  210. package/public/docs/tuning-reference/shuffle.html +3 -3
  211. package/public/docs/tuning-reference/spark-architecture.html +3 -3
  212. package/public/docs/tuning-reference/table-formats.html +3 -3
  213. package/public/docs/user-guide/alternative-log-retrieval/managed-platforms.html +33 -0
  214. package/public/docs/user-guide/alternative-log-retrieval/ssh-bastion.html +30 -0
  215. package/public/docs/user-guide/alternative-log-retrieval.html +6 -9
  216. package/public/docs/user-guide/getting-started/ci-and-automation.html +52 -0
  217. package/public/docs/user-guide/getting-started/reading-the-dashboard.html +30 -0
  218. package/public/docs/user-guide/getting-started/regression-metric-keys.html +30 -0
  219. package/public/docs/user-guide/getting-started/tuning-thresholds.html +33 -0
  220. package/public/docs/user-guide/getting-started.html +5 -28
  221. package/public/docs/user-guide/mcp-tools/compare-runs.html +85 -0
  222. package/public/docs/user-guide/mcp-tools/diagnose-run.html +52 -0
  223. package/public/docs/user-guide/mcp-tools/evaluate-budgets.html +55 -0
  224. package/public/docs/user-guide/mcp-tools/get-finding-documentation.html +43 -0
  225. package/public/docs/user-guide/mcp-tools/get-finding-evidence.html +49 -0
  226. package/public/docs/user-guide/mcp-tools/get-reference-doc.html +34 -0
  227. package/public/docs/user-guide/mcp-tools/get-run-summary.html +44 -0
  228. package/public/docs/user-guide/mcp-tools/list-runs.html +41 -0
  229. package/public/docs/user-guide/mcp-tools.html +8 -140
  230. package/public/docs/user-guide/run-comparison/how-stages-are-matched.html +30 -0
  231. package/public/docs/user-guide/run-comparison.html +5 -5
  232. package/public/docs/user-guide/understanding-findings.html +5 -5
  233. package/public/export-template.html +45 -45
  234. package/public/index.html +5 -5
  235. package/vendor-core/analyzer.js +1 -1
  236. package/vendor-core/api-merge-writes.js +80 -0
  237. package/vendor-core/cli/budgets.js +62 -2
  238. package/vendor-core/cli/regression-budgets.js +35 -0
  239. package/vendor-core/comparison-output.js +50 -0
  240. package/vendor-core/core-source-hash.txt +1 -1
  241. package/vendor-core/delta-commands.js +13 -0
  242. package/vendor-core/delta-log-paths.js +42 -0
  243. package/vendor-core/delta-targets.js +127 -0
  244. package/vendor-core/detectors.js +219 -56
  245. package/vendor-core/docs-content/detection/chrn.md +3 -1
  246. package/vendor-core/docs-content/detection/cold.md +2 -1
  247. package/vendor-core/docs-content/detection/plan.md +9 -2
  248. package/vendor-core/docs-content/detection/shfl.md +8 -2
  249. package/vendor-core/docs-content/detection/skew.md +11 -5
  250. package/vendor-core/docs-content/detection/slow.md +6 -2
  251. package/vendor-core/docs-content/detection/spill.md +4 -1
  252. package/vendor-core/docs-content/detection/strag.md +5 -3
  253. package/vendor-core/docs-content/detection/tiny.md +3 -2
  254. package/vendor-core/event-handlers.js +95 -9
  255. package/vendor-core/event-schemas.js +3 -1
  256. package/vendor-core/evidence-report.js +24 -15
  257. package/vendor-core/finding-presentation.js +39 -13
  258. package/vendor-core/finding-types.js +50 -6
  259. package/vendor-core/mcp-server-factory.js +17 -6
  260. package/vendor-core/mcp-tools.js +53 -35
  261. package/vendor-core/median.js +8 -0
  262. package/vendor-core/plan-graph-model.js +1 -1
  263. package/vendor-core/plan-summary.js +13 -10
  264. package/vendor-core/run-comparison.js +81 -120
  265. package/vendor-core/run-output.js +26 -0
  266. package/vendor-core/sequence-alignment.js +83 -0
  267. package/vendor-core/stage-alignment.js +586 -0
  268. package/vendor-core/stage-identity.js +112 -0
  269. package/vendor-core/stage-quantiles.js +3 -1
  270. package/vendor-core/stage-structure.js +83 -0
  271. package/vendor-core/types.js +5 -0
  272. package/vendor-core/write-target-args.js +48 -0
  273. package/vendor-core/write-targets.js +35 -52
  274. package/public/assets/CachingOpportunity-Cfw3wAFa.js +0 -1
  275. package/public/assets/CoreUsageArea-BObEeenE.js +0 -1
  276. package/public/assets/DisclosureContext-Ds0hcZFn.js +0 -1
  277. package/public/assets/PlanGraphRoute-NV5mCXRm.js +0 -1
  278. package/public/assets/Spill-CH1bNpxL.js +0 -1
  279. package/public/assets/StageFindingGroup-B1A0nf-e.js +0 -1
  280. package/public/assets/index-BcqTLKFp.css +0 -2
  281. package/public/assets/index-DaC8A2FS.js +0 -13
  282. package/public/assets/parser-worker-CfDvPkwF.js +0 -122
  283. package/public/assets/useRecentFiles-Bb4-ukVz.js +0 -11
  284. package/public/docs/assets/chunks/@localSearchIndexroot.r9imX8Aq.js +0 -1
  285. package/public/docs/assets/contributor-guide_architecture_board-widgets.md.D0Kkk_r5.js +0 -1
  286. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.js +0 -1
  287. package/public/docs/assets/contributor-guide_architecture_detector-contract.md.caA46z-u.lean.js +0 -1
  288. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.js +0 -1
  289. package/public/docs/assets/contributor-guide_architecture_drill-down.md.CdtlKHl-.lean.js +0 -1
  290. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.js +0 -1
  291. package/public/docs/assets/contributor-guide_architecture_impact-estimation.md.BNp14LtL.lean.js +0 -1
  292. package/public/docs/assets/contributor-guide_architecture_index.md.B9yZieYL.js +0 -1
  293. package/public/docs/assets/contributor-guide_architecture_overview.md.DBGo-bwn.js +0 -1
  294. package/public/docs/assets/contributor-guide_architecture_state-and-history.md.w4lwybdh.js +0 -1
  295. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.js +0 -1
  296. package/public/docs/assets/contributor-guide_architecture_widget-rendering.md.CcvqxnZI.lean.js +0 -1
  297. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.js +0 -6
  298. package/public/docs/assets/contributor-guide_architecture_worker-protocol.md.CKeFttKj.lean.js +0 -1
  299. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.js +0 -7
  300. package/public/docs/assets/user-guide_alternative-log-retrieval.md.z-8npxNk.lean.js +0 -1
  301. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.js +0 -24
  302. package/public/docs/assets/user-guide_getting-started.md.BJW5oVhe.lean.js +0 -1
  303. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.js +0 -148
  304. package/public/docs/assets/user-guide_mcp-tools.md.DcLiOa9t.lean.js +0 -1
  305. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.js +0 -1
  306. package/public/docs/assets/user-guide_run-comparison.md.qin0a4IX.lean.js +0 -1
  307. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.js +0 -1
  308. package/public/docs/assets/user-guide_understanding-findings.md.DzReyCXb.lean.js +0 -1
  309. /package/public/docs/assets/{contributor-guide_architecture_overview.md.DBGo-bwn.lean.js → contributor-guide_architecture_overview.md.C1sSvm9i.lean.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  import { pathBasename, formatBytes, IMPACT_BAND_ORDER } from './format-utils.js';
2
+ import { medianOfSorted } from './median.js';
2
3
  import { shareLabel } from './finding-presentation.js';
3
4
  import { scanRelationId } from './plan-summary.js';
4
5
  import { computePeakConcurrentCores, computePeakConcurrentExecutorCount } from './core-count.js';
@@ -21,7 +22,7 @@ import { cyrb53 } from './string-hash.js';
21
22
  import { decreaseConf, increaseConf, setConf } from './remediation.js';
22
23
  import { MAX_FAILURE_GROUPS, describeTaskFailure, } from './task-failure.js';
23
24
 
24
-
25
+
25
26
 
26
27
  const MB = 1024 * 1024;
27
28
  const GB = 1024 * MB;
@@ -543,7 +544,7 @@ function maxMedianRatio(
543
544
  ) {
544
545
  if (samples.length < 3) return null;
545
546
  const vals = samples.map(s => s.value).sort((a, b) => a - b);
546
- const median = vals[Math.floor(vals.length / 2)];
547
+ const median = medianOfSorted(vals);
547
548
  if (median <= 0) return null;
548
549
  const top = samples.reduce((a, b) => (b.value > a.value ? b : a));
549
550
  return { key: top.key, ratio: top.value / median, value: top.value };
@@ -655,14 +656,36 @@ function tailClaimFloorMs(claim , stageId , ctx )
655
656
  return tailClaimImpact(claim, stageId, ctx.impact).wallClock?.high ?? claim.wasteMs;
656
657
  }
657
658
 
658
- // A setting is only a fix when the run's logged conf doesn't already have it: a run that set it
659
- // needs another remedy. Only explicitly logged properties count; Spark's unlogged version
660
- // defaults are not modeled. Booleans compare case-insensitively, as Spark parses them.
659
+ // Spark's default for the properties a detector's fix reads or suggests, for a run that did not log
660
+ // them. Undefined when the property is not modeled or its default depends on a version the log did
661
+ // not record. A property is modeled only where its default has made a suggestion a no-op; every
662
+ // other property a detector suggests defaults off, or to a value other than the suggested one.
663
+ function versionDefault(app , key ) {
664
+ if (key === 'spark.sql.shuffle.partitions') return '200';
665
+ if (key === 'spark.sql.autoBroadcastJoinThreshold') return String(10 * MB);
666
+ const version = /^(\d+)\.(\d+)/.exec(app?.sparkVersion ?? '');
667
+ if (version == null) return undefined;
668
+ const [major, minor] = [Number(version[1]), Number(version[2])];
669
+ if (key === 'spark.sql.adaptive.enabled') return major > 3 || (major === 3 && minor >= 2) ? 'true' : 'false';
670
+ if (key === 'spark.sql.adaptive.skewJoin.enabled' || key === 'spark.sql.adaptive.coalescePartitions.enabled') {
671
+ return major >= 3 ? 'true' : undefined;
672
+ }
673
+ return undefined;
674
+ }
675
+
676
+ // The run's effective value of a property: the logged one, else versionDefault's.
677
+ function effectiveConf(app , key ) {
678
+ return (app?.config?.[key] ?? versionDefault(app, key))?.trim();
679
+ }
680
+
681
+ // A setting is only a fix when the run's effective conf doesn't already have it: a run that set it,
682
+ // or whose Spark version defaults to it, needs another remedy. Booleans compare case-insensitively,
683
+ // as Spark parses them.
661
684
  function loggedAs(app , key , suggested ) {
662
- const logged = app?.config?.[key]?.trim();
685
+ const effective = effectiveConf(app, key);
663
686
  return typeof suggested === 'boolean'
664
- ? logged?.toLowerCase() === String(suggested)
665
- : logged === suggested;
687
+ ? effective?.toLowerCase() === String(suggested)
688
+ : effective === suggested;
666
689
  }
667
690
 
668
691
  function setConfUnlessLogged(app , key , suggested ) {
@@ -675,18 +698,67 @@ function switchFix(on , key , suggested , recomm
675
698
  return on ? { text: alreadyOn, remediation: [] } : { text: recommend, remediation: [setConf(key, suggested)] };
676
699
  }
677
700
 
701
+ const SKEW_KEY_REMEDY = 'salt the key or repartition on a better key';
702
+
703
+ // What a stage reads, by the dominant side: a stage that reads a shuffle and mostly input files is
704
+ // an input stage for any fix that sizes a shuffle's reduce side.
705
+ function stageReads(stage ) {
706
+ if (stage.shuffleReadBytes > 0 && stage.shuffleReadBytes >= stage.inputBytes) return 'shuffle';
707
+ return stage.inputBytes > 0 ? 'input' : 'other';
708
+ }
709
+
710
+ // Whether the run's Spark predates 3.0, which has no AQE skew-join handling to suggest.
711
+ function predatesAqeSkewJoin(app ) {
712
+ const major = /^(\d+)\./.exec(app?.sparkVersion ?? '');
713
+ return major != null && Number(major[1]) < 3;
714
+ }
715
+
716
+ // What a skewed stage reads, which decides whether AQE skew-join handling can act on it. It splits
717
+ // skewed partitions on the shuffle-read side of a sort-merge or shuffled-hash join, so only a stage
718
+ // that reads a shuffle, in a SQL execution whose plan has such a join, is 'shuffleJoin'. A plan does
719
+ // not say which stage runs which join (a node's stageIds are empty in real logs), so the join is
720
+ // matched per execution. 'inputScan' reads mostly files (its tasks differ in input size);
721
+ // 'other' is any stage the plan cannot tie to a join (an aggregation's shuffle, no plan, no input),
722
+ // and any stage of a run on Spark before 3.0 (skewFix).
723
+ function skewOrigin(stage , ctx , shuffleEvidence ) {
724
+ if (shuffleEvidence || (stage.shuffleReadBytes > 0 && stage.shuffleReadBytes >= stage.inputBytes)) {
725
+ let hasJoin = false;
726
+ const plan = stage.sqlExecutionId != null ? ctx.sql.get(stage.sqlExecutionId)?.planTree : null;
727
+ walkPlanTree(plan, (node) => { if (SKEW_JOIN_NODES.has(node.name)) hasJoin = true; });
728
+ return hasJoin ? 'shuffleJoin' : 'other';
729
+ }
730
+ return stage.inputBytes > 0 ? 'inputScan' : 'other';
731
+ }
732
+ const SKEW_JOIN_NODES = new Set(['SortMergeJoin', 'ShuffledHashJoin']);
733
+
734
+ // The skew finding's fix for the stage's origin. Join-driven skew gets AQE skew-join handling,
735
+ // unless the run's effective conf already has it; uneven input gets the file-size remedy.
736
+ function skewFix(stage , ctx , shuffleEvidence = false) {
737
+ const origin = skewOrigin(stage, ctx, shuffleEvidence);
738
+ if (origin === 'inputScan') {
739
+ return {
740
+ origin,
741
+ text: 'the stage reads uneven input files: compact small files or split large ones (lower spark.sql.files.maxPartitionBytes)',
742
+ remediation: [decreaseConf('spark.sql.files.maxPartitionBytes')],
743
+ };
744
+ }
745
+ if (origin === 'other') return { origin, text: SKEW_KEY_REMEDY, remediation: [] };
746
+ // Before Spark 3.0 there is no AQE skew-join handling, so the stage gets the generic advice.
747
+ if (predatesAqeSkewJoin(ctx.app)) return { origin: 'other', text: SKEW_KEY_REMEDY, remediation: [] };
748
+ return { origin, ...skewJoinFix(ctx.app) };
749
+ }
750
+
678
751
  function skewJoinFix(app ) {
679
752
  const key = 'spark.sql.adaptive.skewJoin.enabled';
680
- const remedy = 'salt the key or repartition on a better key';
681
753
  if (loggedAs(app, 'spark.sql.adaptive.enabled', false)) {
682
754
  return {
683
- text: `AQE is off, so enable it (spark.sql.adaptive.enabled) for skew-join handling to apply; otherwise ${remedy}`,
755
+ text: `AQE is off, so enable it (spark.sql.adaptive.enabled) for skew-join handling to apply; otherwise ${SKEW_KEY_REMEDY}`,
684
756
  remediation: [setConf('spark.sql.adaptive.enabled', true), ...setConfUnlessLogged(app, key, true)],
685
757
  };
686
758
  }
687
759
  return switchFix(loggedAs(app, key, true), key, true,
688
- `for join-driven skew, enable AQE skew-join handling (${key}); otherwise ${remedy}`,
689
- `AQE skew-join handling is already on, so ${remedy}`);
760
+ `for join-driven skew, enable AQE skew-join handling (${key}); otherwise ${SKEW_KEY_REMEDY}`,
761
+ `AQE skew-join handling is already on, so ${SKEW_KEY_REMEDY}`);
690
762
  }
691
763
 
692
764
  // The resources flag is read from the same property as the logged conf.
@@ -695,31 +767,75 @@ function dynamicAllocationFix(app , recommend , alreadyOn
695
767
  return switchFix(app.resources?.dynamicAllocationEnabled === true || loggedAs(app, key, true), key, true, recommend, alreadyOn);
696
768
  }
697
769
 
698
- // The run's logged spark.sql.shuffle.partitions as a count, or null when unlogged or not a count.
699
- function loggedShufflePartitions(app ) {
700
- const logged = app?.config?.['spark.sql.shuffle.partitions']?.trim();
701
- return logged != null && /^\d+$/.test(logged) ? Number(logged) : null;
770
+ // The run's effective spark.sql.shuffle.partitions (logged, else Spark's 200) as a count, or null
771
+ // when the logged value is not a count.
772
+ function effectiveShufflePartitions(app ) {
773
+ const value = effectiveConf(app, 'spark.sql.shuffle.partitions');
774
+ return value != null && /^\d+$/.test(value) ? Number(value) : null;
775
+ }
776
+
777
+ // Whether partition-count advice fits a shuffle-reading stage, from the effective conf and the
778
+ // stage's own task sizes. The partition count that brings each shuffle partition down to the ideal
779
+ // size is per stage; the property is job-wide. 'sufficient': the stage's tasks are already at or
780
+ // under the ideal size. 'aqeCoalesced': AQE merged the property's partitions into fewer tasks, so
781
+ // the advisory partition size is the lever. 'ownPartitioning': the property already gives at least
782
+ // the count needed, so the stage's own repartition(n) or RDD parallelism limits it. Otherwise the
783
+ // property limits the stage: 'raise'.
784
+ function shufflePartitionCase(stage , app ) {
785
+ const count = effectiveShufflePartitions(app);
786
+ const needed = Math.ceil(stage.shuffleReadBytes / IDEAL_BYTES_PER_PARTITION_TASK);
787
+ const tasks = Math.max(1, stage.taskCount);
788
+ let partitions = 'raise';
789
+ if (stage.shuffleReadBytes / tasks <= IDEAL_BYTES_PER_PARTITION_TASK) partitions = 'sufficient';
790
+ else if (count != null && stage.taskCount < count
791
+ && loggedAs(app, 'spark.sql.adaptive.enabled', true) && loggedAs(app, 'spark.sql.adaptive.coalescePartitions.enabled', true)) partitions = 'aqeCoalesced';
792
+ else if (count != null && count >= needed) partitions = 'ownPartitioning';
793
+ return { partitions, count, needed };
702
794
  }
703
795
 
704
- // lowShuffleParallelism's fix. The partition count that brings each shuffle partition down to the
705
- // ideal size, as the estimate models it, is per stage; the property is job-wide. A logged value at
706
- // or above it means the property is not what limits that stage (a repartition(n) or an RDD
707
- // shuffle is), so the text points at the stage's own partitioning and no property is suggested.
708
- // Unlogged, the count is not a safe value: null.
709
- function lowShuffleParallelismFix(app , needed ) {
710
- const logged = loggedShufflePartitions(app);
711
- if (logged != null && logged >= needed) {
796
+ const ADVISORY_PARTITION_SIZE_KEY = 'spark.sql.adaptive.advisoryPartitionSizeInBytes';
797
+
798
+ // lowShuffleParallelism's fix. Unlike the shuffle finding it fires on stages whose tasks are larger
799
+ // than the ideal size, so its cases are 'raise', 'aqeCoalesced' and 'ownPartitioning'.
800
+ function lowShuffleParallelismFix(stage , app ) {
801
+ const { partitions, count, needed } = shufflePartitionCase(stage, app);
802
+ if (partitions === 'aqeCoalesced') {
712
803
  return {
713
- text: `spark.sql.shuffle.partitions is already ${logged}, so raise this stage's own partition count (its repartition(n) or RDD parallelism) so each partition is smaller`,
804
+ partitions,
805
+ text: `AQE coalesced the shuffle into ${stage.taskCount} tasks (from ${count} configured partitions): lower ${ADVISORY_PARTITION_SIZE_KEY} so each partition is smaller`,
806
+ remediation: [decreaseConf(ADVISORY_PARTITION_SIZE_KEY)],
807
+ };
808
+ }
809
+ if (partitions === 'ownPartitioning') {
810
+ return {
811
+ partitions,
812
+ text: `spark.sql.shuffle.partitions is already ${count}, so raise this stage's own partition count (its repartition(n) or RDD parallelism) so each partition is smaller`,
714
813
  remediation: [],
715
814
  };
716
815
  }
717
816
  return {
817
+ partitions,
718
818
  text: 'raise spark.sql.shuffle.partitions so each partition is smaller',
719
- remediation: [increaseConf('spark.sql.shuffle.partitions', logged == null ? null : needed)],
819
+ remediation: [increaseConf('spark.sql.shuffle.partitions', count == null ? null : needed)],
720
820
  };
721
821
  }
722
822
 
823
+ // A Spark byte-size property as bytes (a plain number, or with a k/m/g/t suffix, optionally 'b'), or
824
+ // null when it is not one. Negative numbers pass through: -1 turns auto-broadcast off.
825
+ function parseSparkBytes(value ) {
826
+ const m = /^(-?\d+(?:\.\d+)?)\s*([kmgt]?)b?$/i.exec(value?.trim() ?? '');
827
+ if (m == null) return null;
828
+ return m[2] === '' ? Number(m[1]) : Number(m[1]) * 1024 ** ('kmgt'.indexOf(m[2].toLowerCase()) + 1);
829
+ }
830
+
831
+ // The run's effective spark.sql.autoBroadcastJoinThreshold in bytes (logged, else Spark's 10 MiB);
832
+ // negative when auto-broadcast is disabled, null when the logged value is not a size.
833
+ function effectiveBroadcastThreshold(app ) {
834
+ const raw = effectiveConf(app, 'spark.sql.autoBroadcastJoinThreshold');
835
+ if (raw != null && /^-\d+$/.test(raw)) return -1;
836
+ return parseSparkBytes(raw);
837
+ }
838
+
723
839
  // No dynamic-allocation property has an effect on a run whose logged conf turns it off.
724
840
  function dynamicAllocationOff(app ) {
725
841
  return app?.resources?.dynamicAllocationEnabled === false || app?.config?.['spark.dynamicAllocation.enabled']?.trim().toLowerCase() === 'false';
@@ -1061,9 +1177,9 @@ export const DETECTORS = [
1061
1177
  const floorWasteMs = tailClaimFloorMs(skewTailClaim(stage, metric === 'P95/median'), stage.id, ctx);
1062
1178
  if (!meetsRuntimeFloor(floorWasteMs, appDurationMs(ctx.app), thresholds.floorPctWarn)) return null;
1063
1179
  const value = Math.round(ratio * 10) / 10;
1064
- const fix = skewJoinFix(ctx.app);
1180
+ const fix = skewFix(stage, ctx);
1065
1181
  return {
1066
- type: 'skew', stageId: stage.id,
1182
+ type: 'skew', stageId: stage.id, origin: fix.origin,
1067
1183
  impactBand: 'warning',
1068
1184
  metric, value,
1069
1185
  confidence: skewConfidence(ratio, thresholds.ratioWarn),
@@ -1188,12 +1304,21 @@ export const DETECTORS = [
1188
1304
  const bytes = stage.shuffleReadBytes;
1189
1305
  if (bytes <= thresholds.minBytes) return null;
1190
1306
  if (stageBelowRuntimeFloor(stage, ctx, thresholds.stageFloorPct)) return null;
1307
+ const { partitions, count } = shufflePartitionCase(stage, ctx.app);
1308
+ const perTask = formatBytes(bytes / Math.max(1, stage.taskCount));
1309
+ const fix = partitions === 'sufficient'
1310
+ ? { text: `its ${stage.taskCount} tasks already read about ${perTask} each, so more partitions will not help: consider a broadcast join for the smaller side`, remediation: [] }
1311
+ : partitions === 'aqeCoalesced'
1312
+ ? { text: `AQE coalesced it into ${stage.taskCount} tasks (from ${count} configured partitions): consider lowering ${ADVISORY_PARTITION_SIZE_KEY} or adding a broadcast join`, remediation: [decreaseConf(ADVISORY_PARTITION_SIZE_KEY)] }
1313
+ : partitions === 'ownPartitioning'
1314
+ ? { text: `spark.sql.shuffle.partitions is already ${count}, so consider raising this stage's own partition count (its repartition(n) or RDD parallelism) or adding a broadcast join`, remediation: [] }
1315
+ : { text: 'consider increasing spark.sql.shuffle.partitions or adding a broadcast join', remediation: [increaseConf('spark.sql.shuffle.partitions')] };
1191
1316
  return {
1192
1317
  type: 'shuffle', stageId: stage.id,
1193
- impactBand: 'info',
1318
+ impactBand: 'info', partitions,
1194
1319
  metric: 'shuffleReadBytes', value: bytes,
1195
- recommendation: `${formatBytes(bytes)} shuffled in this stage: consider increasing spark.sql.shuffle.partitions or adding a broadcast join.`,
1196
- remediation: [increaseConf('spark.sql.shuffle.partitions')],
1320
+ recommendation: `${formatBytes(bytes)} shuffled in this stage: ${fix.text}.`,
1321
+ remediation: fix.remediation,
1197
1322
  };
1198
1323
  },
1199
1324
  estimate(finding, ctx) {
@@ -1225,19 +1350,19 @@ export const DETECTORS = [
1225
1350
  const ratioText = p50 > 0
1226
1351
  ? `${Math.round(max / p50 * 10) / 10}× the median (${formatBytes(p50)})`
1227
1352
  : 'far larger than the median, which is effectively empty';
1228
- const fix = skewJoinFix(ctx.app);
1353
+ const fix = skewFix(stage, ctx, true);
1229
1354
  out.push({
1230
1355
  type: 'partitionSizing', stageId: stage.id, impactBand: 'warning',
1231
- rule: 'shufflePartitionSkew', metric: 'shuffleReadMax', value: max,
1356
+ rule: 'shufflePartitionSkew', origin: fix.origin, metric: 'shuffleReadMax', value: max,
1232
1357
  recommendation: `The largest shuffle partition (${formatBytes(max)}) is ${ratioText}: ${fix.text}.`,
1233
1358
  remediation: fix.remediation,
1234
1359
  });
1235
1360
  }
1236
1361
  if (total >= thresholds.lowParTotalBytes && taskCount <= thresholds.lowParMaxTasks) {
1237
- const fix = lowShuffleParallelismFix(ctx.app, Math.ceil(total / IDEAL_BYTES_PER_PARTITION_TASK));
1362
+ const fix = lowShuffleParallelismFix(stage, ctx.app);
1238
1363
  out.push({
1239
1364
  type: 'partitionSizing', stageId: stage.id, impactBand: 'warning',
1240
- rule: 'lowShuffleParallelism', metric: 'taskCount', value: taskCount,
1365
+ rule: 'lowShuffleParallelism', partitions: fix.partitions, metric: 'taskCount', value: taskCount,
1241
1366
  recommendation: `${Math.round(total / GB * 10) / 10} GB of shuffle spread over only ${taskCount} tasks: ${fix.text}.`,
1242
1367
  remediation: fix.remediation,
1243
1368
  });
@@ -1296,8 +1421,11 @@ export const DETECTORS = [
1296
1421
  const classified = cls === 'skew' || cls === 'volume';
1297
1422
  const mag = computeSpillMagnitude(stage, thresholds);
1298
1423
  const impactBand = 'warning';
1424
+ // shuffle.partitions sizes a shuffle's reduce side: a stage that reads no shuffle gets memory only.
1425
+ const reads = stageReads(stage);
1426
+ const partitionAdvice = reads === 'shuffle';
1299
1427
  return {
1300
- type: 'spill', stageId: stage.id, impactBand,
1428
+ type: 'spill', stageId: stage.id, impactBand, reads,
1301
1429
  spillMagnitude: mag?.magnitude,
1302
1430
  metric: 'memoryBytesSpilled', value: stage.memoryBytesSpilled,
1303
1431
  confidence: classified ? 'medium' : 'low',
@@ -1306,8 +1434,10 @@ export const DETECTORS = [
1306
1434
  : 'Spill cause could not be classified: inspect per-task spill metrics in the Spark UI before acting.',
1307
1435
  recommendation: cls === 'skew'
1308
1436
  ? `${formatBytes(stage.memoryBytesSpilled)} spilled, skew-driven: fix task skew first; adding memory will not help.`
1309
- : `${formatBytes(stage.memoryBytesSpilled)} spilled: raise spark.sql.shuffle.partitions or increase executor memory.`,
1310
- remediation: cls === 'skew' ? [] : [increaseConf('spark.sql.shuffle.partitions'), increaseConf('spark.executor.memory')],
1437
+ : partitionAdvice
1438
+ ? `${formatBytes(stage.memoryBytesSpilled)} spilled: raise spark.sql.shuffle.partitions or increase executor memory.`
1439
+ : `${formatBytes(stage.memoryBytesSpilled)} spilled: this stage reads no shuffle, so increase executor memory or process less data per task.`,
1440
+ remediation: cls === 'skew' ? [] : [...(partitionAdvice ? [increaseConf('spark.sql.shuffle.partitions')] : []), increaseConf('spark.executor.memory')],
1311
1441
  };
1312
1442
  },
1313
1443
  estimate(finding, ctx) {
@@ -1419,7 +1549,7 @@ export const DETECTORS = [
1419
1549
  if (hosts.length >= thresholds.minHosts) {
1420
1550
  const means = hosts.map(h => ({ host: h.host, taskCount: h.taskCount, mean: h.totalDuration / h.taskCount }));
1421
1551
  const sorted = [...means].map(h => h.mean).sort((a, b) => a - b);
1422
- const overallMedian = sorted[Math.floor(sorted.length / 2)];
1552
+ const overallMedian = medianOfSorted(sorted);
1423
1553
  if (overallMedian > 0) {
1424
1554
  for (const h of means) {
1425
1555
  const ratio = h.mean / overallMedian;
@@ -1532,11 +1662,21 @@ export const DETECTORS = [
1532
1662
  const impactBand = durationMinutes >= t.infoMin ? 'info' : null;
1533
1663
  if (!impactBand) return null;
1534
1664
  const value = Math.round(durationMinutes * 10) / 10;
1665
+ // shuffle.partitions and default.parallelism size a shuffle's reduce side: a stage that reads
1666
+ // no shuffle gets the input-partitioning remedy instead.
1667
+ const reads = stageReads(stage);
1668
+ const recommendation = reads === 'shuffle'
1669
+ ? `This stage ran ${value} minutes with no more specific cause flagged: often a partition-count problem, raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.`
1670
+ : reads === 'input'
1671
+ ? `This stage ran ${value} minutes with no more specific cause flagged and reads mostly input files: often too few or too uneven input partitions, so check input file sizes and lower spark.sql.files.maxPartitionBytes, or look for a large per-task data volume driving heavy spill.`
1672
+ : `This stage ran ${value} minutes with no more specific cause flagged and reads neither shuffle nor input files: check what it computes and for a large per-task data volume driving heavy spill.`;
1535
1673
  return {
1536
1674
  type: 'stageSlowness', stageId: stage.id, impactBand,
1537
- metric: 'stageDurationMinutes', value,
1538
- recommendation: `This stage ran ${value} minutes with no more specific cause flagged: often a partition-count problem, raise parallelism via spark.sql.shuffle.partitions or spark.default.parallelism, or check for a large per-task data volume driving heavy shuffle and spill.`,
1539
- remediation: [increaseConf('spark.sql.shuffle.partitions'), increaseConf('spark.default.parallelism')],
1675
+ metric: 'stageDurationMinutes', value, reads,
1676
+ recommendation,
1677
+ remediation: reads === 'shuffle'
1678
+ ? [increaseConf('spark.sql.shuffle.partitions'), increaseConf('spark.default.parallelism')]
1679
+ : reads === 'input' ? [decreaseConf('spark.sql.files.maxPartitionBytes')] : [],
1540
1680
  };
1541
1681
  },
1542
1682
  estimate(finding, ctx) {
@@ -1664,8 +1804,10 @@ export const DETECTORS = [
1664
1804
  const detail = useSpeculativeMetric
1665
1805
  ? `${value} speculative attempt${value === 1 ? '' : 's'} discarded`
1666
1806
  : `${value}% of tasks straggled`;
1807
+ // The skew advice fits only a stage that reads a shuffle feeding a join (skewFix).
1808
+ const fix = skewFix(stage, ctx);
1667
1809
  return {
1668
- type: 'straggler', stageId: stage.id, impactBand,
1810
+ type: 'straggler', stageId: stage.id, impactBand, origin: fix.origin,
1669
1811
  metric: useSpeculativeMetric ? 'speculativeTasks' : 'stragglerShare',
1670
1812
  value,
1671
1813
  unit: useSpeculativeMetric ? 'count' : 'pct',
@@ -1675,7 +1817,8 @@ export const DETECTORS = [
1675
1817
  ? stragglerConfidence(speculativeShare, thresholds.warnPct, thresholds.critPct)
1676
1818
  : stragglerConfidence(stragglerShare, thresholds.shareWarn, thresholds.critPct),
1677
1819
  validationRequired: `Warning needs at least ${shareLabel(thresholds.floorPctWarn)} of run time at stake, critical ${shareLabel(thresholds.floorPctCrit)}.`,
1678
- recommendation: `${detail}: rule out a GC pause or a slow shuffle fetch before assuming a hardware issue; if a skewed key is the real cause, that's a candidate for AQE's skew-join handling.`,
1820
+ recommendation: `${detail}: rule out a GC pause or a slow shuffle fetch before assuming a hardware issue; if uneven data is the cause, ${fix.text}.`,
1821
+ remediation: fix.remediation,
1679
1822
  };
1680
1823
  },
1681
1824
  estimate(finding, ctx) {
@@ -1755,14 +1898,15 @@ export const DETECTORS = [
1755
1898
  if (stageBelowRuntimeFloor(stage, ctx, thresholds.stageFloorPct)) return null;
1756
1899
  if (stage.taskDurationP50 > thresholds.maxP50 || stage.taskDurationP95 > thresholds.maxP95) return null;
1757
1900
  const coalesceTo = Math.max(1, Math.round(stage.taskCount / 10));
1758
- const fix = stage.shuffleReadBytes > 0
1901
+ const reads = stageReads(stage);
1902
+ const fix = reads === 'shuffle'
1759
1903
  ? `lower spark.sql.shuffle.partitions or .coalesce(${coalesceTo})`
1760
1904
  : `.coalesce(${coalesceTo})`;
1761
1905
  return {
1762
- type: 'tinyTask', stageId: stage.id, impactBand: 'info',
1906
+ type: 'tinyTask', stageId: stage.id, impactBand: 'info', reads,
1763
1907
  metric: 'taskDurationP50', value: Math.round(stage.taskDurationP50),
1764
1908
  recommendation: `Many small tasks (${stage.taskCount}, P50 ${Math.round(stage.taskDurationP50)}ms): scheduler overhead may dominate. Try ${fix}.`,
1765
- remediation: stage.shuffleReadBytes > 0 ? [decreaseConf('spark.sql.shuffle.partitions')] : [],
1909
+ remediation: reads === 'shuffle' ? [decreaseConf('spark.sql.shuffle.partitions')] : [],
1766
1910
  };
1767
1911
  },
1768
1912
  estimate(finding, ctx) {
@@ -1839,6 +1983,7 @@ export const DETECTORS = [
1839
1983
  const value = Math.round(gapSeconds);
1840
1984
  return {
1841
1985
  type: 'coldStart', stageId: null, impactBand: 'warning',
1986
+ dynamicAllocation: dynamicAllocationOff(ctx.app) ? 'off' : 'on',
1842
1987
  metric: 'startupGapSeconds', value,
1843
1988
  recommendation: `The first stage waited ${value}s for an executor to become available: keep a warm pool of idle executors, or if using dynamic allocation, raise the minimum/initial executor count so it doesn't scale up from zero.`,
1844
1989
  remediation: dynamicAllocationOff(ctx.app) ? [] : [increaseConf('spark.dynamicAllocation.minExecutors'), increaseConf('spark.dynamicAllocation.initialExecutors')],
@@ -2196,14 +2341,19 @@ export const DETECTORS = [
2196
2341
  if (!impactBand) return null;
2197
2342
 
2198
2343
  const pct = Math.round(shortLivedPct * 100);
2344
+ const daOff = dynamicAllocationOff(app);
2199
2345
  return {
2200
2346
  type: 'autoscalingChurn', stageId: null, impactBand,
2201
2347
  metric: 'shortLivedExecutorPct', value: pct,
2202
2348
  // Raw count behind the percentage, for the impact estimator's startup-overhead figure.
2203
2349
  shortLivedExecutorCount: shortLivedCount,
2204
2350
  confidence: autoscalingChurnConfidence(shortLivedPct, thresholds.warningPct, thresholds.criticalPct),
2205
- recommendation: `${pct}% of executors ran for under 2 minutes before being removed: this looks like wasteful re-provisioning rather than normal scale-down; consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.`,
2206
- remediation: [
2351
+ // No dynamic-allocation property has an effect when the run's conf turns it off (as for coldStart).
2352
+ dynamicAllocation: daOff ? 'off' : 'on',
2353
+ recommendation: daOff
2354
+ ? `${pct}% of executors ran for under 2 minutes before being removed: dynamic allocation is off, so the churn comes from executors lost or preempted, not from autoscaling bounds; check the cluster manager's preemption and executor-loss logs.`
2355
+ : `${pct}% of executors ran for under 2 minutes before being removed: this looks like wasteful re-provisioning rather than normal scale-down; consider raising spark.dynamicAllocation.executorIdleTimeout or widening the minExecutors/maxExecutors bounds to reduce flapping.`,
2356
+ remediation: daOff ? [] : [
2207
2357
  increaseConf('spark.dynamicAllocation.executorIdleTimeout'),
2208
2358
  decreaseConf('spark.dynamicAllocation.minExecutors'),
2209
2359
  increaseConf('spark.dynamicAllocation.maxExecutors'),
@@ -2705,14 +2855,21 @@ export const DETECTORS = [
2705
2855
  || (smaller < broadcastTiers[3] && larger > comparisonTiers[2]);
2706
2856
  if (fires) {
2707
2857
  const contributors = [...boundarySizeContributors(childA), ...boundarySizeContributors(childB)];
2858
+ // A threshold that already admits the smaller side cannot be what stopped the broadcast.
2859
+ const threshold = effectiveBroadcastThreshold(ctx.app);
2860
+ const broadcastThreshold = threshold != null && threshold < 0 ? 'disabled'
2861
+ : threshold != null && threshold > smaller ? 'notLimiting' : 'limits';
2862
+ const raiseAdvice = 'Consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.';
2708
2863
  out.push({
2709
2864
  type: 'underBroadcast', executionId: sqlExec.id, stageIds: unionStageIds(contributors, fallbackStageIds),
2710
2865
  // resolvePlanTree always sets id; safe downstream of it.
2711
2866
  planNodeIds: contributors.map((n) => n.id ).filter(Boolean),
2712
2867
  impactBand: 'info', metric: 'smallerSideBytes', value: smaller,
2713
- largerSideBytes: larger,
2714
- recommendation: `The smaller input to this Sort Merge Join (${formatBytes(smaller)}) is well under the broadcast threshold relative to the larger side (${formatBytes(larger)}): this could have been a broadcast join. Consider a broadcast() hint or raising spark.sql.autoBroadcastJoinThreshold.`,
2715
- remediation: [increaseConf('spark.sql.autoBroadcastJoinThreshold')],
2868
+ largerSideBytes: larger, broadcastThreshold,
2869
+ recommendation: broadcastThreshold === 'notLimiting'
2870
+ ? `The smaller input to this Sort Merge Join (${formatBytes(smaller)}) is under the effective spark.sql.autoBroadcastJoinThreshold (${formatBytes(threshold )}) yet was not broadcast (the larger side is ${formatBytes(larger)}), so the threshold is not what stopped it: missing table statistics or a join type that cannot broadcast usually is. Consider a broadcast() hint or collecting statistics (ANALYZE TABLE).`
2871
+ : `The smaller input to this Sort Merge Join (${formatBytes(smaller)}) is well under the broadcast threshold relative to the larger side (${formatBytes(larger)}): this could have been a broadcast join. ${raiseAdvice}`,
2872
+ remediation: broadcastThreshold === 'notLimiting' ? [] : [increaseConf('spark.sql.autoBroadcastJoinThreshold')],
2716
2873
  });
2717
2874
  }
2718
2875
  }
@@ -2724,17 +2881,23 @@ export const DETECTORS = [
2724
2881
  // (node.stageIds always empty in real data); its child carries the executor-side
2725
2882
  // metrics, so only the child unions in.
2726
2883
  const child = (node.children ?? [])[0];
2727
- const autoBroadcastOff = ctx.app?.config?.['spark.sql.autoBroadcastJoinThreshold']?.trim() === '-1';
2884
+ // A threshold below the broadcast cannot have admitted it, so a hint forced it.
2885
+ const threshold = effectiveBroadcastThreshold(ctx.app);
2886
+ const autoBroadcastOff = threshold != null && threshold < 0;
2887
+ const hintForced = autoBroadcastOff || (threshold != null && m.value > threshold);
2888
+ const broadcastThreshold = autoBroadcastOff ? 'disabled' : hintForced ? 'notLimiting' : 'limits';
2728
2889
  out.push({
2729
2890
  type: 'overBroadcast', executionId: sqlExec.id,
2730
2891
  stageIds: unionStageIds(child ? [child] : [], fallbackStageIds),
2731
2892
  // resolvePlanTree always sets id; safe downstream of it.
2732
2893
  planNodeIds: [node.id ].filter(Boolean),
2733
- impactBand: 'warning', metric: 'broadcastBytes', value: m.value,
2894
+ impactBand: 'warning', metric: 'broadcastBytes', value: m.value, broadcastThreshold,
2734
2895
  recommendation: autoBroadcastOff
2735
2896
  ? `This broadcast (${formatBytes(m.value)}) exceeds the ${binaryThresholdLabel(overBroadcastBytes)} threshold: automatic broadcast is already disabled, so remove the broadcast() hint that forced it.`
2736
- : `This broadcast (${formatBytes(m.value)}) exceeds the ${binaryThresholdLabel(overBroadcastBytes)} threshold: check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.`,
2737
- remediation: autoBroadcastOff ? [] : [decreaseConf('spark.sql.autoBroadcastJoinThreshold')],
2897
+ : hintForced
2898
+ ? `This broadcast (${formatBytes(m.value)}) exceeds the ${binaryThresholdLabel(overBroadcastBytes)} threshold: spark.sql.autoBroadcastJoinThreshold (${formatBytes(threshold )}) is below it, so a broadcast() hint forced it: remove the hint.`
2899
+ : `This broadcast (${formatBytes(m.value)}) exceeds the ${binaryThresholdLabel(overBroadcastBytes)} threshold: check for a misapplied broadcast hint or a misconfigured spark.sql.autoBroadcastJoinThreshold.`,
2900
+ remediation: hintForced ? [] : [decreaseConf('spark.sql.autoBroadcastJoinThreshold')],
2738
2901
  });
2739
2902
  }
2740
2903
  }
@@ -3,6 +3,8 @@
3
3
  Executors are stood up and torn down again before they can do useful work:
4
4
  re-provisioning churn rather than normal scale-down. Raise
5
5
  `spark.dynamicAllocation.executorIdleTimeout`, or widen the
6
- `minExecutors`/`maxExecutors` bounds to reduce flapping. Self-flags a
6
+ `minExecutors`/`maxExecutors` bounds to reduce flapping. With dynamic
7
+ allocation off (`evidence.dynamicAllocation` is `off`) none of those applies:
8
+ the churn is executor loss or preemption, and `remediation` is empty. Self-flags a
7
9
  confidence that scales with how far the short-lived-executor share sits
8
10
  past the threshold.
@@ -3,4 +3,5 @@
3
3
  The first stage waited more than 30 s for an executor. Keep a warm pool of
4
4
  executors, or, with dynamic allocation, raise
5
5
  `spark.dynamicAllocation.minExecutors`/`initialExecutors` so the app doesn't
6
- scale up from zero.
6
+ scale up from zero. With dynamic allocation off (`evidence.dynamicAllocation`
7
+ is `off`) no executor-count property applies and `remediation` is empty.
@@ -11,7 +11,14 @@ this tag:
11
11
  under 3 MB. Compact upstream output, or coalesce before writing.
12
12
  - Under-broadcast: the smaller side of a Sort Merge Join looks well under
13
13
  the broadcast threshold; consider a `broadcast()` hint or raising
14
- `spark.sql.autoBroadcastJoinThreshold`.
14
+ `spark.sql.autoBroadcastJoinThreshold`. When the effective threshold
15
+ (logged, else Spark's 10 MiB) already admits the smaller side
16
+ (`evidence.broadcastThreshold` is `notLimiting`), the threshold is not what
17
+ stopped the broadcast, so `remediation` is empty and the advice is a hint or
18
+ table statistics.
15
19
  - Over-broadcast: a broadcast exceeds the 1 GB threshold; check for a
16
20
  misapplied broadcast hint or a misconfigured
17
- `spark.sql.autoBroadcastJoinThreshold`.
21
+ `spark.sql.autoBroadcastJoinThreshold`. When the effective threshold is
22
+ below the broadcast or auto-broadcast is disabled (`evidence.broadcastThreshold`
23
+ is `notLimiting` or `disabled`), a hint forced it: remove the hint, and
24
+ `remediation` is empty.
@@ -1,5 +1,11 @@
1
1
  ### `SHFL`: Shuffle I/O {#shfl}
2
2
 
3
3
  Tasks move a large amount of intermediate data between stages. Raise
4
- `spark.sql.shuffle.partitions`, or add a broadcast join. Only flagged on
5
- stages that take at least 0.5% of the run.
4
+ `spark.sql.shuffle.partitions`, or add a broadcast join. The partition-count
5
+ advice follows the effective conf (`evidence.partitions`): `raise` when the
6
+ property limits the stage, `sufficient` when the tasks already read a good
7
+ size each, `aqeCoalesced` when AQE merged the partitions (lower
8
+ `spark.sql.adaptive.advisoryPartitionSizeInBytes`), and `ownPartitioning` when
9
+ the property is already high enough (the stage's own `repartition(n)` or RDD
10
+ parallelism limits it). Only flagged on stages that take at least 0.5% of the
11
+ run.
@@ -1,8 +1,14 @@
1
1
  ### `SKEW`: Task skew {#skew}
2
2
 
3
3
  A small number of tasks take much longer than their peers in the same
4
- stage. For join-driven skew, enable AQE skew-join handling
5
- (`spark.sql.adaptive.skewJoin.enabled`); otherwise salt the key or
6
- repartition on a better key. Flagged when P95 task time (the longest task,
7
- on a stage with fewer than 20 tasks) exceeds 3x the median and the
8
- recoverable tail is at least 0.5% of the run.
4
+ stage. The fix depends on what the stage reads, which the finding's
5
+ `evidence.origin` records. A stage that reads a shuffle feeding a join
6
+ (`shuffleJoin`) gets AQE skew-join handling
7
+ (`spark.sql.adaptive.skewJoin.enabled`), unless the run's effective conf
8
+ already has it; otherwise salt the key or repartition on a better key. A
9
+ stage that reads files with uneven sizes (`inputScan`) gets compaction of
10
+ small files or a lower `spark.sql.files.maxPartitionBytes`. Any other stage
11
+ (`other`) gets the salting advice and no conf. Flagged when P95 task time
12
+ (the longest task, on a stage with fewer than 20 tasks) exceeds 3x the median
13
+ and the recoverable tail is at least 0.5% of the run. The median is the
14
+ textbook one: on an even task count, the mean of the two middle values.
@@ -1,6 +1,10 @@
1
1
  ### `SLOW`: Stage slowness {#slow}
2
2
 
3
3
  A stage ran for 15 minutes or more and no slow host was flagged on it. It
4
- can appear alongside other findings on the same stage. Often a partition-count problem: raise parallelism via
4
+ can appear alongside other findings on the same stage. On a stage that reads a
5
+ shuffle, often a partition-count problem: raise parallelism via
5
6
  `spark.sql.shuffle.partitions` or `spark.default.parallelism`, or check for a
6
- large per-task data volume driving heavy shuffle and spill.
7
+ large per-task data volume driving heavy shuffle and spill. On a stage that
8
+ reads input files and no shuffle, check input file sizes and lower
9
+ `spark.sql.files.maxPartitionBytes`. `evidence.reads` says which case
10
+ applied (`shuffle`, `input` or `other`).
@@ -4,4 +4,7 @@ Tasks are writing data out of memory, which slows execution. Two spill
4
4
  patterns get flagged differently: skew spill, where a few heavy tasks spill
5
5
  while most don't (rebalance partitioning), and volume spill, where most
6
6
  tasks spill because the data genuinely exceeds available memory (add
7
- partitions or executor memory). Only flagged on stages that take at least 0.5% of the run.
7
+ partitions or executor memory). `spark.sql.shuffle.partitions` is suggested
8
+ only for a stage that reads a shuffle; `evidence.reads` says what the stage
9
+ reads (`shuffle`, `input` or `other`). Only flagged on stages that take at
10
+ least 0.5% of the run.
@@ -1,6 +1,8 @@
1
1
  ### `STRAG`: Straggler tasks {#strag}
2
2
 
3
3
  A few tasks run much slower than the rest of their stage. Rule out a GC
4
- pause or a slow shuffle fetch before assuming a hardware issue; if a skewed
5
- key is the real cause, that's a candidate for AQE's skew-join handling. Only
6
- flagged on stages that take at least 0.5% of the run.
4
+ pause or a slow shuffle fetch before assuming a hardware issue. If uneven
5
+ data is the cause, the advice is the one a skew finding gives for the same
6
+ stage (`evidence.origin`, see `SKEW`): AQE skew-join handling for a shuffle
7
+ feeding a join, file sizes for a scan, salting otherwise. Only flagged on
8
+ stages that take at least 0.5% of the run.
@@ -1,5 +1,6 @@
1
1
  ### `TINY`: Tiny tasks {#tiny}
2
2
 
3
3
  Many very short tasks add scheduling overhead out of proportion to the work
4
- each one does. Repartition to fewer, larger tasks. Only flagged on stages that
5
- take at least 0.5% of the run.
4
+ each one does. Repartition to fewer, larger tasks; a stage that reads a shuffle
5
+ (`evidence.reads` is `shuffle`) can also lower `spark.sql.shuffle.partitions`.
6
+ Only flagged on stages that take at least 0.5% of the run.