diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index 2b84fef0ef..6ba26006e0 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -104,12 +104,12 @@ lflow_northd_handler(struct engine_node *node,
                      void *data)
 {
     struct northd_data *northd_data = engine_get_input_data("northd", node);
-    if (!northd_data->change_tracked) {
+    if (!northd_has_tracked_data(&northd_data->trk_data)) {
         return false;
     }
 
-    /* Fall back to recompute if lb related data has changed. */
-    if (northd_data->lb_changed) {
+    /* Fall back to recompute if load balancers have changed. */
+    if (northd_has_lbs_in_tracked_data(&northd_data->trk_data)) {
         return false;
     }
 
@@ -119,9 +119,9 @@ lflow_northd_handler(struct engine_node *node,
     struct lflow_input lflow_input;
     lflow_get_input_data(node, &lflow_input);
 
-    if (!lflow_handle_northd_ls_changes(eng_ctx->ovnsb_idl_txn,
-                                        &northd_data->tracked_ls_changes,
-                                        &lflow_input, &lflow_data->lflows)) {
+    if (!lflow_handle_northd_port_changes(eng_ctx->ovnsb_idl_txn,
+                                &northd_data->trk_data.trk_lsps,
+                                &lflow_input, &lflow_data->lflows)) {
         return false;
     }
 
diff --git a/northd/en-northd.c b/northd/en-northd.c
index aa0f20f0c2..28559ed211 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -171,7 +171,7 @@ northd_nb_logical_switch_handler(struct engine_node *node,
         return false;
     }
 
-    if (nd->change_tracked) {
+    if (northd_has_lsps_in_tracked_data(&nd->trk_data)) {
         engine_set_node_state(node, EN_UPDATED);
     }
 
@@ -209,10 +209,6 @@ northd_nb_logical_router_handler(struct engine_node *node,
         return false;
     }
 
-    if (nd->change_tracked) {
-        engine_set_node_state(node, EN_UPDATED);
-    }
-
     return true;
 }
 
@@ -230,15 +226,15 @@ northd_lb_data_handler(struct engine_node *node, void *data)
                                        &nd->ls_datapaths,
                                        &nd->lr_datapaths,
                                        &nd->lb_datapaths_map,
-                                       &nd->lb_group_datapaths_map)) {
+                                       &nd->lb_group_datapaths_map,
+                                       &nd->trk_data)) {
         return false;
     }
 
-    /* Indicate the depedendant engine nodes that load balancer/group
-     * related data has changed (including association to logical
-     * switch/router). */
-    nd->lb_changed = true;
-    engine_set_node_state(node, EN_UPDATED);
+    if (northd_has_lbs_in_tracked_data(&nd->trk_data)) {
+        engine_set_node_state(node, EN_UPDATED);
+    }
+
     return true;
 }
 
diff --git a/northd/en-sync-from-sb.c b/northd/en-sync-from-sb.c
index 48b1576173..8c05239b49 100644
--- a/northd/en-sync-from-sb.c
+++ b/northd/en-sync-from-sb.c
@@ -65,7 +65,7 @@ sync_from_sb_northd_handler(struct engine_node *node,
                             void *data OVS_UNUSED)
 {
     struct northd_data *nd = engine_get_input_data("northd", node);
-    if (nd->change_tracked) {
+    if (northd_has_tracked_data(&nd->trk_data)) {
         /* So far the changes tracked in northd don't impact this node.
          *
          * In particular, for the LS related changes, the only field this node
diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index 2ec3bf54f8..3aaab8d005 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -236,7 +236,8 @@ sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
 {
     struct northd_data *nd = engine_get_input_data("northd", node);
 
-    if (!nd->change_tracked || nd->lb_changed) {
+    if (!northd_has_tracked_data(&nd->trk_data) ||
+            northd_has_lbs_in_tracked_data(&nd->trk_data)) {
         /* Return false if no tracking data or if lbs changed. */
         return false;
     }
@@ -306,11 +307,13 @@ sync_to_sb_pb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
     }
 
     struct northd_data *nd = engine_get_input_data("northd", node);
-    if (!nd->change_tracked) {
+    if (!northd_has_tracked_data(&nd->trk_data) ||
+            northd_has_lbs_in_tracked_data(&nd->trk_data)) {
+        /* Return false if no tracking data or if lbs changed. */
         return false;
     }
 
-    if (!sync_pbs_for_northd_ls_changes(&nd->tracked_ls_changes)) {
+    if (!sync_pbs_for_northd_changed_ovn_ports(&nd->trk_data.trk_lsps)) {
         return false;
     }
 
diff --git a/northd/northd.c b/northd/northd.c
index 29245d6333..abb06bfc6a 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -4894,21 +4894,20 @@ sync_pbs(struct ovsdb_idl_txn *ovnsb_idl_txn, struct hmap *ls_ports)
 }
 
 /* Sync the SB Port bindings for the added and updated logical switch ports
- *  of the tracked logical switches (from the northd engine node). */
+ * of the tracked northd engine data. */
 bool
-sync_pbs_for_northd_ls_changes(struct tracked_ls_changes *ls_changes)
+sync_pbs_for_northd_changed_ovn_ports( struct tracked_ovn_ports *trk_ovn_ports)
 {
-    struct ls_change *ls_change;
-    LIST_FOR_EACH (ls_change, list_node, &ls_changes->updated) {
-        struct ovn_port *op;
-
-        LIST_FOR_EACH (op, list, &ls_change->added_ports) {
-            sync_pb_for_op(op);
-        }
+    struct hmapx_node *hmapx_node;
+    struct ovn_port *op;
+    HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->created) {
+        op = hmapx_node->data;
+        sync_pb_for_op(op);
+    }
 
-        LIST_FOR_EACH (op, list, &ls_change->updated_ports) {
-            sync_pb_for_op(op);
-        }
+    HMAPX_FOR_EACH (hmapx_node, &trk_ovn_ports->updated) {
+        op = hmapx_node->data;
+        sync_pb_for_op(op);
     }
 
     return true;
@@ -5110,34 +5109,68 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn,
 }
 
 static void
-destroy_tracked_ls_change(struct ls_change *ls_change)
+destroy_tracked_ovn_ports(struct tracked_ovn_ports *trk_ovn_ports)
 {
-    struct ovn_port *op;
-    LIST_FOR_EACH (op, list, &ls_change->added_ports) {
-        ovs_list_remove(&op->list);
-    }
-    LIST_FOR_EACH (op, list, &ls_change->updated_ports) {
-        ovs_list_remove(&op->list);
+    struct hmapx_node *hmapx_node;
+    HMAPX_FOR_EACH_SAFE (hmapx_node, &trk_ovn_ports->deleted) {
+        ovn_port_destroy_orphan(hmapx_node->data);
+        hmapx_delete(&trk_ovn_ports->deleted, hmapx_node);
     }
-    LIST_FOR_EACH_SAFE (op, list, &ls_change->deleted_ports) {
-        ovs_list_remove(&op->list);
-        ovn_port_destroy_orphan(op);
+
+    hmapx_clear(&trk_ovn_ports->created);
+    hmapx_clear(&trk_ovn_ports->updated);
+}
+
+static void
+destroy_tracked_lbs(struct tracked_lbs *trk_lbs)
+{
+    struct hmapx_node *hmapx_node;
+    HMAPX_FOR_EACH_SAFE (hmapx_node, &trk_lbs->deleted) {
+        ovn_lb_datapaths_destroy(hmapx_node->data);
+        hmapx_delete(&trk_lbs->deleted, hmapx_node);
     }
+
+    hmapx_clear(&trk_lbs->crupdated);
+}
+
+static void
+add_op_to_northd_tracked_ports(struct hmapx *tracked_ovn_ports,
+                               struct ovn_port *op)
+{
+    hmapx_add(tracked_ovn_ports, op);
 }
 
 void
 destroy_northd_data_tracked_changes(struct northd_data *nd)
 {
-    struct ls_change *ls_change;
-    LIST_FOR_EACH_SAFE (ls_change, list_node,
-                        &nd->tracked_ls_changes.updated) {
-        destroy_tracked_ls_change(ls_change);
-        ovs_list_remove(&ls_change->list_node);
-        free(ls_change);
-    }
+    struct northd_tracked_data *trk_changes = &nd->trk_data;
+    destroy_tracked_ovn_ports(&trk_changes->trk_lsps);
+    destroy_tracked_lbs(&trk_changes->trk_lbs);
+    nd->trk_data.type = NORTHD_TRACKED_NONE;
+}
+
+static void
+init_northd_tracked_data(struct northd_data *nd)
+{
+    struct northd_tracked_data *trk_data = &nd->trk_data;
+    trk_data->type = NORTHD_TRACKED_NONE;
+    hmapx_init(&trk_data->trk_lsps.created);
+    hmapx_init(&trk_data->trk_lsps.updated);
+    hmapx_init(&trk_data->trk_lsps.deleted);
+    hmapx_init(&trk_data->trk_lbs.crupdated);
+    hmapx_init(&trk_data->trk_lbs.deleted);
+}
 
-    nd->change_tracked = false;
-    nd->lb_changed = false;
+static void
+destroy_northd_tracked_data(struct northd_data *nd)
+{
+    struct northd_tracked_data *trk_data = &nd->trk_data;
+    trk_data->type = NORTHD_TRACKED_NONE;
+    hmapx_destroy(&trk_data->trk_lsps.created);
+    hmapx_destroy(&trk_data->trk_lsps.updated);
+    hmapx_destroy(&trk_data->trk_lsps.deleted);
+    hmapx_destroy(&trk_data->trk_lbs.crupdated);
+    hmapx_destroy(&trk_data->trk_lbs.deleted);
 }
 
 /* Check if a changed LSP can be handled incrementally within the I-P engine
@@ -5344,12 +5377,11 @@ check_lsp_changes_other_than_up(const struct nbrec_logical_switch_port *nbsp)
  */
 static bool
 ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
-                                const struct nbrec_logical_switch *changed_ls,
-                                const struct northd_input *ni,
-                                struct northd_data *nd,
-                                struct ovn_datapath *od,
-                                struct ls_change *ls_change,
-                                bool *updated)
+                      const struct nbrec_logical_switch *changed_ls,
+                      const struct northd_input *ni,
+                      struct northd_data *nd,
+                      struct ovn_datapath *od,
+                      struct tracked_ovn_ports *trk_lsps)
 {
     bool ls_ports_changed = false;
     if (!nbrec_logical_switch_is_updated(changed_ls,
@@ -5370,7 +5402,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
         return true;
     }
 
-    ls_change->had_only_router_ports = (od->n_router_ports
+    bool ls_had_only_router_ports = (od->n_router_ports
             && (od->n_router_ports == hmap_count(&od->ports)));
 
     struct ovn_port *op;
@@ -5396,8 +5428,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
             if (!op) {
                 goto fail;
             }
-            ovs_list_push_back(&ls_change->added_ports,
-                                &op->list);
+            add_op_to_northd_tracked_ports(&trk_lsps->created, op);
         } else if (ls_port_has_changed(op->nbsp, new_nbsp)) {
             /* Existing port updated */
             bool temp = false;
@@ -5433,7 +5464,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
             if (!op) {
                 goto fail;
             }
-            ovs_list_push_back(&ls_change->updated_ports, &op->list);
+            add_op_to_northd_tracked_ports(&trk_lsps->updated, op);
         }
         op->visited = true;
     }
@@ -5442,15 +5473,14 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
     HMAP_FOR_EACH_SAFE (op, dp_node, &od->ports) {
         if (!op->visited) {
             if (!op->lsp_can_be_inc_processed) {
-                goto fail_clean_deleted;
+                goto fail;
             }
             if (sset_contains(&nd->svc_monitor_lsps, op->key)) {
                 /* This port was used for svc monitor, which may be
                  * impacted by this deletion. Fallback to recompute. */
-                goto fail_clean_deleted;
+                goto fail;
             }
-            ovs_list_push_back(&ls_change->deleted_ports,
-                                &op->list);
+            add_op_to_northd_tracked_ports(&trk_lsps->deleted, op);
             hmap_remove(&nd->ls_ports, &op->key_node);
             hmap_remove(&od->ports, &op->dp_node);
             sbrec_port_binding_delete(op->sb);
@@ -5459,27 +5489,32 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
         }
     }
 
-    if (!ovs_list_is_empty(&ls_change->added_ports) ||
-        !ovs_list_is_empty(&ls_change->updated_ports) ||
-        !ovs_list_is_empty(&ls_change->deleted_ports)) {
-        *updated = true;
+    bool ls_has_only_router_ports = (od->n_router_ports
+            && (od->n_router_ports == hmap_count(&od->ports)));
+
+    /* There are lflows related to router ports that depends on whether
+     * there are switch ports on the logical switch (see
+     * build_lswitch_rport_arp_req_flow() for more details). Check if this
+     * dependency has changed and if it has, then add the router ports
+     * to the tracked 'updated' ovn ports so that lflow engine can
+     * regenerate lflows for these router ports. */
+    if (ls_had_only_router_ports != ls_has_only_router_ports) {
+        for (size_t i = 0; i < od->n_router_ports; i++) {
+            op = od->router_ports[i];
+            add_op_to_northd_tracked_ports(&trk_lsps->updated, op);
+        }
     }
 
     return true;
 
-fail_clean_deleted:
-    LIST_FOR_EACH_POP (op, list, &ls_change->deleted_ports) {
-        ovn_port_destroy_orphan(op);
-    }
-
 fail:
+    destroy_tracked_ovn_ports(trk_lsps);
     return false;
 }
 
 /* Return true if changes are handled incrementally, false otherwise.
  * When there are any changes, try to track what's exactly changed and set
- * northd_data->change_tracked accordingly: change tracked - true, otherwise,
- * false.
+ * northd_data->trk_data accordingly.
  *
  * Note: Changes to load balancer and load balancer groups associated with
  * the logical switches are handled separately in the lb_data change handlers.
@@ -5490,7 +5525,7 @@ northd_handle_ls_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                          struct northd_data *nd)
 {
     const struct nbrec_logical_switch *changed_ls;
-    struct ls_change *ls_change = NULL;
+    struct northd_tracked_data *trk_data = &nd->trk_data;
 
     NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (changed_ls,
                                              ni->nbrec_logical_switch_table) {
@@ -5514,31 +5549,16 @@ northd_handle_ls_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
             goto fail;
         }
 
-        ls_change = xzalloc(sizeof *ls_change);
-        ls_change->od = od;
-        ovs_list_init(&ls_change->added_ports);
-        ovs_list_init(&ls_change->deleted_ports);
-        ovs_list_init(&ls_change->updated_ports);
-
-        bool updated = false;
         if (!ls_handle_lsp_changes(ovnsb_idl_txn, changed_ls,
-                                   ni, nd, od, ls_change,
-                                   &updated)) {
-            destroy_tracked_ls_change(ls_change);
-            free(ls_change);
+                                   ni, nd, od, &trk_data->trk_lsps)) {
             goto fail;
         }
-
-        if (updated) {
-            ovs_list_push_back(&nd->tracked_ls_changes.updated,
-                               &ls_change->list_node);
-        } else {
-            free(ls_change);
-        }
     }
 
-    if (!ovs_list_is_empty(&nd->tracked_ls_changes.updated)) {
-        nd->change_tracked = true;
+    if (!hmapx_is_empty(&trk_data->trk_lsps.created)
+        || !hmapx_is_empty(&trk_data->trk_lsps.updated)
+        || !hmapx_is_empty(&trk_data->trk_lsps.deleted)) {
+        trk_data->type |= NORTHD_TRACKED_PORTS;
     }
 
     return true;
@@ -5603,13 +5623,10 @@ lr_changes_can_be_handled(
 }
 
 /* Return true if changes are handled incrementally, false otherwise.
- * When there are any changes, try to track what's exactly changed and set
- * northd_data->change_tracked accordingly: change tracked - true, otherwise,
- * false.
+ *
  * Note: Changes to load balancer and load balancer groups associated with
  * the logical routers are handled separately in the lb_data change
- * handlers (northd_handle_lb_data_changes_pre_od and
- * northd_handle_lb_data_changes_post_od).
+ * handler -  northd_handle_lb_data_changes().
  * */
 bool
 northd_handle_lr_changes(const struct northd_input *ni,
@@ -5715,7 +5732,8 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                               struct ovn_datapaths *ls_datapaths,
                               struct ovn_datapaths *lr_datapaths,
                               struct hmap *lb_datapaths_map,
-                              struct hmap *lbgrp_datapaths_map)
+                              struct hmap *lbgrp_datapaths_map,
+                              struct northd_tracked_data *nd_changes)
 {
     if (trk_lb_data->has_health_checks) {
         /* Fall back to recompute since a tracked load balancer
@@ -5778,7 +5796,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
         }
 
         hmap_remove(lb_datapaths_map, &lb_dps->hmap_node);
-        ovn_lb_datapaths_destroy(lb_dps);
+
+        /* Add the deleted lb to the northd tracked data. */
+        hmapx_add(&nd_changes->trk_lbs.deleted, lb_dps);
     }
 
     /* Create the 'lb_dps' if not already created for each
@@ -5795,6 +5815,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             hmap_insert(lb_datapaths_map, &lb_dps->hmap_node,
                         uuid_hash(lb_uuid));
         }
+
+        /* Add the updated lb to the northd tracked data. */
+        hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
     }
 
     struct ovn_lb_group_datapaths *lbgrp_dps;
@@ -5825,6 +5848,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, &uuidnode->uuid);
             ovs_assert(lb_dps);
             ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
+
+            /* Add the lb to the northd tracked data. */
+            hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
         }
 
         UUIDSET_FOR_EACH (uuidnode, &codlb->assoc_lbgrps) {
@@ -5840,6 +5866,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                 lb_dps = ovn_lb_datapaths_find(lb_datapaths_map, lb_uuid);
                 ovs_assert(lb_dps);
                 ovn_lb_datapaths_add_ls(lb_dps, 1, &od);
+
+                /* Add the lb to the northd tracked data. */
+                hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
             }
         }
 
@@ -5860,6 +5889,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
             /* Add the lb_ips of lb_dps to the od. */
             build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
             build_lrouter_lb_reachable_ips(od, lb_dps->lb);
+
+            /* Add the lb to the northd tracked data. */
+            hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
         }
 
         UUIDSET_FOR_EACH (uuidnode, &codlb->assoc_lbgrps) {
@@ -5879,6 +5911,9 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                 /* Add the lb_ips of lb_dps to the od. */
                 build_lrouter_lb_ips(od->lb_ips, lb_dps->lb);
                 build_lrouter_lb_reachable_ips(od, lb_dps->lb);
+
+                /* Add the lb to the northd tracked data. */
+                hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
             }
         }
 
@@ -5957,9 +5992,17 @@ northd_handle_lb_data_changes(struct tracked_lb_data *trk_lb_data,
                 /* Re-evaluate 'od->has_lb_vip' */
                 init_lb_for_datapath(od);
             }
+
+            /* Add the lb to the northd tracked data. */
+            hmapx_add(&nd_changes->trk_lbs.crupdated, lb_dps);
         }
     }
 
+    if (!hmapx_is_empty(&nd_changes->trk_lbs.crupdated)
+        || !hmapx_is_empty(&nd_changes->trk_lbs.deleted)) {
+        nd_changes->type |= NORTHD_TRACKED_LBS;
+    }
+
     return true;
 }
 
@@ -16980,149 +17023,122 @@ delete_lflow_for_lsp(struct ovn_port *op, bool is_update,
     return true;
 }
 
-bool lflow_handle_northd_ls_changes(struct ovsdb_idl_txn *ovnsb_txn,
-                                    struct tracked_ls_changes *ls_changes,
-                                    struct lflow_input *lflow_input,
-                                    struct hmap *lflows)
+bool
+lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
+                                 struct tracked_ovn_ports *trk_lsps,
+                                 struct lflow_input *lflow_input,
+                                 struct hmap *lflows)
 {
-    struct ls_change *ls_change;
+    struct hmapx_node *hmapx_node;
+    struct ovn_port *op;
 
-    LIST_FOR_EACH (ls_change, list_node, &ls_changes->updated) {
-        const struct sbrec_multicast_group *sbmc_flood =
-            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
-                               MC_FLOOD, ls_change->od->sb);
-        const struct sbrec_multicast_group *sbmc_flood_l2 =
-            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
-                               MC_FLOOD_L2, ls_change->od->sb);
-        const struct sbrec_multicast_group *sbmc_unknown =
-            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
-                               MC_UNKNOWN, ls_change->od->sb);
+    HMAPX_FOR_EACH (hmapx_node, &trk_lsps->deleted) {
+        op = hmapx_node->data;
+        /* Make sure 'op' is an lsp and not lrp. */
+        ovs_assert(op->nbsp);
 
-        struct ovn_port *op;
-        LIST_FOR_EACH (op, list, &ls_change->deleted_ports) {
-            if (!delete_lflow_for_lsp(op, false,
-                                      lflow_input->sbrec_logical_flow_table,
-                                      lflows)) {
+        if (!delete_lflow_for_lsp(op, false,
+                                  lflow_input->sbrec_logical_flow_table,
+                                  lflows)) {
                 return false;
             }
 
-            /* No need to update SB multicast groups, thanks to weak
-             * references. */
-        }
+        /* No need to update SB multicast groups, thanks to weak
+         * references. */
+    }
 
-        LIST_FOR_EACH (op, list, &ls_change->updated_ports) {
-            /* Delete old lflows. */
-            if (!delete_lflow_for_lsp(op, true,
-                                      lflow_input->sbrec_logical_flow_table,
-                                      lflows)) {
-                return false;
-            }
+    HMAPX_FOR_EACH (hmapx_node, &trk_lsps->updated) {
+        op = hmapx_node->data;
+        /* Make sure 'op' is an lsp and not lrp. */
+        ovs_assert(op->nbsp);
 
-            /* Generate new lflows. */
-            struct ds match = DS_EMPTY_INITIALIZER;
-            struct ds actions = DS_EMPTY_INITIALIZER;
-            build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
-                                                     lflow_input->lr_ports,
-                                                     lflow_input->meter_groups,
-                                                     &match, &actions,
-                                                     lflows);
-            ds_destroy(&match);
-            ds_destroy(&actions);
-
-            /* SB port_binding is not deleted, so don't update SB multicast
-             * groups. */
-
-            /* Sync the new flows to SB. */
-            struct lflow_ref_node *lfrn;
-            LIST_FOR_EACH (lfrn, lflow_list_node, &op->lflows) {
-                sync_lsp_lflows_to_sb(ovnsb_txn, lflow_input, lflows,
-                                      lfrn->lflow);
-            }
+        /* Delete old lflows. */
+        if (!delete_lflow_for_lsp(op, true,
+                                  lflow_input->sbrec_logical_flow_table,
+                                  lflows)) {
+            return false;
         }
 
-        LIST_FOR_EACH (op, list, &ls_change->added_ports) {
-            struct ds match = DS_EMPTY_INITIALIZER;
-            struct ds actions = DS_EMPTY_INITIALIZER;
-            build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
-                                                     lflow_input->lr_ports,
-                                                     lflow_input->meter_groups,
-                                                     &match, &actions,
-                                                     lflows);
-            ds_destroy(&match);
-            ds_destroy(&actions);
-
-            /* Update SB multicast groups for the new port. */
-            if (!sbmc_flood) {
-                sbmc_flood = create_sb_multicast_group(ovnsb_txn,
-                    ls_change->od->sb, MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY);
-            }
-            sbrec_multicast_group_update_ports_addvalue(sbmc_flood, op->sb);
+        /* Generate new lflows. */
+        struct ds match = DS_EMPTY_INITIALIZER;
+        struct ds actions = DS_EMPTY_INITIALIZER;
+        build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
+                                                 lflow_input->lr_ports,
+                                                 lflow_input->meter_groups,
+                                                 &match, &actions,
+                                                 lflows);
+        ds_destroy(&match);
+        ds_destroy(&actions);
 
-            if (!sbmc_flood_l2) {
-                sbmc_flood_l2 = create_sb_multicast_group(ovnsb_txn,
-                    ls_change->od->sb, MC_FLOOD_L2,
-                    OVN_MCAST_FLOOD_L2_TUNNEL_KEY);
-            }
-            sbrec_multicast_group_update_ports_addvalue(sbmc_flood_l2, op->sb);
+        /* SB port_binding is not deleted, so don't update SB multicast
+         * groups. */
 
-            if (op->has_unknown) {
-                if (!sbmc_unknown) {
-                    sbmc_unknown = create_sb_multicast_group(ovnsb_txn,
-                        ls_change->od->sb, MC_UNKNOWN,
-                        OVN_MCAST_UNKNOWN_TUNNEL_KEY);
-                }
-                sbrec_multicast_group_update_ports_addvalue(sbmc_unknown,
-                                                            op->sb);
-            }
-
-            /* Sync the newly added flows to SB. */
-            struct lflow_ref_node *lfrn;
-            LIST_FOR_EACH (lfrn, lflow_list_node, &op->lflows) {
-                sync_lsp_lflows_to_sb(ovnsb_txn, lflow_input, lflows,
-                                      lfrn->lflow);
-            }
+        /* Sync the new flows to SB. */
+        struct lflow_ref_node *lfrn;
+        LIST_FOR_EACH (lfrn, lflow_list_node, &op->lflows) {
+            sync_lsp_lflows_to_sb(ovnsb_txn, lflow_input, lflows,
+                                  lfrn->lflow);
         }
+    }
 
-        bool ls_has_only_router_ports = (ls_change->od->n_router_ports &&
-                                         (ls_change->od->n_router_ports ==
-                                          hmap_count(&ls_change->od->ports)));
-
-        if (ls_change->had_only_router_ports != ls_has_only_router_ports) {
-            /* There are lflows related to router ports that depends on whether
-             * there are switch ports on the logical switch (see
-             * build_lswitch_rport_arp_req_flow() for more details). Since this
-             * dependency changed, we need to regenerate lflows for each router
-             * port on this logical switch. */
-            for (size_t i = 0; i < ls_change->od->n_router_ports; i++) {
-                op = ls_change->od->router_ports[i];
-
-                /* Delete old lflows. */
-                if (!delete_lflow_for_lsp(op, "affected router",
-                                      lflow_input->sbrec_logical_flow_table,
-                                      lflows)) {
-                    return false;
-                }
+    HMAPX_FOR_EACH (hmapx_node, &trk_lsps->created) {
+        op = hmapx_node->data;
+        /* Make sure 'op' is an lsp and not lrp. */
+        ovs_assert(op->nbsp);
 
-                /* Generate new lflows. */
-                struct ds match = DS_EMPTY_INITIALIZER;
-                struct ds actions = DS_EMPTY_INITIALIZER;
-                build_lswitch_and_lrouter_iterate_by_lsp(op,
-                    lflow_input->ls_ports, lflow_input->lr_ports,
-                    lflow_input->meter_groups, &match, &actions, lflows);
-                ds_destroy(&match);
-                ds_destroy(&actions);
-
-                /* Sync the new flows to SB. */
-                struct lflow_ref_node *lfrn;
-                LIST_FOR_EACH (lfrn, lflow_list_node, &op->lflows) {
-                    sync_lsp_lflows_to_sb(ovnsb_txn, lflow_input, lflows,
-                                          lfrn->lflow);
-                }
+        const struct sbrec_multicast_group *sbmc_flood =
+            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+                               MC_FLOOD, op->od->sb);
+        const struct sbrec_multicast_group *sbmc_flood_l2 =
+            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+                               MC_FLOOD_L2, op->od->sb);
+        const struct sbrec_multicast_group *sbmc_unknown =
+            mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp,
+                               MC_UNKNOWN, op->od->sb);
+
+        struct ds match = DS_EMPTY_INITIALIZER;
+        struct ds actions = DS_EMPTY_INITIALIZER;
+        build_lswitch_and_lrouter_iterate_by_lsp(op, lflow_input->ls_ports,
+                                                    lflow_input->lr_ports,
+                                                    lflow_input->meter_groups,
+                                                    &match, &actions,
+                                                    lflows);
+        ds_destroy(&match);
+        ds_destroy(&actions);
+
+        /* Update SB multicast groups for the new port. */
+        if (!sbmc_flood) {
+            sbmc_flood = create_sb_multicast_group(ovnsb_txn,
+                op->od->sb, MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY);
+        }
+        sbrec_multicast_group_update_ports_addvalue(sbmc_flood, op->sb);
+
+        if (!sbmc_flood_l2) {
+            sbmc_flood_l2 = create_sb_multicast_group(ovnsb_txn,
+                op->od->sb, MC_FLOOD_L2,
+                OVN_MCAST_FLOOD_L2_TUNNEL_KEY);
+        }
+        sbrec_multicast_group_update_ports_addvalue(sbmc_flood_l2, op->sb);
+
+        if (op->has_unknown) {
+            if (!sbmc_unknown) {
+                sbmc_unknown = create_sb_multicast_group(ovnsb_txn,
+                    op->od->sb, MC_UNKNOWN,
+                    OVN_MCAST_UNKNOWN_TUNNEL_KEY);
             }
+            sbrec_multicast_group_update_ports_addvalue(sbmc_unknown,
+                                                        op->sb);
+        }
+
+        /* Sync the newly added flows to SB. */
+        struct lflow_ref_node *lfrn;
+        LIST_FOR_EACH (lfrn, lflow_list_node, &op->lflows) {
+            sync_lsp_lflows_to_sb(ovnsb_txn, lflow_input, lflows,
+                                    lfrn->lflow);
         }
     }
-    return true;
 
+    return true;
 }
 
 static bool
@@ -17767,8 +17783,7 @@ northd_init(struct northd_data *data)
     data->ovn_internal_version_changed = false;
     sset_init(&data->svc_monitor_lsps);
     hmap_init(&data->svc_monitor_map);
-    data->change_tracked = false;
-    ovs_list_init(&data->tracked_ls_changes.updated);
+    init_northd_tracked_data(data);
 }
 
 void
@@ -17808,6 +17823,7 @@ northd_destroy(struct northd_data *data)
     destroy_debug_config();
 
     sset_destroy(&data->svc_monitor_lsps);
+    destroy_northd_tracked_data(data);
 }
 
 void
diff --git a/northd/northd.h b/northd/northd.h
index 5be7b5384d..23521065e8 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -83,22 +83,44 @@ struct ovn_datapaths {
     struct ovn_datapath **array;
 };
 
-/* Track what's changed for a single LS.
- * Now only track port changes. */
-struct ls_change {
-    struct ovs_list list_node;
-    struct ovn_datapath *od;
-    struct ovs_list added_ports;
-    struct ovs_list deleted_ports;
-    struct ovs_list updated_ports;
-    bool had_only_router_ports;
+struct tracked_ovn_ports {
+    /* tracked created ports.
+     * hmapx node data is 'struct ovn_port *' */
+    struct hmapx created;
+
+    /* tracked updated ports.
+     * hmapx node data is 'struct ovn_port *' */
+    struct hmapx updated;
+
+    /* tracked deleted ports.
+     * hmapx node data is 'struct ovn_port *' */
+    struct hmapx deleted;
 };
 
-/* Track what's changed for logical switches.
- * Now only track updated ones (added or deleted may be supported in the
- * future). */
-struct tracked_ls_changes {
-    struct ovs_list updated; /* Contains struct ls_change */
+struct tracked_lbs {
+    /* Tracked created or updated load balancers.
+     * hmapx node data is 'struct ovn_lb_datapaths' */
+    struct hmapx crupdated;
+
+    /* Tracked deleted lbs.
+     * hmapx node data is 'struct ovn_lb_datapaths' */
+    struct hmapx deleted;
+};
+
+enum northd_tracked_data_type {
+    NORTHD_TRACKED_NONE,
+    NORTHD_TRACKED_PORTS = (1 << 0),
+    NORTHD_TRACKED_LBS   = (1 << 1),
+};
+
+/* Track what's changed in the northd engine node.
+ * Now only tracks ovn_ports (of vif type) - created, updated
+ * and deleted. */
+struct northd_tracked_data {
+    /* Indicates the type of data tracked.  One or all of NORTHD_TRACKED_*. */
+    enum northd_tracked_data_type type;
+    struct tracked_ovn_ports trk_lsps;
+    struct tracked_lbs trk_lbs;
 };
 
 struct northd_data {
@@ -114,10 +136,9 @@ struct northd_data {
     struct chassis_features features;
     struct sset svc_monitor_lsps;
     struct hmap svc_monitor_map;
-    bool change_tracked;
-    struct tracked_ls_changes tracked_ls_changes;
-    bool lb_changed; /* Indicates if load balancers changed or association of
-                      * load balancer to logical switch/router changed. */
+
+    /* Change tracking data. */
+    struct northd_tracked_data trk_data;
 };
 
 struct lflow_data {
@@ -338,9 +359,10 @@ void northd_indices_create(struct northd_data *data,
 void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                   struct lflow_input *input_data,
                   struct hmap *lflows);
-bool lflow_handle_northd_ls_changes(struct ovsdb_idl_txn *ovnsb_txn,
-                                    struct tracked_ls_changes *,
-                                    struct lflow_input *, struct hmap *lflows);
+bool lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn,
+                                      struct tracked_ovn_ports *,
+                                      struct lflow_input *,
+                                      struct hmap *lflows);
 bool northd_handle_sb_port_binding_changes(
     const struct sbrec_port_binding_table *, struct hmap *ls_ports);
 
@@ -349,7 +371,8 @@ bool northd_handle_lb_data_changes(struct tracked_lb_data *,
                                    struct ovn_datapaths *ls_datapaths,
                                    struct ovn_datapaths *lr_datapaths,
                                    struct hmap *lb_datapaths_map,
-                                   struct hmap *lbgrp_datapaths_map);
+                                   struct hmap *lbgrp_datapaths_map,
+                                   struct northd_tracked_data *);
 
 void build_bfd_table(struct ovsdb_idl_txn *ovnsb_txn,
                      const struct nbrec_bfd_table *,
@@ -372,6 +395,23 @@ void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
 bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
 
 void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports);
-bool sync_pbs_for_northd_ls_changes(struct tracked_ls_changes *);
+bool sync_pbs_for_northd_changed_ovn_ports( struct tracked_ovn_ports *);
+
+static inline bool
+northd_has_tracked_data(struct northd_tracked_data *trk_nd_changes) {
+    return trk_nd_changes->type != NORTHD_TRACKED_NONE;
+}
+
+static inline bool
+northd_has_lbs_in_tracked_data(struct northd_tracked_data *trk_nd_changes)
+{
+    return (trk_nd_changes->type & NORTHD_TRACKED_LBS);
+}
+
+static inline bool
+northd_has_lsps_in_tracked_data(struct northd_tracked_data *trk_nd_changes)
+{
+    return (trk_nd_changes->type & NORTHD_TRACKED_PORTS);
+}
 
 #endif /* NORTHD_H */
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index a8099786c1..4fc9e6a4e0 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -10632,10 +10632,10 @@ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
-check_engine_stats lflow recompute nocompute
-check_engine_stats sync_to_sb_lb recompute nocompute
-
+check_engine_stats lflow norecompute nocompute
+check_engine_stats sync_to_sb_lb norecompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
+
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 
 lb1_uuid=$(fetch_column nb:Load_Balancer _uuid)
@@ -10662,7 +10662,6 @@ check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow recompute nocompute
 check_engine_stats sync_to_sb_lb recompute nocompute
-
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
@@ -10810,8 +10809,8 @@ check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 lbg1_uuid=$(ovn-nbctl --wait=sb create load_balancer_group name=lbg1)
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
-check_engine_stats lflow recompute nocompute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats lflow norecompute nocompute
+check_engine_stats sync_to_sb_lb norecompute nocompute
 
 check as northd ovn-appctl -t NORTHD_TYPE inc-engine/clear-stats
 check ovn-nbctl --wait=sb set load_balancer_group . load_balancer="$lb2_uuid,$lb3_uuid,$lb4_uuid"
