diff --git a/dev/tool/src/elastic.ts b/dev/tool/src/elastic.ts
index f9b757a2f2..b22e971632 100644
--- a/dev/tool/src/elastic.ts
+++ b/dev/tool/src/elastic.ts
@@ -211,9 +211,14 @@ async function restoreElastic (mongoUrl: string, dbName: string, minio: Client,
     const isCollectionCreateTx = (tx: Tx): boolean =>
       tx._class === core.class.TxCollectionCUD &&
       (tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxCreateDoc
+    const isMixinTx = (tx: Tx): boolean =>
+      tx._class === core.class.TxMixin ||
+      (tx._class === core.class.TxCollectionCUD &&
+        (tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxMixin)
 
     const createTxes = data.filter((tx) => isCreateTx(tx))
     const collectionTxes = data.filter((tx) => isCollectionCreateTx(tx))
+    const mixinTxes = data.filter((tx) => isMixinTx(tx))
     const removedDocument = new Set<Ref<Doc>>()
 
     const startCreate = Date.now()
@@ -287,6 +292,31 @@ async function restoreElastic (mongoUrl: string, dbName: string, minio: Client,
     )
     console.log('replay elastic collection transactions done', Date.now() - startCollection)
 
+    const startMixin = Date.now()
+    console.log('replay elastic mixin transactions', mixinTxes.length)
+    await Promise.all(
+      mixinTxes.map(async (tx) => {
+        try {
+          let deleted = false
+          if (tx._class === core.class.TxMixin) {
+            deleted = removedDocument.has((tx as TxMixin<Doc, Doc>).objectId)
+          }
+          if (
+            tx._class === core.class.TxCollectionCUD &&
+            (tx as TxCollectionCUD<Doc, AttachedDoc>).tx._class === core.class.TxMixin
+          ) {
+            deleted = removedDocument.has((tx as TxCollectionCUD<Doc, AttachedDoc>).tx.objectId)
+          }
+          if (!deleted) {
+            await tool.storage.tx(metricsCtx, tx)
+          }
+        } catch (err: any) {
+          console.error('failed to replay tx', tx, err.message)
+        }
+      })
+    )
+    console.log('replay elastic mixin transactions done', Date.now() - startMixin)
+
     let apos = 0
     if (await minio.bucketExists(dbName)) {
       const minioObjects = await listMinioObjects(minio, dbName)
diff --git a/server/core/src/fulltext.ts b/server/core/src/fulltext.ts
index 1ab039e700..91d8950fea 100644
--- a/server/core/src/fulltext.ts
+++ b/server/core/src/fulltext.ts
@@ -165,28 +165,7 @@ export class FullTextIndex implements WithFind {
       }
     }
     const resultIds = Array.from(getResultIds(ids, _id))
-    if (options?.limit === undefined) {
-      return await this.getResult(ctx, _class, resultIds, mainQuery as DocumentQuery<T>, options)
-    } else {
-      const result: T[] = []
-      const size = options.limit
-      let start = 0
-      while (true) {
-        const ids = resultIds.slice(start, start + size)
-        const res = await this.getResult(ctx, _class, ids, mainQuery as DocumentQuery<T>, options)
-        result.push(...res)
-        if (result.length >= size || res.length < size) {
-          break
-        }
-        start += size
-      }
-      if (result.length >= size) {
-        const total = await this.getResult(ctx, _class, resultIds, mainQuery as DocumentQuery<T>, { limit: 1 })
-
-        return toFindResult(result, total.total)
-      }
-      return toFindResult(result)
-    }
+    return await this.getResult(ctx, _class, resultIds, mainQuery as DocumentQuery<T>, options)
   }
 
   private async getResult<T extends Doc>(
@@ -200,14 +179,31 @@ export class FullTextIndex implements WithFind {
     for (let index = 0; index < ids.length; index++) {
       orderMap.set(ids[index], index)
     }
+    const { sort, ...otherOptions } = options ?? {}
+    if (options?.lookup !== undefined && options.limit !== undefined) {
+      const resIds = await this.dbStorage.findAll(
+        ctx,
+        _class,
+        { _id: { $in: ids }, ...mainQuery },
+        { projection: { _id: 1 } }
+      )
+      const total = resIds.total
+      resIds.sort((a, b) => (orderMap.get(a._id) ?? 0) - (orderMap.get(b._id) ?? 0))
+      const targetIds = resIds.slice(0, options.limit).map((p) => p._id)
 
-    const result = await this.dbStorage.findAll(ctx, _class, { _id: { $in: ids }, ...mainQuery }, options)
+      const result = await this.dbStorage.findAll(ctx, _class, { _id: { $in: targetIds }, ...mainQuery }, otherOptions)
+      result.sort((a, b) => (orderMap.get(a._id) ?? 0) - (orderMap.get(b._id) ?? 0))
 
-    const total = result.total
+      return toFindResult(result, total)
+    } else {
+      const result = await this.dbStorage.findAll(ctx, _class, { _id: { $in: ids }, ...mainQuery }, otherOptions)
 
-    result.sort((a, b) => (orderMap.get(a._id) ?? 0) - (orderMap.get(b._id) ?? 0))
+      const total = result.total
 
-    return toFindResult(result, total)
+      result.sort((a, b) => (orderMap.get(a._id) ?? 0) - (orderMap.get(b._id) ?? 0))
+
+      return toFindResult(result, total)
+    }
   }
 
   private getFullTextAttributes (clazz: Ref<Class<Obj>>, parentDoc?: Doc): AnyAttribute[] {
diff --git a/server/elastic/src/adapter.ts b/server/elastic/src/adapter.ts
index 5822d243df..2663f14a10 100644
--- a/server/elastic/src/adapter.ts
+++ b/server/elastic/src/adapter.ts
@@ -47,7 +47,7 @@ class ElasticAdapter implements FullTextAdapter {
           {
             terms: {
               _class: _classes.map((c) => c.toLowerCase()),
-              boost: 10.0
+              boost: 50.0
             }
           }
         ],