Переглянути джерело

feat: support mmap settings (#835)

* dialog UI

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update settings

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update schema page

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* update

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* finish

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

* fix typo

Signed-off-by: ryjiang <jiangruiyi@gmail.com>

---------

Signed-off-by: ryjiang <jiangruiyi@gmail.com>
ryjiang 2 тижнів тому
батько
коміт
abc8fd8a07

+ 2 - 0
client/src/components/customDialog/DialogTemplate.tsx

@@ -58,6 +58,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
   handleClose,
   handleCancel,
   confirmLabel,
+  confirmDisabledTooltip,
   handleConfirm,
   confirmDisabled,
   children,
@@ -125,6 +126,7 @@ const DialogTemplate: FC<DialogContainerProps> = ({
                   disabled={confirming || confirmDisabled}
                   name="confirm"
                   className="btn"
+                  tooltip={confirmDisabled ? confirmDisabledTooltip : ''}
                 >
                   {confirming ? <CircularProgress size={16} /> : confirm}
                 </CustomButton>

+ 1 - 0
client/src/components/customDialog/Types.ts

@@ -32,6 +32,7 @@ export type DialogContainerProps = {
   handleCancel?: () => void;
   handleConfirm: (param?: any) => void;
   confirmDisabled?: boolean;
+  confirmDisabledTooltip?: string;
   showActions?: boolean;
   showCancel?: boolean;
   leftActions?: ReactElement;

+ 13 - 0
client/src/http/Collection.service.ts

@@ -10,6 +10,7 @@ import {
   ResStatus,
   DescribeIndexRes,
   IndexObject,
+  MmapChanges,
 } from '@server/types';
 import { ManageRequestMethods } from '@/consts';
 import type {
@@ -153,4 +154,16 @@ export class CollectionService extends BaseModel {
       data: params,
     });
   }
+
+  static async updateMmap(
+    collectionName: string,
+    params: MmapChanges[]
+  ): Promise<ResStatus> {
+    const path = `/collections/${collectionName}/mmap`;
+
+    return super.update<ResStatus>({
+      path,
+      data: params,
+    });
+  }
 }

+ 1 - 0
client/src/i18n/cn/button.ts

@@ -46,6 +46,7 @@ const btnTrans = {
   downloadChart: '下载图表',
   editDefaultValue: '编辑默认值',
   viewData: '查看数据',
+  mmapSetting: '内存映射(MMap)设置',
 
   // tips
   loadColTooltip: '加载Collection',

+ 9 - 2
client/src/i18n/cn/collection.ts

@@ -11,12 +11,16 @@ const collectionTrans = {
   aliasTooltip: '请选择一个Collection创建别名',
   collection: 'Collection',
   entities: 'Entities',
+  mmapEnabled: '内存映射(MMap)',
+  mmapSettings: 'MMap设置',
+  collectionMMapSettingsLabel: '全局原始数据 MMap设置',
+  rawData: '全局内存映射(MMap)的原始数据配置',
 
   // table
   id: 'ID',
   name: '名称',
-  features: '特性',
-  nameTip: 'Collection名称',
+  features: 'Collection 特性',
+  nameTip: 'Collection 名称',
   status: '状态',
   desc: '描述',
   createdTime: '创建时间',
@@ -147,6 +151,9 @@ const collectionTrans = {
   consistencyEventuallyTooltip:
     '没有保证读写的顺序,副本最终会在没有进一步写操作的情况下收敛到相同的状态。',
   noVectorIndexTooltip: '请保证所有向量列都有索引。',
+  mmapTooltip: `内存映射文件允许将原始数据和索引文件直接映射到内存中。此功能提高了内存效率,尤其是在可用内存稀缺但无法完全加载数据的情况下。`,
+  mmapFieldSettingDisabledTooltip: `此设置已禁用,因为Collection级别的 mmap 配置覆盖了字段级别的设置。`,
+  mmapCollectionNotReleasedTooltip: `Collection没有释放, 无法更新mmap配置。`,
 
   clickToLoad: '点击加载collection',
   clickToRelease: '点击释放collection',

+ 2 - 2
client/src/i18n/cn/dialog.ts

@@ -1,5 +1,3 @@
-import { Edit } from '@mui/icons-material';
-
 const dialogTrans = {
   value: '值',
   deleteTipAction: '输入',
@@ -16,6 +14,7 @@ const dialogTrans = {
   editEntityTitle: `编辑 Entity`,
   modifyReplicaTitle: `修改 {{type}} 的副本`,
   editAnalyzerTitle: `编辑分析器`,
+  manageMmapTitle: `管理 {{type}} 的内存映射 (MMap) 设置`,
 
   loadContent: `您正在尝试加载带有数据的 {{type}}。只有已加载的 {{type}} 可以被搜索。`,
   releaseContent: `您正在尝试发布带有数据的 {{type}}。请注意,数据将不再可用于搜索。`,
@@ -33,6 +32,7 @@ const dialogTrans = {
   resetPropertyInfo: '您确定要重置属性吗?',
   editEntityInfo: `注意:编辑PrimayKey字段将会创建一个新的实体。`,
   editAnalyzerInfo: `分析器以JSON格式定义,请参考milvus.io 了解<a href='https://milvus.io/docs/analyzer-overview.md' target='_blank'>更多信息</a>。`,
+  editMmapInfo: `在Milvus中,内存映射文件允许将原始数据和索引文件直接映射到内存中。此功能提高了内存效率,特别是在可用内存稀缺但无法完全加载数据的情况下。 请参考milvus.io 了解<a href='https://milvus.io/docs/mmap.md' target='_blank'>更多信息</a>。 <br /><br />注意:mmap设置仅在加载Collection后生效。`,
 };
 
 export default dialogTrans;

+ 1 - 0
client/src/i18n/cn/index.ts

@@ -8,6 +8,7 @@ const indexTrans = {
 
   create: '创建索引',
   index: '索引',
+  noIndex: '没有索引',
   desc: '描述',
   indexName: '索引名称',
 

+ 1 - 0
client/src/i18n/cn/success.ts

@@ -11,6 +11,7 @@ const successTrans = {
   reset: `{{name}}重置成功。`,
   modifyReplica: `{{name}}修改副本数量成功。`,
   passwordChanged: `密码更新成功`,
+  updateMmap: `{{name}}的Mmap设置已更新。`,
 };
 
 export default successTrans;

+ 1 - 0
client/src/i18n/en/button.ts

@@ -46,6 +46,7 @@ const btnTrans = {
   downloadChart: 'Download Chart',
   EditDefaultValue: 'Edit Default Value',
   viewData: 'View Data',
+  mmapSetting: 'MMap Settings',
 
   // tips
   loadColTooltip: 'Load Collection',

+ 8 - 1
client/src/i18n/en/collection.ts

@@ -11,11 +11,15 @@ const collectionTrans = {
   aliasTooltip: 'Please select one collection to create alias',
   collection: 'Collection',
   entities: 'entities',
+  mmapEnabled: 'MMap-Enabled',
+  mmapSettings: 'MMap Settings',
+  collectionMMapSettingsLabel: 'Collection Raw Data MMap Settings',
+  rawData: 'Raw Data',
 
   // table
   id: 'ID',
   name: 'Name',
-  features: 'Features',
+  features: 'Collection Features',
   nameTip: 'Collection Name',
   status: 'Status',
   desc: 'Description',
@@ -150,6 +154,9 @@ const collectionTrans = {
   consistencySessionTooltip: `It ensures that all data writes can be immediately perceived in reads during the same session.`,
   consistencyEventuallyTooltip: `There is no guaranteed order of reads and writes, and replicas eventually converge to the same state given that no further write operations are done.`,
   noVectorIndexTooltip: `Please make sure all vector fields have index.`,
+  mmapTooltip: `Memory-mapped files allow for direct mapping of raw data and indexes into memory. This feature enhances memory efficiency, particularly in situations where available memory is scarce but complete data loading is infeasible.`,
+  mmapFieldSettingDisabledTooltip: `This setting is disabled because collection-level mmap configuration overrides field-level settings.`,
+  mmapCollectionNotReleasedTooltip: `Collection is not released, please release it first.`,
 
   clickToLoad: 'Click to load the collection.',
   clickToRelease: 'Click to release the collection.',

+ 2 - 0
client/src/i18n/en/dialog.ts

@@ -14,6 +14,7 @@ const dialogTrans = {
   editEntityTitle: `Edit Entity(JSON)`,
   editAnalyzerTitle: `Edit Analyzer`,
   modifyReplicaTitle: `Modify replica for {{type}}`,
+  manageMmapTitle: `Manage MMap settings for {{type}}`,
 
   loadContent: `You are trying to load a {{type}} with data. Only loaded {{type}} can be searched.`,
   releaseContent: `You are trying to release {{type}} with data. Please be aware that the data will no longer be available for search.`,
@@ -31,6 +32,7 @@ const dialogTrans = {
   resetPropertyInfo: `Are you sure you want to reset the property?`,
   editEntityInfo: `NOTE: Edit PrimayKey field will create a new entity.`,
   editAnalyzerInfo: `Analyzer is defined in JSON format, please refer to milvus.io for <a href='https://milvus.io/docs/analyzer-overview.md' target='_blank'>more information</a>.`,
+  editMmapInfo: `In Milvus, memory-mapped files allow for direct mapping of raw data and indexes into memory. This feature enhances memory efficiency, particularly in situations where available memory is scarce but complete data loading is infeasible. Please refer to milvus.io for <a href='https://milvus.io/docs/mmap.md' target='_blank'>more information</a>. <br /><br />Note: The mmap settings will only take effect after the collection is loaded.`,
 };
 
 export default dialogTrans;

+ 1 - 0
client/src/i18n/en/index.ts

@@ -8,6 +8,7 @@ const indexTrans = {
 
   create: 'Create Index',
   index: 'Index',
+  noIndex: 'No Index',
   desc: 'Description',
   indexName: 'Index Name',
 

+ 1 - 0
client/src/i18n/en/success.ts

@@ -11,6 +11,7 @@ const successTrans = {
   reset: `{{name}} has been reset.`,
   modifyReplica: `Replica number for {{name}} has been modified.`,
   passwordChanged: `Password updated successfully`,
+  updateMmap: `Mmap settings for {{name}} have been updated.`,
 };
 
 export default successTrans;

+ 92 - 33
client/src/pages/databases/collections/schema/Schema.tsx

@@ -15,6 +15,7 @@ import { useStyles } from './Styles';
 import CustomIconButton from '@/components/customButton/CustomIconButton';
 import LoadCollectionDialog from '@/pages/dialogs/LoadCollectionDialog';
 import RenameCollectionDialog from '@/pages/dialogs/RenameCollectionDialog';
+import EditMmapDialog from '@/pages/dialogs/EditMmapDialog';
 import DropCollectionDialog from '@/pages/dialogs/DropCollectionDialog';
 import CopyButton from '@/components/advancedSearch/CopyButton';
 import RefreshButton from '@/components/customButton/RefreshButton';
@@ -47,6 +48,11 @@ const Overview = () => {
     c => c.collection_name === collectionName
   );
 
+  // check if collection is mmap enabled
+  const isCollectionMmapEnabled = collection?.properties!.some((p: any) => {
+    return p.key === 'mmap.enabled' && p.value === 'true';
+  });
+
   // get fields
   const fields = collection?.schema?.fields || [];
 
@@ -55,7 +61,7 @@ const Overview = () => {
       id: 'name',
       align: 'left',
       disablePadding: true,
-      formatter(f) {
+      formatter(f: FieldObject) {
         return (
           <div className={classes.nameWrapper}>
             {f.name}
@@ -83,7 +89,7 @@ const Overview = () => {
             ) : null}
             {findKeyValue(f.type_params, 'enable_analyzer') ? (
               <Tooltip
-                title={findKeyValue(f.type_params, 'analyzer_params')}
+                title={findKeyValue(f.type_params, 'analyzer_params') as string}
                 arrow
               >
                 <Chip
@@ -107,6 +113,32 @@ const Overview = () => {
                 />
               </Tooltip>
             ) : null}
+            {findKeyValue(f.type_params, 'mmap.enabled') === 'true' ||
+            isCollectionMmapEnabled ? (
+              <Tooltip title={collectionTrans('mmapTooltip')} arrow>
+                <Chip
+                  className={classes.chip}
+                  size="small"
+                  label={collectionTrans('mmapEnabled')}
+                  onClick={() => {
+                    setDialog({
+                      open: true,
+                      type: 'custom',
+                      params: {
+                        component: (
+                          <EditMmapDialog
+                            collection={collection!}
+                            cb={async () => {
+                              fetchCollection(collectionName);
+                            }}
+                          />
+                        ),
+                      },
+                    });
+                  }}
+                />
+              </Tooltip>
+            ) : null}
 
             {f.function ? (
               <Tooltip title={JSON.stringify(f.function)} arrow>
@@ -448,46 +480,73 @@ const Overview = () => {
               <Typography variant="h5">
                 {collectionTrans('features')}
               </Typography>
-              <Typography variant="h6">
-                {isAutoIDEnabled ? (
-                  <Chip
-                    className={`${classes.chip} ${classes.featureChip}`}
-                    label={collectionTrans('autoId')}
-                    size="small"
-                  />
-                ) : null}
+              {isAutoIDEnabled ? (
+                <Chip
+                  className={`${classes.chip} ${classes.featureChip}`}
+                  label={collectionTrans('autoId')}
+                  size="small"
+                />
+              ) : null}
+              <Tooltip
+                title={
+                  consistencyTooltipsMap[collection.consistency_level!] || ''
+                }
+                placement="top"
+                arrow
+              >
+                <Chip
+                  className={`${classes.chip} ${classes.featureChip}`}
+                  label={`${collectionTrans('consistency')}: ${
+                    collection.consistency_level
+                  }`}
+                  size="small"
+                />
+              </Tooltip>
+
+              {collection &&
+              collection.schema &&
+              collection.schema.enable_dynamic_field ? (
                 <Tooltip
-                  title={
-                    consistencyTooltipsMap[collection.consistency_level!] || ''
-                  }
+                  title={collectionTrans('dynamicSchemaTooltip')}
                   placement="top"
                   arrow
                 >
                   <Chip
-                    className={`${classes.chip} ${classes.featureChip}`}
-                    label={`${collectionTrans('consistency')}: ${
-                      collection.consistency_level
-                    }`}
+                    className={`${classes.chip}`}
+                    label={collectionTrans('dynamicSchema')}
                     size="small"
                   />
                 </Tooltip>
+              ) : null}
 
-                {collection &&
-                collection.schema &&
-                collection.schema.enable_dynamic_field ? (
-                  <Tooltip
-                    title={collectionTrans('dynamicSchemaTooltip')}
-                    placement="top"
-                    arrow
-                  >
-                    <Chip
-                      className={`${classes.chip}`}
-                      label={collectionTrans('dynamicSchema')}
-                      size="small"
-                    />
-                  </Tooltip>
-                ) : null}
-              </Typography>
+              <Tooltip
+                title={collectionTrans('mmapTooltip')}
+                placement="top"
+                arrow
+              >
+                <Chip
+                  className={classes.chip}
+                  label={collectionTrans('mmapSettings')}
+                  size="small"
+                  onDelete={async () => {
+                    setDialog({
+                      open: true,
+                      type: 'custom',
+                      params: {
+                        component: (
+                          <EditMmapDialog
+                            collection={collection}
+                            cb={async () => {
+                              fetchCollection(collectionName);
+                            }}
+                          />
+                        ),
+                      },
+                    });
+                  }}
+                  deleteIcon={<Icons.settings />}
+                />
+              </Tooltip>
             </div>
           </div>
         </section>

+ 11 - 1
client/src/pages/databases/collections/schema/Styles.tsx

@@ -51,6 +51,7 @@ export const useStyles = makeStyles((theme: Theme) => ({
     flexBasis: 'calc(33.333% - 12px)',
     height: '100%',
     minWidth: 200,
+    flexWrap: 'wrap',
   },
   icon: {
     fontSize: '20px',
@@ -64,6 +65,14 @@ export const useStyles = makeStyles((theme: Theme) => ({
       color: theme.palette.text.primary,
     },
   },
+  mmapExtraBtn: {
+    position: 'relative',
+    top: -2,
+    '& svg': {
+      width: 15,
+      color: theme.palette.text.primary,
+    },
+  },
   smallIcon: {
     fontSize: '13px',
     marginLeft: theme.spacing(0.5),
@@ -83,7 +92,8 @@ export const useStyles = makeStyles((theme: Theme) => ({
     color: theme.palette.text.primary,
     cursor: 'normal',
     marginRight: 4,
-    marginLeft: 4,
+    marginBottom: 4,
+    marginTop: 4,
   },
   dataTypeChip: {
     backgroundColor: theme.palette.background.grey,

+ 295 - 0
client/src/pages/dialogs/EditMmapDialog.tsx

@@ -0,0 +1,295 @@
+import { FC, useContext, useState } from 'react';
+import {
+  Typography,
+  Theme,
+  Table,
+  TableBody,
+  TableCell,
+  TableHead,
+  TableRow,
+  Switch,
+  Box,
+  Tooltip,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { rootContext, dataContext } from '@/context';
+import DialogTemplate from '@/components/customDialog/DialogTemplate';
+import { CollectionService } from '@/http';
+import { makeStyles } from '@mui/styles';
+import { CollectionObject, MmapChanges } from '@server/types';
+import { findKeyValue } from '@/utils';
+
+const useStyles = makeStyles((theme: Theme) => ({
+  desc: {
+    margin: '8px 0 16px 0',
+    fontSize: '14px',
+  },
+  collection: {
+    margin: '8px 0 0 0',
+    fontSize: '13px',
+    fontWeight: 800,
+  },
+  dialog: {
+    minWidth: '600px',
+    maxWidth: '800px',
+  },
+  table: {
+    marginTop: theme.spacing(0),
+    '& th': {
+      fontWeight: 'bold',
+    },
+  },
+  fieldName: {
+    display: 'flex',
+    flexDirection: 'column',
+  },
+  fieldType: {
+    color: theme.palette.text.secondary,
+    fontSize: '0.8rem',
+  },
+}));
+
+interface EditMmapProps {
+  collection: CollectionObject;
+  cb?: () => void;
+}
+
+interface FieldMmapState {
+  id: string;
+  name: string;
+  dataType: string;
+  indexName: string;
+  rawMmapEnabled: boolean;
+  indexMmapEnabled: boolean;
+  hasIndex: boolean;
+}
+
+const EditMmapDialog: FC<EditMmapProps> = props => {
+  const { collection, cb } = props;
+
+  const classes = useStyles();
+
+  const { handleCloseDialog, openSnackBar } = useContext(rootContext);
+  const { setCollectionProperty } = useContext(dataContext);
+
+  const { t: dialogTrans } = useTranslation('dialog');
+  const { t: btnTrans } = useTranslation('btn');
+  const { t: successTrans } = useTranslation('success');
+  const { t: collectionTrans } = useTranslation('collection');
+  const { t: indexTrans } = useTranslation('index');
+
+  // Current state of all fields (initial state)
+  const [fieldsState, setFieldsState] = useState<FieldMmapState[]>(() => {
+    return collection.schema!.fields.map(field => ({
+      id: field.fieldID as string,
+      name: field.name,
+      indexName: field.index?.index_name,
+      dataType: field.data_type,
+      rawMmapEnabled:
+        findKeyValue(field.type_params, 'mmap.enabled') === 'true',
+      indexMmapEnabled: field.index
+        ? findKeyValue(field.index!.params, 'mmap.enabled') === 'true'
+        : false,
+      hasIndex: !!field.index,
+    }));
+  });
+
+  const isCollectionMmapEnabled = collection?.properties!.some((p: any) => {
+    return p.key === 'mmap.enabled' && p.value === 'true';
+  });
+
+  // Track changes that will be applied on confirm
+  const [pendingChanges, setPendingChanges] = useState<MmapChanges[]>([]);
+  const [pendingCollectionMmap, setPendingCollectionMmap] = useState<boolean>(
+    isCollectionMmapEnabled
+  );
+
+  const handleRawMmapChange = (fieldId: string, enabled: boolean) => {
+    const field = fieldsState.find(f => f.id === fieldId)!;
+
+    setFieldsState(prev =>
+      prev.map(f => (f.id === fieldId ? { ...f, rawMmapEnabled: enabled } : f))
+    );
+
+    setPendingChanges(prev => {
+      // Remove any existing changes for this field
+      const filtered = prev.filter(change => change.fieldName !== field.name);
+      // Add the new change
+      return [
+        ...filtered,
+        {
+          fieldName: field.name,
+          indexName: field.indexName,
+          rawMmapEnabled: enabled,
+          // Preserve existing index change if it exists
+          indexMmapEnabled: prev.find(c => c.fieldName === field.name)
+            ?.indexMmapEnabled,
+        },
+      ];
+    });
+  };
+
+  const handleIndexMmapChange = (fieldId: string, enabled: boolean) => {
+    const field = fieldsState.find(f => f.id === fieldId)!;
+
+    setFieldsState(prev =>
+      prev.map(f =>
+        f.id === fieldId ? { ...f, indexMmapEnabled: enabled } : f
+      )
+    );
+
+    setPendingChanges(prev => {
+      // Remove any existing changes for this field
+      const filtered = prev.filter(change => change.fieldName !== field.name);
+      // Add the new change
+      return [
+        ...filtered,
+        {
+          fieldName: field.name,
+          indexName: field.indexName,
+          // Preserve existing raw change if it exists
+          rawMmapEnabled: prev.find(c => c.fieldName === field.name)
+            ?.rawMmapEnabled,
+          indexMmapEnabled: enabled,
+        },
+      ];
+    });
+  };
+
+  const handleConfirm = async () => {
+    // Make the API call to update mmap settings
+
+    try {
+      if (pendingCollectionMmap !== isCollectionMmapEnabled) {
+        await setCollectionProperty(
+          collection.collection_name,
+          'mmap.enabled',
+          pendingCollectionMmap
+        );
+      }
+      if (pendingChanges.length > 0) {
+        const res = await CollectionService.updateMmap(
+          collection.collection_name,
+          pendingChanges
+        );
+
+        if (res.error_code === 'Success') {
+          openSnackBar(
+            successTrans('updateMmap', { name: collection.collection_name }),
+            'success'
+          );
+        } else {
+          openSnackBar(res.reason, 'error');
+        }
+      }
+    } catch (error) {
+      console.error('Error updating mmap settings:', error);
+    } finally {
+      cb && (await cb());
+      handleCloseDialog();
+    }
+  };
+
+  const notReleased = collection.loaded;
+  const noChange =
+    pendingChanges.length === 0 &&
+    isCollectionMmapEnabled === pendingCollectionMmap;
+
+  return (
+    <DialogTemplate
+      dialogClass={classes.dialog}
+      title={dialogTrans('manageMmapTitle', {
+        type: collection.collection_name,
+      })}
+      handleClose={handleCloseDialog}
+      children={
+        <Box>
+          <p
+            className={classes.desc}
+            dangerouslySetInnerHTML={{ __html: dialogTrans('editMmapInfo') }}
+          ></p>
+          <div className={classes.collection}>
+            {collectionTrans('collectionMMapSettingsLabel')}
+            <Switch
+              checked={pendingCollectionMmap}
+              onChange={e => {
+                setPendingCollectionMmap(e.target.checked);
+              }}
+              color="primary"
+            />
+          </div>
+          <br />
+          <Table className={classes.table}>
+            <TableHead>
+              <TableRow>
+                <TableCell>{collectionTrans('fieldName')}</TableCell>
+                <TableCell>{collectionTrans('rawData')}</TableCell>
+                <TableCell>{indexTrans('index')}</TableCell>
+              </TableRow>
+            </TableHead>
+            <TableBody>
+              {fieldsState.map(field => (
+                <TableRow key={field.id}>
+                  <TableCell>
+                    <Box className={classes.fieldName}>
+                      <span>{field.name}</span>
+                      <span className={classes.fieldType}>
+                        {field.dataType}
+                      </span>
+                    </Box>
+                  </TableCell>
+                  <TableCell>
+                    <Tooltip
+                      title={
+                        pendingCollectionMmap
+                          ? collectionTrans('mmapFieldSettingDisabledTooltip')
+                          : ''
+                      }
+                      placement="top"
+                    >
+                      <span>
+                        <Switch
+                          checked={
+                            pendingCollectionMmap || field.rawMmapEnabled
+                          }
+                          onChange={e =>
+                            handleRawMmapChange(field.id, e.target.checked)
+                          }
+                          disabled={pendingCollectionMmap}
+                          color="primary"
+                        />
+                      </span>
+                    </Tooltip>
+                  </TableCell>
+                  <TableCell>
+                    {field.hasIndex ? (
+                      <Switch
+                        checked={field.indexMmapEnabled}
+                        onChange={e =>
+                          handleIndexMmapChange(field.id, e.target.checked)
+                        }
+                        color="primary"
+                      />
+                    ) : (
+                      <Typography variant="body2" color="textSecondary">
+                        {indexTrans('noIndex')}
+                      </Typography>
+                    )}
+                  </TableCell>
+                </TableRow>
+              ))}
+            </TableBody>
+          </Table>
+        </Box>
+      }
+      confirmDisabled={noChange || notReleased}
+      confirmLabel={btnTrans('confirm')}
+      confirmDisabledTooltip={
+        notReleased ? collectionTrans('mmapCollectionNotReleasedTooltip') : ''
+      }
+      handleConfirm={handleConfirm}
+    />
+  );
+};
+
+export default EditMmapDialog;

+ 5 - 3
client/src/utils/Common.ts

@@ -1,6 +1,6 @@
 import { saveAs } from 'file-saver';
 import { Parser } from '@json2csv/plainjs';
-import type { KeyValuePair } from '@server/types';
+import type { KeyValuePair, TypeParamKey, TypeParam } from '@server/types';
 
 export const copyToCommand = (
   value: string,
@@ -97,5 +97,7 @@ export const saveCsvAs = (csvObj: any, as: string) => {
   }
 };
 
-export const findKeyValue = (obj: KeyValuePair[], key: string) =>
-  obj.find(v => v.key === key)?.value;
+export const findKeyValue = (
+  obj: KeyValuePair<TypeParamKey | string, TypeParam>[],
+  key: string
+) => obj.find(v => v.key === key)?.value;

+ 58 - 1
server/src/collections/collections.controller.ts

@@ -5,6 +5,8 @@ import {
   LoadCollectionReq,
   IndexType,
   MetricType,
+  ResStatus,
+  ErrorCode,
 } from '@zilliz/milvus2-sdk-node';
 import {
   CreateAliasDto,
@@ -16,6 +18,7 @@ import {
   DuplicateCollectionDto,
   ManageIndexDto,
 } from './dto';
+import { MmapChanges } from '../types';
 
 export class CollectionController {
   private collectionsService: CollectionsService;
@@ -135,6 +138,9 @@ export class CollectionController {
     // compact
     this.router.put('/:name/compact', this.compact.bind(this));
 
+    // mmap settings
+    this.router.put('/:name/mmap', this.updateMmapSettings.bind(this));
+
     return this.router;
   }
 
@@ -261,7 +267,7 @@ export class CollectionController {
     const name = req.params?.name;
     const data = req.body;
     try {
-      const result = await this.collectionsService.alterCollection(
+      const result = await this.collectionsService.alterCollectionProperties(
         req.clientId,
         {
           collection_name: name,
@@ -644,4 +650,55 @@ export class CollectionController {
       next(error);
     }
   }
+
+  async updateMmapSettings(
+    req: Request<{ name: string }, ResStatus, MmapChanges[]>,
+    res: Response,
+    next: NextFunction
+  ) {
+    const name = req.params?.name;
+    const data = req.body;
+    try {
+      const promises = [];
+      // loop through all fields and update the mmap settings, if it is raw, update fieldproperties, if it is index, update index
+      for (const field of data) {
+        if (typeof field.rawMmapEnabled !== 'undefined') {
+          promises.push(
+            await this.collectionsService.alterCollectionFieldProperties(
+              req.clientId,
+              {
+                collection_name: name,
+                field_name: field.fieldName,
+                properties: {
+                  'mmap.enabled': field.rawMmapEnabled,
+                },
+              }
+            )
+          );
+        }
+        if (typeof field.indexMmapEnabled !== 'undefined') {
+          promises.push(
+            this.collectionsService.alterIndex(req.clientId, {
+              collection_name: name,
+              index_name: field.fieldName,
+              params: {
+                'mmap.enabled': field.indexMmapEnabled,
+              },
+            })
+          );
+        }
+      }
+      const results = await Promise.all(promises);
+
+      // loop through results, if any of them is not success, return the promise
+      for (const result of results) {
+        if (result.error_code !== ErrorCode.SUCCESS) {
+          return result;
+        }
+      }
+      res.send(results[0]);
+    } catch (error) {
+      next(error);
+    }
+  }
 }

+ 25 - 10
server/src/collections/collections.service.ts

@@ -29,6 +29,8 @@ import {
   SearchSimpleReq,
   LoadState,
   ErrorCode,
+  AlterCollectionFieldPropertiesReq,
+  AlterIndexReq,
 } from '@zilliz/milvus2-sdk-node';
 import { Parser } from '@json2csv/plainjs';
 import {
@@ -204,17 +206,23 @@ export class CollectionsService {
     return newCollection[0];
   }
 
-  async alterCollection(clientId: string, data: AlterCollectionReq) {
+  async alterCollectionProperties(clientId: string, data: AlterCollectionReq) {
     const { milvusClient } = clientCache.get(clientId);
-    const res = await milvusClient.alterCollectionProperties(data);
+    return await milvusClient.alterCollectionProperties(data);
+  }
 
-    const newCollection = (await this.getAllCollections(
-      clientId,
-      [data.collection_name],
-      data.db_name
-    )) as CollectionFullObject[];
+  async alterCollectionFieldProperties(
+    clientId: string,
+    data: AlterCollectionFieldPropertiesReq
+  ) {
+    const { milvusClient } = clientCache.get(clientId);
 
-    return newCollection[0];
+    return await milvusClient.alterCollectionFieldProperties(data);
+  }
+
+  async alterIndex(clientId: string, data: AlterIndexReq) {
+    const { milvusClient } = clientCache.get(clientId);
+    return await milvusClient.alterIndexProperties(data);
   }
 
   async dropCollection(clientId: string, data: DropCollectionReq) {
@@ -783,7 +791,10 @@ export class CollectionsService {
 
       // copy index.params withouth index_type and metric_type and params
       const indexParams = index.params.filter(
-        p => p.key !== 'index_type' && p.key !== 'metric_type' && p.key !== 'params'
+        p =>
+          p.key !== 'index_type' &&
+          p.key !== 'metric_type' &&
+          p.key !== 'params'
       );
       // get index parameter pairs
       const paramsJSONstring = findKeyValue(index.params, 'params'); // params is a json string
@@ -792,7 +803,11 @@ export class CollectionsService {
           getKeyValueListFromJsonString(paramsJSONstring as string)) ||
         [];
 
-      index.indexParameterPairs = [...metricTypePair, ...indexParams, ...params];
+      index.indexParameterPairs = [
+        ...metricTypePair,
+        ...indexParams,
+        ...params,
+      ];
     });
 
     // Return the response from the Milvus SDK's describeIndex function

+ 7 - 0
server/src/types/collections.type.ts

@@ -120,3 +120,10 @@ export type DatabaseObject = {
   properties: KeyValuePair[];
   collections: string[];
 };
+
+export interface MmapChanges {
+  fieldName: string;
+  indexName: string;
+  rawMmapEnabled?: boolean;
+  indexMmapEnabled?: boolean;
+}

+ 2 - 0
server/src/types/index.ts

@@ -4,6 +4,8 @@ export {
   MilvusClient,
   ResStatus,
   RerankerObj,
+  TypeParamKey,
+  TypeParam,
 } from '@zilliz/milvus2-sdk-node';
 
 export * from './collections.type';