Browse Source

pref: improve search page render performance

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 1 week ago
parent
commit
283db00dd2

+ 124 - 78
client/src/pages/databases/collections/search/Search.tsx

@@ -1,4 +1,11 @@
-import { useState, useMemo, ChangeEvent, useCallback } from 'react';
+import {
+  useState,
+  useMemo,
+  ChangeEvent,
+  useCallback,
+  useEffect,
+  useRef,
+} from 'react';
 import { Typography, AccordionSummary, Checkbox } from '@mui/material';
 import { useTranslation } from 'react-i18next';
 import { DataService, CollectionService } from '@/http';
@@ -75,15 +82,45 @@ const Search = (props: CollectionDataProps) => {
   const [tableLoading, setTableLoading] = useState<boolean>();
   const [highlightField, setHighlightField] = useState<string>('');
   const [explorerOpen, setExplorerOpen] = useState<boolean>(false);
+  const [localFilterValue, setLocalFilterValue] = useState<string>('');
+
+  // Use ref to track searchParams changes without causing re-renders
+  const searchParamsRef = useRef(searchParams);
+  searchParamsRef.current = searchParams;
+
+  // Memoize collection to avoid unnecessary re-renders
+  const memoizedCollection = useMemo(
+    () => collection,
+    [collection?.collection_name]
+  );
+
+  // Memoize selected fields to avoid recalculation
+  const selectedFields = useMemo(
+    () => searchParams.searchParams.filter(s => s.selected),
+    [searchParams.searchParams]
+  );
+
+  // Memoize enablePartitionsFilter
+  const enablePartitionsFilter = useMemo(
+    () => !collection.schema.enablePartitionKey,
+    [collection.schema.enablePartitionKey]
+  );
 
   // translations
   const { t: searchTrans } = useTranslation('search');
   const { t: btnTrans } = useTranslation('btn');
 
-  // UI functions
+  // Sync local filter value with searchParams on mount and when searchParams change
+  useEffect(() => {
+    if (searchParams?.globalParams?.filter !== undefined) {
+      setLocalFilterValue(searchParams.globalParams.filter);
+    }
+  }, [searchParams?.globalParams?.filter]);
+
+  // UI functions - optimized with better dependencies
   const handleExpand = useCallback(
     (panel: string) => (event: ChangeEvent<{}>, expanded: boolean) => {
-      const s = cloneObj(searchParams);
+      const s = cloneObj(searchParamsRef.current);
       const target = s.searchParams.find((sp: SearchSingleParams) => {
         return sp.field.name === panel;
       });
@@ -93,54 +130,54 @@ const Search = (props: CollectionDataProps) => {
         setSearchParams({ ...s });
       }
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
   const handleSelect = useCallback(
     (panel: string) => (event: ChangeEvent<{}>) => {
-      const s = cloneObj(searchParams) as SearchParamsType;
+      const s = cloneObj(searchParamsRef.current) as SearchParamsType;
       const target = s.searchParams.find(sp => {
         return sp.field.name === panel;
       });
       if (target) {
         target.selected = !target.selected;
-
         setSearchParams({ ...s });
       }
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
   // update search params
   const updateSearchParamCallback = useCallback(
     (updates: SearchSingleParams, index: number) => {
+      const currentParams = searchParamsRef.current;
       if (
         JSON.stringify(updates) !==
-        JSON.stringify(searchParams.searchParams[index].params)
+        JSON.stringify(currentParams.searchParams[index].params)
       ) {
-        const s = cloneObj(searchParams);
+        const s = cloneObj(currentParams);
         // update the searchParams
         s.searchParams[index].params = updates;
         setSearchParams({ ...s });
       }
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
   // generate random vectors
   const genRandomVectors = useCallback(() => {
-    const s = cloneObj(searchParams) as SearchParamsType;
+    const s = cloneObj(searchParamsRef.current) as SearchParamsType;
     s.searchParams.forEach((sp: SearchSingleParams) => {
       sp.data = generateVectorsByField(sp.field) as any;
     });
 
     setSearchParams({ ...s });
-  }, [JSON.stringify(searchParams)]);
+  }, [setSearchParams]);
 
   // on vector input change, update the search params
   const onSearchInputChange = useCallback(
     (anns_field: string, value: string) => {
-      const s = cloneObj(searchParams) as SearchParamsType;
+      const s = cloneObj(searchParamsRef.current) as SearchParamsType;
       const target = s.searchParams.find((sp: SearchSingleParams) => {
         return sp.anns_field === anns_field;
       });
@@ -150,36 +187,29 @@ const Search = (props: CollectionDataProps) => {
         setSearchParams({ ...s });
       }
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
-  // on filter change
-  const onFilterChange = useCallback(
-    (value: string) => {
-      const s = cloneObj(searchParams) as SearchParamsType;
-      s.globalParams.filter = value;
-      setSearchParams({ ...s });
-
-      return s;
-    },
-    [JSON.stringify(searchParams)]
-  );
+  // on filter change - only update local state, not searchParams
+  const onFilterChange = useCallback((value: string) => {
+    setLocalFilterValue(value);
+  }, []);
 
   // on partitions change
   const onPartitionsChange = useCallback(
     (value: any) => {
-      const s = cloneObj(searchParams) as SearchParamsType;
+      const s = cloneObj(searchParamsRef.current) as SearchParamsType;
       s.partitions = value;
       setSearchParams({ ...s });
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
   // set search result
   const setSearchResult = useCallback(
     (props: VectorSearchResults) => {
       const { results, latency } = props;
-      const s = cloneObj(searchParams) as SearchParamsType;
+      const s = cloneObj(searchParamsRef.current) as SearchParamsType;
       s.searchResult = results;
       s.searchLatency = latency;
       const newGraphData = formatMilvusData(
@@ -191,18 +221,25 @@ const Search = (props: CollectionDataProps) => {
 
       setSearchParams({ ...s });
     },
-    [JSON.stringify(searchParams)]
+    [setSearchParams]
   );
 
   // execute search
   const onSearchClicked = useCallback(async () => {
-    const params = buildSearchParams(searchParams);
+    // Create a copy of current searchParams and update with current filter value
+    const s = cloneObj(searchParamsRef.current) as SearchParamsType;
+    s.globalParams.filter = localFilterValue;
+
+    // Update searchParams for future use
+    setSearchParams({ ...s });
+
+    const params = buildSearchParams(s);
     setExplorerOpen(false);
 
     setTableLoading(true);
     try {
       const res = await DataService.vectorSearchData(
-        searchParams.collection.collection_name,
+        s.collection.collection_name,
         params
       );
 
@@ -211,12 +248,12 @@ const Search = (props: CollectionDataProps) => {
     } catch (err) {
       setTableLoading(false);
     }
-  }, [JSON.stringify(searchParams)]);
+  }, [localFilterValue, setSearchParams, setSearchResult]);
 
   // execute explore
-  const onExploreClicked = () => {
+  const onExploreClicked = useCallback(() => {
     setExplorerOpen(explorerOpen => !explorerOpen);
-  };
+  }, []);
 
   const onNodeClicked = useCallback(
     async (node: GraphNode) => {
@@ -226,8 +263,8 @@ const Search = (props: CollectionDataProps) => {
       setExplorerOpen(true);
       setTableLoading(false);
 
-      const s = cloneObj(searchParams);
-      const params = cloneObj(buildSearchParams(searchParams));
+      const s = cloneObj(searchParamsRef.current);
+      const params = cloneObj(buildSearchParams(searchParamsRef.current));
 
       try {
         const query = await CollectionService.queryData(collectionName, {
@@ -255,7 +292,7 @@ const Search = (props: CollectionDataProps) => {
         console.log('err', err);
       }
     },
-    [JSON.stringify(searchParams)]
+    [collectionName, setSearchParams]
   );
 
   const searchResultMemo = useSearchResult(
@@ -279,18 +316,20 @@ const Search = (props: CollectionDataProps) => {
 
   // reset
   const onResetClicked = useCallback(() => {
-    const s = cloneObj(searchParams) as SearchParamsType;
+    const s = cloneObj(searchParamsRef.current) as SearchParamsType;
     s.searchResult = null;
 
     setSearchParams({ ...s });
     setCurrentPage(0);
-  }, [JSON.stringify(searchParams), setCurrentPage]);
+    setLocalFilterValue(''); // Also reset local filter value
+  }, [setSearchParams, setCurrentPage]);
 
   const onExplorerResetClicked = useCallback(() => {
     onSearchClicked();
     onExploreClicked();
-  }, [onSearchClicked, onSearchClicked]);
+  }, [onSearchClicked, onExploreClicked]);
 
+  // Memoize colDefinitions with better dependencies
   const colDefinitions: ColDefinitionsType[] = useMemo(() => {
     if (!searchParams || !searchParams.collection) {
       return [];
@@ -325,10 +364,15 @@ const Search = (props: CollectionDataProps) => {
               label: key === DYNAMIC_FIELD ? searchTrans('dynamicFields') : key,
               needCopy: key !== 'score',
               headerFormatter: v => {
-                return <CollectionColHeader def={v} collection={collection} />;
+                return (
+                  <CollectionColHeader
+                    def={v}
+                    collection={memoizedCollection}
+                  />
+                );
               },
               formatter(_: any, cellData: any) {
-                const field = collection.schema.fields.find(
+                const field = memoizedCollection.schema.fields.find(
                   f => f.name === key
                 );
 
@@ -343,15 +387,38 @@ const Search = (props: CollectionDataProps) => {
           })
       : [];
   }, [
-    JSON.stringify({
-      searchParams,
-    }),
+    searchParams?.searchResult,
+    searchParams?.globalParams?.output_fields,
+    searchTrans,
+    memoizedCollection,
   ]);
 
+  // Memoize disable search logic
+  const { disableSearch, disableSearchTooltip } = useMemo(() => {
+    let disableSearch = false;
+    let disableSearchTooltip = '';
+
+    if (selectedFields.length === 0) {
+      disableSearch = true;
+      disableSearchTooltip = searchTrans('noSelectedVectorField');
+    }
+
+    const noDataInSelected = selectedFields.some(s => s.data === '');
+    if (noDataInSelected) {
+      disableSearch = true;
+      disableSearchTooltip = searchTrans('noVectorToSearch');
+    }
+
+    return { disableSearch, disableSearchTooltip };
+  }, [selectedFields, searchTrans]);
+
   // methods
-  const handlePageChange = (e: any, page: number) => {
-    handleCurrentPage(page);
-  };
+  const handlePageChange = useCallback(
+    (e: any, page: number) => {
+      handleCurrentPage(page);
+    },
+    [handleCurrentPage]
+  );
 
   // collection is not found or collection full object is not ready
   if (
@@ -375,27 +442,6 @@ const Search = (props: CollectionDataProps) => {
     );
   }
 
-  // disable search button
-  let disableSearch = false;
-  let disableSearchTooltip = '';
-  // has selected vector fields
-  const selectedFields = searchParams.searchParams.filter(s => s.selected);
-
-  if (selectedFields.length === 0) {
-    disableSearch = true;
-    disableSearchTooltip = searchTrans('noSelectedVectorField');
-  }
-  // has vector data to search
-  const noDataInSelected = selectedFields.some(s => s.data === '');
-
-  if (noDataInSelected) {
-    disableSearch = true;
-    disableSearchTooltip = searchTrans('noVectorToSearch');
-  }
-
-  // enable partition filter
-  const enablePartitionsFilter = !collection.schema.enablePartitionKey;
-
   return (
     <SearchRoot>
       {collection && (
@@ -448,7 +494,7 @@ const Search = (props: CollectionDataProps) => {
                       <VectorInputBox
                         searchParams={s}
                         onChange={onSearchInputChange}
-                        collection={collection}
+                        collection={memoizedCollection}
                         type={field.is_function_output ? 'text' : 'vector'}
                       />
 
@@ -486,8 +532,9 @@ const Search = (props: CollectionDataProps) => {
                 }}
                 searchParams={searchParams}
                 handleFormChange={(params: any) => {
-                  searchParams.globalParams = params;
-                  setSearchParams({ ...searchParams });
+                  const s = cloneObj(searchParamsRef.current);
+                  s.globalParams = params;
+                  setSearchParams({ ...s });
                 }}
               />
 
@@ -526,7 +573,7 @@ const Search = (props: CollectionDataProps) => {
             <Toolbar>
               <div className="left">
                 <OptimizedInput
-                  value={searchParams.globalParams.filter}
+                  value={localFilterValue}
                   onChange={onFilterChange}
                   onKeyDown={(e: any) => {
                     if (e.key === 'Enter') {
@@ -535,17 +582,16 @@ const Search = (props: CollectionDataProps) => {
                     }
                   }}
                   disabled={explorerOpen}
-                  fields={collection.schema.scalarFields}
+                  fields={memoizedCollection.schema.scalarFields}
                   onSubmit={(expression: string) => {
-                    onFilterChange(expression);
+                    setLocalFilterValue(expression);
+                    // Don't immediately update searchParams, wait for search
                   }}
                 />
               </div>
               <div className="right">
                 <CustomButton
-                  onClick={() => {
-                    onExploreClicked();
-                  }}
+                  onClick={onExploreClicked}
                   size="small"
                   disabled={
                     !searchParams.searchResult ||

+ 0 - 1
client/src/pages/databases/collections/search/SearchGlobalParams.tsx

@@ -199,7 +199,6 @@ const SearchGlobalParams = (props: SearchGlobalProps) => {
                     }),
                   },
                 ],
-                defaultValue: 60,
                 value: searchGlobalParams.rrfParams!.k,
               }}
               checkValid={() => true}