Browse Source

perf(perf): Memoize SQL query formatting (#60919)

Our frontend SQL query formatter has [poor performance at higher
percentiles](https://sentry.sentry.io/performance/summary/spans/function:cbeaa3ae40853db6/?project=11276&query=&statsPeriod=7d&transaction=%2Fperformance%2Fdatabase%2Fspans%2Fspan%2F%2A).
To add insult to injury, the parsing isn't memoized at all, and each
re-render incurs the cost. Introducing memoization at the formatter
level didn't sit right with me. Queries have high cardinality, and I
don't want to keep a giant memoizer around. Instead, I'm using `useMemo`
in the components that use it.

As a next step I'll find some profiles and see why this function is
slow. I'm expecting a recursion problem, so I might need to adjust the
syntax? Unclear!

- Memoize SQL formatting in span summary page
- Memoize SQL formatting in database landing page
George Gritsouk 1 year ago
parent
commit
f326980999

+ 8 - 2
static/app/views/starfish/components/spanDescription.tsx

@@ -1,3 +1,4 @@
+import {useMemo} from 'react';
 import styled from '@emotion/styled';
 
 import Feature from 'sentry/components/acl/feature';
@@ -29,13 +30,18 @@ export function SpanDescription({span, project}: Props) {
   return <WordBreak>{span[SpanMetricsField.SPAN_DESCRIPTION]}</WordBreak>;
 }
 
+const formatter = new SQLishFormatter();
+
 function DatabaseSpanDescription({span, project}: Props) {
-  const formatter = new SQLishFormatter();
+  const rawDescription = span[SpanMetricsField.SPAN_DESCRIPTION];
+  const formatterDescription = useMemo(() => {
+    return formatter.toString(rawDescription);
+  }, [rawDescription]);
 
   return (
     <Frame>
       <CodeSnippet language="sql" isRounded={false}>
-        {formatter.toString(span[SpanMetricsField.SPAN_DESCRIPTION])}
+        {formatterDescription}
       </CodeSnippet>
 
       <Feature features={['organizations:performance-database-view-query-source']}>

+ 15 - 9
static/app/views/starfish/components/tableCells/spanDescriptionCell.tsx

@@ -1,4 +1,4 @@
-import {Fragment} from 'react';
+import {Fragment, useMemo} from 'react';
 import styled from '@emotion/styled';
 
 import {Hovercard} from 'sentry/components/hovercard';
@@ -12,23 +12,31 @@ import {SQLishFormatter} from 'sentry/views/starfish/utils/sqlish/SQLishFormatte
 const formatter = new SQLishFormatter();
 
 interface Props {
+  description: string;
   moduleName: ModuleName;
   projectId: number;
-  description?: string;
   endpoint?: string;
   endpointMethod?: string;
   group?: string;
 }
 
 export function SpanDescriptionCell({
-  description,
+  description: rawDescription,
   group,
   moduleName,
   endpoint,
   endpointMethod,
   projectId,
 }: Props) {
-  if (!description) {
+  const formatterDescription = useMemo(() => {
+    if (moduleName !== ModuleName.DB) {
+      return rawDescription;
+    }
+
+    return formatter.toSimpleMarkup(rawDescription);
+  }, [moduleName, rawDescription]);
+
+  if (!rawDescription) {
     return NULL_DESCRIPTION;
   }
 
@@ -38,9 +46,7 @@ export function SpanDescriptionCell({
       projectId={projectId}
       endpoint={endpoint}
       endpointMethod={endpointMethod}
-      description={
-        moduleName === ModuleName.DB ? formatter.toSimpleMarkup(description) : description
-      }
+      description={formatterDescription}
     />
   );
 
@@ -52,7 +58,7 @@ export function SpanDescriptionCell({
           body={
             <FullSpanDescription
               group={group}
-              shortDescription={description}
+              shortDescription={rawDescription}
               language="sql"
             />
           }
@@ -73,7 +79,7 @@ export function SpanDescriptionCell({
               <TitleWrapper>{t('Example')}</TitleWrapper>
               <FullSpanDescription
                 group={group}
-                shortDescription={description}
+                shortDescription={rawDescription}
                 language="http"
               />
             </Fragment>