diff --git a/northd/en-lflow.c b/northd/en-lflow.c
index b7538d6382..dacaaa549a 100644
--- a/northd/en-lflow.c
+++ b/northd/en-lflow.c
@@ -106,7 +106,7 @@ lflow_northd_handler(struct engine_node *node,
     }
 
     /* Fall back to recompute if lb related data has changed. */
-    if (northd_data->lb_changed) {
+    if (northd_data->trk_northd_changes.lb_changed) {
         return false;
     }
 
@@ -116,9 +116,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)) {
+    if (!lflow_handle_northd_changes(eng_ctx->ovnsb_idl_txn,
+                                     &northd_data->trk_northd_changes,
+                                     &lflow_input, lflow_data)) {
         return false;
     }
 
diff --git a/northd/en-northd.c b/northd/en-northd.c
index 3be0f79e19..7a4a1d8629 100644
--- a/northd/en-northd.c
+++ b/northd/en-northd.c
@@ -303,7 +303,7 @@ northd_lb_data_handler_post_od(struct engine_node *node, void *data)
     /* Indicate the depedendant engine nodes that load balancer/group
      * related data has changed (including association to logical
      * switch/router). */
-    nd->lb_changed = true;
+    nd->trk_northd_changes.lb_changed = true;
     engine_set_node_state(node, EN_UPDATED);
     return true;
 }
diff --git a/northd/northd.c b/northd/northd.c
index c1f66ac3c7..b7736bbc85 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -4994,34 +4994,50 @@ 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);
+    struct tracked_ovn_port *trk_op;
+    HMAP_FOR_EACH_POP (trk_op, hmap_node, &trk_ovn_ports->deleted) {
+        ovn_port_destroy_orphan(trk_op->op);
+        free(trk_op);
     }
-    LIST_FOR_EACH (op, list, &ls_change->updated_ports) {
-        ovs_list_remove(&op->list);
+
+    HMAP_FOR_EACH_POP (trk_op, hmap_node, &trk_ovn_ports->created) {
+        free(trk_op);
     }
-    LIST_FOR_EACH_SAFE (op, list, &ls_change->deleted_ports) {
-        ovs_list_remove(&op->list);
-        ovn_port_destroy_orphan(op);
+
+    HMAP_FOR_EACH_POP (trk_op, hmap_node, &trk_ovn_ports->updated) {
+        free(trk_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_northd_changes;
+    destroy_tracked_ovn_ports(&trk_changes->trk_ovn_ports);
 
     nd->change_tracked = false;
-    nd->lb_changed = false;
+    trk_changes->lb_changed = false;
+}
+
+static void
+add_op_to_northd_tracked_ports(struct hmap *tracked_ovn_ports,
+                               struct ovn_port *op)
+{
+    struct tracked_ovn_port *trk_op;
+    uint32_t hash = hash_string(op->key, 0);
+    HMAP_FOR_EACH_WITH_HASH (trk_op, hmap_node,  hash, tracked_ovn_ports) {
+        if (!strcmp(trk_op->op->key, op->key)) {
+            break;
+        }
+    }
+
+    if (!trk_op) {
+        trk_op = xzalloc(sizeof *trk_op);
+        trk_op->op = op;
+        hmap_insert(tracked_ovn_ports, &trk_op->hmap_node, hash);
+    }
 }
 
 /* Check if a changed LSP can be handled incrementally within the I-P engine
@@ -5272,7 +5288,7 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                 const struct northd_input *ni,
                                 struct northd_data *nd,
                                 struct ovn_datapath *od,
-                                struct ls_change *ls_change,
+                                struct tracked_ovn_ports *trk_ports,
                                 bool *updated)
 {
     bool ls_ports_changed = false;
@@ -5333,8 +5349,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_ports->created, op);
         } else if (ls_port_has_changed(op->nbsp, new_nbsp)) {
             /* Existing port updated */
             bool temp = false;
@@ -5365,7 +5380,8 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
                                 ni->sbrec_chassis_by_hostname)) {
                 goto fail;
             }
-            ovs_list_push_back(&ls_change->updated_ports, &op->list);
+
+            add_op_to_northd_tracked_ports(&trk_ports->updated, op);
         }
         op->visited = true;
     }
@@ -5374,20 +5390,19 @@ 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_ports->deleted, op);
             hmap_remove(&nd->ls_ports, &op->key_node);
             hmap_remove(&od->ports, &op->dp_node);
             sbrec_port_binding_delete(op->sb);
             delete_fdb_entry(ni->sbrec_fdb_by_dp_and_port, od->tunnel_key,
-                                op->tunnel_key);
+                             op->tunnel_key);
         }
     }
 
@@ -5404,23 +5419,19 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn *ovnsb_idl_txn,
     only_rports = (od->n_router_ports
                     && (od->n_router_ports == hmap_count(&od->ports)));
     if (only_rports) {
-        goto fail_clean_deleted;
+        goto fail;
     }
 
-    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)) {
+    if (!hmap_is_empty(&trk_ports->created)
+        || !hmap_is_empty(&trk_ports->updated)
+        || !hmap_is_empty(&trk_ports->deleted)) {
         *updated = true;
     }
 
     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_ports);
     return false;
 }
 
@@ -5438,8 +5449,8 @@ 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_nd_changes = &nd->trk_northd_changes;
     NBREC_LOGICAL_SWITCH_TABLE_FOR_EACH_TRACKED (changed_ls,
                                              ni->nbrec_logical_switch_table) {
         if (nbrec_logical_switch_is_new(changed_ls) ||
@@ -5462,33 +5473,18 @@ 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,
+                                   ni, nd, od, &trk_nd_changes->trk_ovn_ports,
                                    &updated)) {
-            destroy_tracked_ls_change(ls_change);
-            free(ls_change);
             goto fail;
         }
 
         if (updated) {
-            ovs_list_push_back(&nd->tracked_ls_changes.updated,
-                               &ls_change->list_node);
-        } else {
-            free(ls_change);
+            nd->change_tracked = true;
         }
     }
 
-    if (!ovs_list_is_empty(&nd->tracked_ls_changes.updated)) {
-        nd->change_tracked = true;
-    }
-
     return true;
 
 fail:
@@ -17856,118 +17852,121 @@ sync_lflows_from_objres(struct ovsdb_idl_txn *ovnsb_txn,
     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 lflow_data *lflow_data)
+bool lflow_handle_northd_changes(struct ovsdb_idl_txn *ovnsb_txn,
+                                 struct northd_tracked_data *trk_nd_changes,
+                                 struct lflow_input *lflow_input,
+                                 struct lflow_data *lflow_data)
 {
-    struct ls_change *ls_change;
+    struct tracked_ovn_ports *trk_ovn_ports = &trk_nd_changes->trk_ovn_ports;
 
-    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);
+    struct tracked_ovn_port *trk_op;
+    HMAP_FOR_EACH (trk_op, hmap_node, &trk_ovn_ports->deleted) {
+        struct ovn_port *op = trk_op->op;
 
-        struct ovn_port *op;
-        LIST_FOR_EACH (op, list, &ls_change->deleted_ports) {
-            struct resource_to_objects_node  *res_node =
+        struct resource_to_objects_node  *res_node =
                 objdep_mgr_find_objs(&op->lflow_dep_mgr, OBJDEP_TYPE_LPORT,
                                      op->nbsp->name);
 
-            /* unlink old lflows. */
-            unlink_objres_lflows(res_node, op->od, lflow_data,
-                                 &op->lflow_dep_mgr);
-            sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
-                                    lflow_data, &op->lflow_dep_mgr);
-            objdep_mgr_clear(&op->lflow_dep_mgr);
+        /* unlink old lflows. */
+        unlink_objres_lflows(res_node, op->od, lflow_data,
+                             &op->lflow_dep_mgr);
+        sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
+                                lflow_data, &op->lflow_dep_mgr);
+        objdep_mgr_clear(&op->lflow_dep_mgr);
 
-            /* 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) {
-            struct resource_to_objects_node  *res_node =
-                objdep_mgr_find_objs(&op->lflow_dep_mgr, OBJDEP_TYPE_LPORT,
-                                     op->nbsp->name);
+    HMAP_FOR_EACH (trk_op, hmap_node, &trk_ovn_ports->updated) {
+        struct ovn_port *op = trk_op->op;
 
-            /* unlink old lflows. */
-            unlink_objres_lflows(res_node, op->od,
-                                 lflow_data, &op->lflow_dep_mgr);
-
-            /* 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,
-                                                     lflow_data);
-            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. */
-            res_node = objdep_mgr_find_objs(&op->lflow_dep_mgr,
-                                            OBJDEP_TYPE_LPORT,
-                                            op->nbsp->name);
-            sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
-                                    lflow_data, &op->lflow_dep_mgr);
-        }
-
-        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,
-                                                     lflow_data);
-            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);
+        struct resource_to_objects_node  *res_node =
+            objdep_mgr_find_objs(&op->lflow_dep_mgr, OBJDEP_TYPE_LPORT,
+                                 op->nbsp->name);
 
-            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);
+        /* unlink old lflows. */
+        unlink_objres_lflows(res_node, op->od,
+                                lflow_data, &op->lflow_dep_mgr);
 
-            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);
-            }
+        /* 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,
+                                                 lflow_data);
+        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. */
+        res_node = objdep_mgr_find_objs(&op->lflow_dep_mgr, OBJDEP_TYPE_LPORT,
+                                        op->nbsp->name);
+        sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
+                                lflow_data, &op->lflow_dep_mgr);
+    }
 
-            /* Sync the newly added flows to SB. */
-            struct resource_to_objects_node  *res_node;
-            res_node = objdep_mgr_find_objs(&op->lflow_dep_mgr,
-                                            OBJDEP_TYPE_LPORT,
-                                            op->nbsp->name);
-            sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
-                                    lflow_data, &op->lflow_dep_mgr);
+    HMAP_FOR_EACH (trk_op, hmap_node, &trk_ovn_ports->created) {
+        struct ovn_port *op = trk_op->op;
+
+        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,
+                                                 lflow_data);
+        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 resource_to_objects_node  *res_node;
+        res_node = objdep_mgr_find_objs(&op->lflow_dep_mgr,
+                                        OBJDEP_TYPE_LPORT,
+                                        op->nbsp->name);
+        sync_lflows_from_objres(ovnsb_txn, res_node, lflow_input,
+                                lflow_data, &op->lflow_dep_mgr);
     }
-    return true;
 
+    return true;
 }
 
 /* Each port group in Port_Group table in OVN_Northbound has a corresponding
@@ -18848,7 +18847,10 @@ northd_init(struct northd_data *data)
     sset_init(&data->svc_monitor_lsps);
     hmap_init(&data->svc_monitor_map);
     data->change_tracked = false;
-    ovs_list_init(&data->tracked_ls_changes.updated);
+
+    hmap_init(&data->trk_northd_changes.trk_ovn_ports.created);
+    hmap_init(&data->trk_northd_changes.trk_ovn_ports.updated);
+    hmap_init(&data->trk_northd_changes.trk_ovn_ports.deleted);
 }
 
 void
@@ -18901,6 +18903,10 @@ northd_destroy(struct northd_data *data)
     destroy_debug_config();
 
     sset_destroy(&data->svc_monitor_lsps);
+
+    hmap_destroy(&data->trk_northd_changes.trk_ovn_ports.created);
+    hmap_destroy(&data->trk_northd_changes.trk_ovn_ports.updated);
+    hmap_destroy(&data->trk_northd_changes.trk_ovn_ports.deleted);
 }
 
 void
diff --git a/northd/northd.h b/northd/northd.h
index d0992f142e..1634463a67 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -19,6 +19,7 @@
 #include "lib/ovn-sb-idl.h"
 #include "lib/ovn-util.h"
 #include "lib/ovs-atomic.h"
+#include "lib/hmapx.h"
 #include "lib/objdep.h"
 #include "lib/sset.h"
 #include "northd/ipam.h"
@@ -87,21 +88,33 @@ 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;
+/* Represents a tracked ovn_port. */
+struct tracked_ovn_port {
+    struct hmap_node hmap_node;
+    struct ovn_port *op;
 };
 
-/* 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_ovn_ports {
+    /* tracked created ports.
+     * hmap node data is 'struct tracked_ovn_port *' */
+    struct hmap created;
+
+    /* tracked updated ports.
+     * hmap node data is 'struct tracked_ovn_port *' */
+    struct hmap updated;
+
+    /* tracked deleted ports.
+     * hmap node data is 'struct tracked_ovn_port *' */
+    struct hmap deleted;
+};
+
+/* Track what's changed in the northd engine node.
+ * Now only tracks ovn_ports (of vif type) - created, updated
+ * and deleted and indicates if load balancers have changed. */
+struct northd_tracked_data {
+    struct tracked_ovn_ports trk_ovn_ports;
+    bool lb_changed; /* Indicates if load balancers changed or association of
+                      * load balancer to logical switch/router changed. */
 };
 
 struct northd_data {
@@ -120,9 +133,7 @@ struct northd_data {
     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. */
+    struct northd_tracked_data trk_northd_changes;
 };
 
 struct lflow_data {
@@ -352,9 +363,9 @@ void northd_indices_create(struct northd_data *data,
 void build_lflows(struct ovsdb_idl_txn *ovnsb_txn,
                   struct lflow_input *input_data,
                   struct lflow_data *lflow_data);
-bool lflow_handle_northd_ls_changes(struct ovsdb_idl_txn *ovnsb_txn,
-                                    struct tracked_ls_changes *,
-                                    struct lflow_input *, struct lflow_data *);
+bool lflow_handle_northd_changes(struct ovsdb_idl_txn *ovnsb_txn,
+                                 struct northd_tracked_data *,
+                                 struct lflow_input *, struct lflow_data *);
 bool northd_handle_sb_port_binding_changes(
     const struct sbrec_port_binding_table *, struct hmap *ls_ports);
 
