diff --git a/bench/wtperf/runners/update-btree.wtperf b/bench/wtperf/runners/update-btree.wtperf
index efd6cc8b0..0c12d5c1a 100644
--- a/bench/wtperf/runners/update-btree.wtperf
+++ b/bench/wtperf/runners/update-btree.wtperf
@@ -1,8 +1,8 @@
 # wtperf options file: btree with inserts/updates, in memory
-conn_config="cache_size=1G"
+conn_config="cache_size=10G"
 table_config="type=file"
-icount=5000000
+icount=50000
 report_interval=5
 run_time=120
 populate_threads=1
-threads=((count=2,inserts=1),(count=2,reads=1),(count=2,updates=1))
+threads=((count=20,updates=1),(count=1,reads=1,ops_per_txn=200000))
diff --git a/src/btree/row_modify.c b/src/btree/row_modify.c
index 1e2dbc129..3998cdaca 100644
--- a/src/btree/row_modify.c
+++ b/src/btree/row_modify.c
@@ -342,11 +342,11 @@ __wt_update_obsolete_check(
 {
     WT_PAGE *page;
     WT_TXN_GLOBAL *txn_global;
-    WT_UPDATE *first, *next;
+    WT_UPDATE *first, *next, *prev_upd;
     size_t size;
     u_int count;
 
-    next = NULL;
+    next = prev_upd = NULL;
     page = cbt->ref->page;
     txn_global = &S2C(session)->txn_global;
 
@@ -364,7 +364,7 @@ __wt_update_obsolete_check(
      *
      * Only updates with globally visible, self-contained data can terminate update chains.
      */
-    for (first = NULL, count = 0; upd != NULL; upd = upd->next, count++) {
+    for (first = NULL, count = 0; upd != NULL; prev_upd = upd, upd = upd->next, count++) {
         if (upd->txnid == WT_TXN_ABORTED)
             continue;
 
@@ -418,15 +418,11 @@ __wt_update_obsolete_check(
     }
 
     /*
-     * Force evict a page when there are more than WT_THOUSAND updates to a single item. Increasing
-     * the minSnapshotHistoryWindowInSeconds to 300 introduced a performance regression in which the
-     * average number of updates on a single item was approximately 1000 in write-heavy workloads.
-     * This is why we use WT_THOUSAND here.
+     * The update chain was truncated using a compare and swap. No other threads can be
+     * accessing the updates now. Release the page lock before proceeding with freeing
+     * the updates so other things can make changes to entries on the page.
      */
-    if (count > WT_THOUSAND) {
-        WT_STAT_CONN_INCR(session, cache_eviction_force_long_update_list);
-        __wt_page_evict_soon(session, cbt->ref);
-    }
+    WT_PAGE_UNLOCK(session, page);
 
     if (next != NULL)
         __wt_free_update_list(session, &next);
@@ -436,11 +432,21 @@ __wt_update_obsolete_check(
          * moved forwards.
          */
         if (count > 20) {
-            page->modify->obsolete_check_txn = txn_global->last_running;
-            if (txn_global->has_pinned_timestamp)
-                page->modify->obsolete_check_timestamp = txn_global->pinned_timestamp;
+            /*
+             * At this stage the previous update should correspond to the oldest update on the chain,
+             * save the time information saved in that update to decide if future obsolete checks
+             * will be useful.
+             * NOTE: This is imprecise, the obsolete IDs are saved per-page, and this check was of
+             * a single update chain (other update chains might have older or newer data).
+             */
+            if (prev_upd != NULL) {
+                page->modify->obsolete_check_txn = prev_upd->txnid;
+                page->modify->obsolete_check_timestamp = prev_upd->start_ts;
+            } else {
+                page->modify->obsolete_check_txn = txn_global->last_running;
+                if (txn_global->has_pinned_timestamp)
+                    page->modify->obsolete_check_timestamp = txn_global->pinned_timestamp;
+            }
         }
     }
-
-    WT_PAGE_UNLOCK(session, page);
 }
diff --git a/src/include/btmem.h b/src/include/btmem.h
index 492c9b9e3..9ed8826d8 100644
--- a/src/include/btmem.h
+++ b/src/include/btmem.h
@@ -312,7 +312,8 @@ struct __wt_page_modify {
     uint64_t last_oldest_id;
 #endif
 
-    /* Avoid checking for obsolete updates during checkpoints. */
+    /* Avoid checking for obsolete updates unnecessarily. */
+    uint64_t obsolete_check_skip_count;
     uint64_t obsolete_check_txn;
     wt_timestamp_t obsolete_check_timestamp;
 
diff --git a/src/include/serial_inline.h b/src/include/serial_inline.h
index 99302070b..c145ec60d 100644
--- a/src/include/serial_inline.h
+++ b/src/include/serial_inline.h
@@ -236,6 +236,69 @@ __wt_insert_serial(WT_SESSION_IMPL *session, WT_PAGE *page, WT_INSERT_HEAD *ins_
     return (0);
 }
 
+
+/*
+ * __wt_update_chain_all_visible --
+ *     Scan an update chain, checking to see if there could be obsolete entries. Done without
+ *     holding a page lock - so needs to be careful to stop when things could become obsolete
+ *     in case another thread is freeing obsolete entries at the same time.
+ *     TODO: This is based on a loop in obsolete check, before taking these code changes the
+ *     two should probably be merged together.
+ */
+static inline bool
+__wt_update_chain_all_visible(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_UPDATE *upd)
+{
+    bool obsolete;
+    uint32_t count;
+
+    obsolete = false;
+    /*
+     * Loop through the update chain checking for any obsolete updates.
+     */
+    for (count = 0; upd != NULL; upd = upd->next, count++) {
+        if (upd->txnid == WT_TXN_ABORTED)
+            continue;
+
+        /*
+         * WiredTiger internal operations such as Rollback to stable and prepare transaction
+         * rollback adds a globally visible tombstone to the update chain to remove the entire key.
+         * Treating these globally visible tombstones as obsolete and trimming update list can cause
+         * problems if the update chain is getting accessed somewhere. To avoid this problem, skip
+         * these globally visible tombstones from the update obsolete check were generated from
+         * prepare transaction rollback and not from RTS, because there are no concurrent operations
+         * run in parallel to the RTS to be affected.
+         */
+        if (upd->txnid == WT_TXN_NONE && upd->start_ts == WT_TS_NONE &&
+          upd->type == WT_UPDATE_TOMBSTONE && upd->next != NULL &&
+          upd->next->txnid == WT_TXN_ABORTED && upd->next->prepare_state == WT_PREPARE_INPROGRESS)
+            continue;
+
+        /*
+         * An update can be obsolete if:
+         * * A newer update is visible to all readers in the system (hence the next NULL check)
+         * * The prior update was full (not a modify that is built on earlier updates)
+         * * It isn't tracking an update that is scheduled for removal from the history store.
+         */
+        if (__wt_txn_upd_visible_all(session, upd) && upd->next != NULL &&
+            !F_ISSET(upd, WT_UPDATE_TO_DELETE_FROM_HS) && WT_UPDATE_DATA_VALUE(upd) && upd->next != NULL) {
+            obsolete = true;
+            break;
+        }
+    }
+
+    /*
+     * Flag a page for forced eviction when there are more than a thousand updates to a single item.
+     * Workloads that use a long window of history can generate long chains of updates that don't
+     * become obsolete. Traversing those long chains in memory becomes inefficient - so schedule
+     * them to be written out from cache.
+     */
+    if (count > WT_THOUSAND) {
+        WT_STAT_CONN_INCR(session, cache_eviction_force_long_update_list);
+        __wt_page_evict_soon(session, cbt->ref);
+    }
+    return (!obsolete);
+}
+
 /*
  * __wt_update_serial --
  *     Update a row or column-store entry.
@@ -294,17 +357,20 @@ __wt_update_serial(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_PAGE *page
         return (0);
 
     /* If there are no subsequent WT_UPDATE structures we are done here. */
-    if (upd->next == NULL || exclusive)
+    if (upd->next == NULL || exclusive || __wt_update_chain_all_visible(session, cbt, upd->next))
         return (0);
 
     /*
-     * We would like to call __wt_txn_update_oldest only in the event that there are further updates
-     * to this page, the check against WT_TXN_NONE is used as an indicator of there being further
-     * updates on this page.
+     * Avoid checking for obsolete updates when there is no chance any are present. Occasionally
+     * run the obsolete check regardless, the saved information isn't always accurate, and the
+     * obsolete check does some other update chain health checks that feed into cache management
+     * heuristics.
      */
-    if ((txn = page->modify->obsolete_check_txn) != WT_TXN_NONE) {
+    if (++page->modify->obsolete_check_skip_count < 50 &&
+            (txn = page->modify->obsolete_check_txn) != WT_TXN_NONE) {
         obsolete_timestamp = page->modify->obsolete_check_timestamp;
-        if (!__wt_txn_visible_all(session, txn, obsolete_timestamp)) {
+        /* Periodically try to move the oldest ID forward before checking. */
+        if (page->modify->obsolete_check_skip_count % 10 == 0) {
             /* Try to move the oldest ID forward and re-check. */
             ret = __wt_txn_update_oldest(session, 0);
             /*
@@ -315,14 +381,18 @@ __wt_update_serial(WT_SESSION_IMPL *session, WT_CURSOR_BTREE *cbt, WT_PAGE *page
              */
             if (ret != 0)
                 WT_RET_PANIC(session, ret, "fail to update oldest after serializing the updates");
-
-            if (!__wt_txn_visible_all(session, txn, obsolete_timestamp))
-                return (0);
         }
-
-        page->modify->obsolete_check_txn = WT_TXN_NONE;
+        if (!__wt_txn_visible_all(session, txn, obsolete_timestamp))
+            return (0);
     }
 
+    /*
+     * Clear the saved obsolete transaction ID - we definitely want to complete
+     * an obsolete check now, even if the page lock fails.
+     */
+    page->modify->obsolete_check_txn = WT_TXN_NONE;
+    page->modify->obsolete_check_skip_count = 0;
+
     __wt_update_obsolete_check(session, cbt, upd->next, true);
 
     return (0);
