diff --git a/keyserver/src/creators/update-creator.js b/keyserver/src/creators/update-creator.js
--- a/keyserver/src/creators/update-creator.js
+++ b/keyserver/src/creators/update-creator.js
@@ -9,7 +9,7 @@
   rawUpdateInfoFromUpdateData,
 } from 'lib/shared/update-utils.js';
 import {
-  type RawEntryInfo,
+  type RawEntryInfos,
   type FetchEntryInfosBase,
   type CalendarQuery,
   defaultCalendarQuery,
@@ -526,7 +526,7 @@
   threadInfosResult: FetchThreadInfosResult,
   messageInfosResult: ?FetchMessageInfosResult,
   calendarResult: ?FetchEntryInfosBase,
-  entryInfosResult: ?$ReadOnlyArray<RawEntryInfo>,
+  entryInfosResult: ?RawEntryInfos,
   currentUserInfoResult: ?OldLoggedInUserInfo | LoggedInUserInfo,
 };
 async function updateInfosFromRawUpdateInfos(
@@ -634,9 +634,7 @@
       });
     } else if (rawUpdateInfo.type === updateTypes.UPDATE_ENTRY) {
       invariant(entryInfosResult, 'should be set');
-      const entryInfo = entryInfosResult.find(
-        candidate => candidate.id === rawUpdateInfo.entryID,
-      );
+      const entryInfo = entryInfosResult[rawUpdateInfo.entryID];
       if (!entryInfo) {
         console.warn(
           "failed to hydrate updateTypes.UPDATE_ENTRY because we couldn't " +
diff --git a/keyserver/src/fetchers/entry-fetchers.js b/keyserver/src/fetchers/entry-fetchers.js
--- a/keyserver/src/fetchers/entry-fetchers.js
+++ b/keyserver/src/fetchers/entry-fetchers.js
@@ -14,6 +14,7 @@
   FetchEntryInfosBase,
   DeltaEntryInfosResponse,
   RawEntryInfo,
+  RawEntryInfos,
 } from 'lib/types/entry-types.js';
 import { calendarThreadFilterTypes } from 'lib/types/filter-types.js';
 import type { HistoryRevisionInfo } from 'lib/types/history-types.js';
@@ -38,10 +39,7 @@
   entryID: string,
 ): Promise<?RawEntryInfo> {
   const results = await fetchEntryInfosByID(viewer, new Set([entryID]));
-  if (results.length === 0) {
-    return null;
-  }
-  return results[0];
+  return results[entryID] ?? null;
 }
 
 function rawEntryInfoFromRow(row: Object): RawEntryInfo {
@@ -62,9 +60,9 @@
 async function fetchEntryInfosByID(
   viewer: Viewer,
   entryIDs: $ReadOnlySet<string>,
-): Promise<RawEntryInfo[]> {
+): Promise<RawEntryInfos> {
   if (entryIDs.size === 0) {
-    return [];
+    return {};
   }
   const viewerID = viewer.id;
   const query = SQL`
@@ -78,7 +76,11 @@
       JSON_EXTRACT(m.permissions, ${visPermissionExtractString}) IS TRUE
   `;
   const [result] = await dbQuery(query);
-  return result.map(rawEntryInfoFromRow);
+  const entryInfos = {};
+  for (const row of result) {
+    entryInfos[row.id.toString()] = rawEntryInfoFromRow(row);
+  }
+  return entryInfos;
 }
 
 function sqlConditionForCalendarQuery(
diff --git a/keyserver/src/socket/session-utils.js b/keyserver/src/socket/session-utils.js
--- a/keyserver/src/socket/session-utils.js
+++ b/keyserver/src/socket/session-utils.js
@@ -521,12 +521,14 @@
       stateChanges.rawThreadInfos.push(threadInfo);
     } else if (key.startsWith('entryInfo|')) {
       const [, entryID] = key.split('|');
-      const rawEntryInfos = fetchedData.entriesResult
-        ? fetchedData.entriesResult.rawEntryInfos
-        : fetchedData.entryInfos;
-      const entryInfo = rawEntryInfos.find(
-        candidate => candidate.id === entryID,
-      );
+      let entryInfo;
+      if (fetchedData.entriesResult) {
+        entryInfo = fetchedData.entriesResult.rawEntryInfos.find(
+          candidate => candidate.id === entryID,
+        );
+      } else {
+        entryInfo = fetchedData.entryInfos[entryID];
+      }
       if (!entryInfo) {
         if (!stateChanges.deleteEntryIDs) {
           stateChanges.deleteEntryIDs = [];
diff --git a/lib/types/entry-types.js b/lib/types/entry-types.js
--- a/lib/types/entry-types.js
+++ b/lib/types/entry-types.js
@@ -33,6 +33,9 @@
   creatorID: string,
   deleted: boolean,
 };
+export type RawEntryInfos = {
+  +[id: string]: RawEntryInfo,
+};
 export const rawEntryInfoValidator: TInterface<RawEntryInfo> =
   tShape<RawEntryInfo>({
     id: t.maybe(tID),