diff mbox series

[ovs-dev,v5,15/16] northd: Add northd change handler for sync_to_sb_lb node.

Message ID 20240111153428.2790587-1-numans@ovn.org
State Superseded
Headers show
Series northd lflow incremental processing | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/github-robot-_ovn-kubernetes success github build: passed

Commit Message

Numan Siddique Jan. 11, 2024, 3:34 p.m. UTC
From: Numan Siddique <numans@ovn.org>

Any changes to northd engine node due to load balancers
are now handled in 'sync_to_sb_lb' node to sync the changed
load balancers to SB load balancers.

The logic to sync the SB load balancers is changed a bit and it
now mimics the SB lflow sync.

Below are the scale testing results done with all the patches applied
in this series using ovn-heater.  The test ran the scenario  -
ocp-500-density-heavy.yml [1].

The resuts are:

-------------------------------------------------------------------------------------------------------------------------------------------------------
			Min (s)		Median (s)	90%ile (s)	99%ile (s)	Max (s)		Mean (s)	Total (s)	Count	Failed
-------------------------------------------------------------------------------------------------------------------------------------------------------
Iteration Total		0.136883	1.129016	1.192001	1.204167	1.212728	0.665017	83.127099	125	0
Namespace.add_ports	0.005216	0.005736	0.007034	0.015486	0.018978	0.006211	0.776373	125	0
WorkerNode.bind_port	0.035030	0.046082	0.052469	0.058293	0.060311	0.045973	11.493259	250	0
WorkerNode.ping_port	0.005057	0.006727	1.047692	1.069253	1.071336	0.266896	66.724094	250	0
-------------------------------------------------------------------------------------------------------------------------------------------------------

The results with the present main [2] are:

-------------------------------------------------------------------------------------------------------------------------------------------------------
			Min (s)		Median (s)	90%ile (s)	99%ile (s)	Max (s)		Mean (s)	Total (s)	Count	Failed
-------------------------------------------------------------------------------------------------------------------------------------------------------
Iteration Total		0.135491	2.223805	3.311270	3.339078	3.345346	1.729172	216.146495	125	0
Namespace.add_ports	0.005380	0.005744	0.006819	0.018773	0.020800	0.006292	0.786532	125	0
WorkerNode.bind_port	0.034179	0.046055	0.053488	0.058801	0.071043	0.046117	11.529311	250	0
WorkerNode.ping_port	0.004956	0.006952	3.086952	3.191743	3.192807	0.791544	197.886026	250	0
-------------------------------------------------------------------------------------------------------------------------------------------------------

[1] - https://github.com/ovn-org/ovn-heater/blob/main/test-scenarios/ocp-500-density-heavy.yml
[2] - 2a12cda890a7("controller, northd: Wait for cleanup before replying to exit")

Signed-off-by: Numan Siddique <numans@ovn.org>
---
 northd/en-sync-sb.c      | 505 +++++++++++++++++++++++++++++++++++++--
 northd/inc-proc-northd.c |   1 +
 northd/lflow-mgr.c       | 196 ++++++---------
 northd/lflow-mgr.h       |  23 +-
 northd/northd.c          | 238 ------------------
 northd/northd.h          |   6 -
 tests/ovn-northd.at      | 164 ++++++++++---
 7 files changed, 705 insertions(+), 428 deletions(-)

Comments

Dumitru Ceara Jan. 19, 2024, 12:19 p.m. UTC | #1
On 1/11/24 16:34, numans@ovn.org wrote:
> From: Numan Siddique <numans@ovn.org>
> 
> Any changes to northd engine node due to load balancers
> are now handled in 'sync_to_sb_lb' node to sync the changed
> load balancers to SB load balancers.
> 
> The logic to sync the SB load balancers is changed a bit and it
> now mimics the SB lflow sync.
> 
> Below are the scale testing results done with all the patches applied
> in this series using ovn-heater.  The test ran the scenario  -
> ocp-500-density-heavy.yml [1].
> 
> The resuts are:
> 
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> 			Min (s)		Median (s)	90%ile (s)	99%ile (s)	Max (s)		Mean (s)	Total (s)	Count	Failed
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> Iteration Total		0.136883	1.129016	1.192001	1.204167	1.212728	0.665017	83.127099	125	0
> Namespace.add_ports	0.005216	0.005736	0.007034	0.015486	0.018978	0.006211	0.776373	125	0
> WorkerNode.bind_port	0.035030	0.046082	0.052469	0.058293	0.060311	0.045973	11.493259	250	0
> WorkerNode.ping_port	0.005057	0.006727	1.047692	1.069253	1.071336	0.266896	66.724094	250	0
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> 
> The results with the present main [2] are:
> 
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> 			Min (s)		Median (s)	90%ile (s)	99%ile (s)	Max (s)		Mean (s)	Total (s)	Count	Failed
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> Iteration Total		0.135491	2.223805	3.311270	3.339078	3.345346	1.729172	216.146495	125	0
> Namespace.add_ports	0.005380	0.005744	0.006819	0.018773	0.020800	0.006292	0.786532	125	0
> WorkerNode.bind_port	0.034179	0.046055	0.053488	0.058801	0.071043	0.046117	11.529311	250	0
> WorkerNode.ping_port	0.004956	0.006952	3.086952	3.191743	3.192807	0.791544	197.886026	250	0
> -------------------------------------------------------------------------------------------------------------------------------------------------------
> 
> [1] - https://github.com/ovn-org/ovn-heater/blob/main/test-scenarios/ocp-500-density-heavy.yml
> [2] - 2a12cda890a7("controller, northd: Wait for cleanup before replying to exit")
> 
> Signed-off-by: Numan Siddique <numans@ovn.org>
> ---

Hi Numan,

I only have a few minor comments, please see inline.  With those
addressed I think the patch is correct:

Acked-by: Dumitru Ceara <dceara@redhat.com>

Thanks,
Dumitru

>  northd/en-sync-sb.c      | 505 +++++++++++++++++++++++++++++++++++++--
>  northd/inc-proc-northd.c |   1 +
>  northd/lflow-mgr.c       | 196 ++++++---------
>  northd/lflow-mgr.h       |  23 +-
>  northd/northd.c          | 238 ------------------
>  northd/northd.h          |   6 -
>  tests/ovn-northd.at      | 164 ++++++++++---
>  7 files changed, 705 insertions(+), 428 deletions(-)
> 
> diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
> index d39cbbf2e6..80f3621bb9 100644
> --- a/northd/en-sync-sb.c
> +++ b/northd/en-sync-sb.c
> @@ -18,17 +18,21 @@
>  #include <stdlib.h>
>  #include <stdio.h>
>  
> +/* OVS includes. */
>  #include "lib/svec.h"
>  #include "openvswitch/util.h"
>  
> +/* OVN includes. */
>  #include "en-lr-nat.h"
>  #include "en-lr-stateful.h"
>  #include "en-sync-sb.h"
> +#include "lb.h"
>  #include "lib/inc-proc-eng.h"
>  #include "lib/lb.h"
>  #include "lib/ovn-nb-idl.h"
>  #include "lib/ovn-sb-idl.h"
>  #include "lib/ovn-util.h"
> +#include "lflow-mgr.h"
>  #include "northd.h"
>  
>  #include "openvswitch/vlog.h"
> @@ -51,6 +55,40 @@ static void build_port_group_address_set(const struct nbrec_port_group *,
>                                           struct svec *ipv4_addrs,
>                                           struct svec *ipv6_addrs);
>  
> +struct sb_lb_table;
> +struct sb_lb_record;

These are defined just below.  Let's move the prototypes after the
struct definition?

> +static void sb_lb_table_init(struct sb_lb_table *);
> +static void sb_lb_table_clear(struct sb_lb_table *);
> +static void sb_lb_table_destroy(struct sb_lb_table *);
> +
> +static struct sb_lb_record *sb_lb_table_find(struct hmap *sb_lbs,
> +                                             const struct uuid *);
> +static void sb_lb_table_build_and_sync(struct sb_lb_table *,
> +                                struct ovsdb_idl_txn *ovnsb_txn,
> +                                const struct sbrec_load_balancer_table *,
> +                                const struct sbrec_logical_dp_group_table *,
> +                                struct hmap *lb_dps_map,
> +                                struct ovn_datapaths *ls_datapaths,
> +                                struct ovn_datapaths *lr_datapaths,
> +                                struct chassis_features *);

Nit: indentation

> +static bool sync_sb_lb_record(struct sb_lb_record *,
> +                              const struct sbrec_load_balancer *,
> +                              const struct sbrec_logical_dp_group_table *,
> +                              struct sb_lb_table *,
> +                              struct ovsdb_idl_txn *ovnsb_txn,
> +                              struct ovn_datapaths *ls_datapaths,
> +                              struct ovn_datapaths *lr_datapaths,
> +                              struct chassis_features *);
> +static bool sync_changed_lbs(struct sb_lb_table *,
> +                             struct ovsdb_idl_txn *ovnsb_txn,
> +                             const struct sbrec_load_balancer_table *,
> +                             const struct sbrec_logical_dp_group_table *,
> +                             struct tracked_lbs *,
> +                             struct ovn_datapaths *ls_datapaths,
> +                             struct ovn_datapaths *lr_datapaths,
> +                             struct chassis_features *);
> +static bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
> +
>  void *
>  en_sync_to_sb_init(struct engine_node *node OVS_UNUSED,
>                  struct engine_arg *arg OVS_UNUSED)
> @@ -206,49 +244,99 @@ sync_to_sb_addr_set_nb_port_group_handler(struct engine_node *node,
>  /* sync_to_sb_lb engine node functions.
>   * This engine node syncs the SB load balancers.
>   */
> +struct sb_lb_record {
> +    struct hmap_node key_node;  /* Index on 'nblb->header_.uuid'. */
> +
> +    struct ovn_lb_datapaths *lb_dps;
> +    const struct sbrec_load_balancer *sbrec_lb;
> +    struct ovn_dp_group *ls_dpg;
> +    struct ovn_dp_group *lr_dpg;
> +    struct uuid sb_uuid;
> +};
> +
> +struct sb_lb_table {
> +    struct hmap entries; /* Stores struct sb_lb_record. */
> +    struct hmap ls_dp_groups;
> +    struct hmap lr_dp_groups;
> +};
> +
> +struct ed_type_sync_to_sb_lb_data {
> +    struct sb_lb_table sb_lbs;
> +};
> +
>  void *
>  en_sync_to_sb_lb_init(struct engine_node *node OVS_UNUSED,
>                        struct engine_arg *arg OVS_UNUSED)
>  {
> -    return NULL;
> +    struct ed_type_sync_to_sb_lb_data *data = xzalloc(sizeof *data);
> +    sb_lb_table_init(&data->sb_lbs);
> +
> +    return data;
>  }
>  
>  void
> -en_sync_to_sb_lb_run(struct engine_node *node, void *data OVS_UNUSED)
> +en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
>  {
> +    struct northd_data *northd_data = engine_get_input_data("northd", node);
>      const struct sbrec_load_balancer_table *sb_load_balancer_table =
>          EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> +    const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
> +        EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
>      const struct engine_context *eng_ctx = engine_get_context();
> -    struct northd_data *northd_data = engine_get_input_data("northd", node);
> +    struct ed_type_sync_to_sb_lb_data *data = data_;
> +
> +    sb_lb_table_clear(&data->sb_lbs);
> +    sb_lb_table_build_and_sync(&data->sb_lbs, eng_ctx->ovnsb_idl_txn,
> +                               sb_load_balancer_table,
> +                               sb_dpgrp_table,
> +                               &northd_data->lb_datapaths_map,
> +                               &northd_data->ls_datapaths,
> +                               &northd_data->lr_datapaths,
> +                               &northd_data->features);
>  
> -    sync_lbs(eng_ctx->ovnsb_idl_txn, sb_load_balancer_table,
> -             &northd_data->ls_datapaths, &northd_data->lr_datapaths,
> -             &northd_data->lb_datapaths_map, &northd_data->features);
>      engine_set_node_state(node, EN_UPDATED);
>  }
>  
>  void
> -en_sync_to_sb_lb_cleanup(void *data OVS_UNUSED)
> +en_sync_to_sb_lb_cleanup(void *data_)
>  {
> -
> +    struct ed_type_sync_to_sb_lb_data *data = data_;
> +    sb_lb_table_destroy(&data->sb_lbs);
>  }
>  
>  bool
> -sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
> +sync_to_sb_lb_northd_handler(struct engine_node *node, void *data_)
>  {
>      struct northd_data *nd = engine_get_input_data("northd", node);
>  
> -    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. */
> +    if (!northd_has_tracked_data(&nd->trk_data)) {
> +        /* Return false if no tracking data. */
>          return false;
>      }
>  
> +    if (!northd_has_lbs_in_tracked_data(&nd->trk_data)) {
> +        return true;
> +    }
>  
> -    /* There are only NB LSP related changes and these can be safely
> -     * ignore and returned true.  However in case the northd engine
> -     * tracking data includes other changes, we need to do additional
> -     * checks before safely ignoring. */
> +    const struct engine_context *eng_ctx = engine_get_context();
> +    if (!eng_ctx->ovnsb_idl_txn) {
> +        return false;
> +    }

This will never be hit.  AFAICT we run the incremental processing engine
only if we can commit to the SB, i.e., ovnsb_idl_txn != NULL.

> +
> +    const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
> +        EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
> +    const struct sbrec_load_balancer_table *sb_lb_table =
> +        EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
> +    struct ed_type_sync_to_sb_lb_data *data = data_;
> +
> +    if (!sync_changed_lbs(&data->sb_lbs, eng_ctx->ovnsb_idl_txn, sb_lb_table,
> +                          sb_dpgrp_table, &nd->trk_data.trk_lbs,
> +                          &nd->ls_datapaths, &nd->lr_datapaths,
> +                          &nd->features)) {
> +        return false;
> +    }
> +
> +    engine_set_node_state(node, EN_UPDATED);
>      return true;
>  }
>  
> @@ -519,3 +607,388 @@ sb_address_set_lookup_by_name(struct ovsdb_idl_index *sbrec_addr_set_by_name,
>  
>      return retval;
>  }
> +
> +/* static functions related to sync_to_sb_lb */
> +
> +static void
> +sb_lb_table_init(struct sb_lb_table *sb_lbs)
> +{
> +    hmap_init(&sb_lbs->entries);
> +    ovn_dp_groups_init(&sb_lbs->ls_dp_groups);
> +    ovn_dp_groups_init(&sb_lbs->lr_dp_groups);
> +}
> +
> +static void
> +sb_lb_table_clear(struct sb_lb_table *sb_lbs)
> +{
> +    struct sb_lb_record *sb_lb;
> +    HMAP_FOR_EACH_POP (sb_lb, key_node, &sb_lbs->entries) {
> +        free(sb_lb);
> +    }
> +
> +    ovn_dp_groups_clear(&sb_lbs->ls_dp_groups);
> +    ovn_dp_groups_clear(&sb_lbs->lr_dp_groups);
> +}
> +
> +static void
> +sb_lb_table_destroy(struct sb_lb_table *sb_lbs)
> +{
> +    sb_lb_table_clear(sb_lbs);
> +    hmap_destroy(&sb_lbs->entries);
> +    ovn_dp_groups_destroy(&sb_lbs->ls_dp_groups);
> +    ovn_dp_groups_destroy(&sb_lbs->lr_dp_groups);
> +}
> +
> +static struct sb_lb_record *
> +sb_lb_table_find(struct hmap *sb_lbs, const struct uuid *lb_uuid)
> +{
> +    struct sb_lb_record *sb_lb;
> +    HMAP_FOR_EACH_WITH_HASH (sb_lb, key_node, uuid_hash(lb_uuid),
> +                             sb_lbs) {
> +        if (uuid_equals(&sb_lb->lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
> +            return sb_lb;
> +        }
> +    }
> +
> +    return NULL;
> +}
> +
> +static void
> +sb_lb_table_build_and_sync(struct sb_lb_table *sb_lbs,
> +                    struct ovsdb_idl_txn *ovnsb_txn,
> +                    const struct sbrec_load_balancer_table *sb_lb_table,
> +                    const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
> +                    struct hmap *lb_dps_map,
> +                    struct ovn_datapaths *ls_datapaths,
> +                    struct ovn_datapaths *lr_datapaths,
> +                    struct chassis_features *chassis_features)
> +{
> +    struct hmap tmp_sb_lbs = HMAP_INITIALIZER(&tmp_sb_lbs);
> +    struct ovn_lb_datapaths *lb_dps;
> +    struct sb_lb_record *sb_lb;
> +
> +    HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
> +        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
> +            continue;
> +        }
> +
> +        sb_lb = xzalloc(sizeof *sb_lb);
> +        sb_lb->lb_dps = lb_dps;
> +        hmap_insert(&tmp_sb_lbs, &sb_lb->key_node,
> +                    uuid_hash(&lb_dps->lb->nlb->header_.uuid));
> +    }
> +
> +    const struct sbrec_load_balancer *sbrec_lb;
> +    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_SAFE (sbrec_lb,
> +                                             sb_lb_table) {
> +        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
> +        struct uuid lb_uuid;
> +        if (!nb_lb_uuid || !uuid_from_string(&lb_uuid, nb_lb_uuid)) {
> +            sbrec_load_balancer_delete(sbrec_lb);
> +            continue;
> +        }
> +
> +        sb_lb = sb_lb_table_find(&tmp_sb_lbs, &lb_uuid);
> +        if (sb_lb) {
> +            sb_lb->sbrec_lb = sbrec_lb;
> +            bool success = sync_sb_lb_record(sb_lb, sbrec_lb, sb_dpgrp_table,
> +                                             sb_lbs, ovnsb_txn, ls_datapaths,
> +                                             lr_datapaths, chassis_features);
> +            /* Since we are rebuilding and syncing,  sync_sb_lb_record should
> +             * not return false. */
> +            ovs_assert(success);
> +
> +            hmap_remove(&tmp_sb_lbs, &sb_lb->key_node);
> +            hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
> +                        uuid_hash(&sb_lb->lb_dps->lb->nlb->header_.uuid));
> +        } else {
> +            sbrec_load_balancer_delete(sbrec_lb);
> +        }
> +    }
> +
> +    HMAP_FOR_EACH_POP (sb_lb, key_node, &tmp_sb_lbs) {
> +        bool success = sync_sb_lb_record(sb_lb, NULL, sb_dpgrp_table, sb_lbs,
> +                                         ovnsb_txn, ls_datapaths, lr_datapaths,
> +                                         chassis_features);
> +        /* Since we are rebuilding and syncing,  sync_sb_lb_record should not
> +         * return false. */
> +        ovs_assert(success);
> +
> +        hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
> +                    uuid_hash(&sb_lb->lb_dps->lb->nlb->header_.uuid));
> +    }
> +
> +    hmap_destroy(&tmp_sb_lbs);
> +}
> +
> +static bool
> +sync_sb_lb_record(struct sb_lb_record *sb_lb,
> +                  const struct sbrec_load_balancer *sbrec_lb,
> +                  const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
> +                  struct sb_lb_table *sb_lbs,
> +                  struct ovsdb_idl_txn *ovnsb_txn,
> +                  struct ovn_datapaths *ls_datapaths,
> +                  struct ovn_datapaths *lr_datapaths,
> +                  struct chassis_features *chassis_features)
> +{
> +    struct sbrec_logical_dp_group *sbrec_ls_dp_group = NULL;
> +    struct sbrec_logical_dp_group *sbrec_lr_dp_group = NULL;
> +    const struct ovn_lb_datapaths *lb_dps;
> +    struct ovn_dp_group *pre_sync_ls_dpg;
> +    struct ovn_dp_group *pre_sync_lr_dpg;
> +
> +    lb_dps = sb_lb->lb_dps;
> +    pre_sync_ls_dpg = sb_lb->ls_dpg;
> +    pre_sync_lr_dpg = sb_lb->lr_dpg;
> +
> +    if (!sbrec_lb) {
> +        sb_lb->sb_uuid = uuid_random();
> +        sbrec_lb =  sbrec_load_balancer_insert_persist_uuid(ovnsb_txn,
> +                                                            &sb_lb->sb_uuid);
> +        char *lb_id = xasprintf(
> +            UUID_FMT, UUID_ARGS(&lb_dps->lb->nlb->header_.uuid));
> +        const struct smap external_ids =
> +            SMAP_CONST1(&external_ids, "lb_id", lb_id);
> +        sbrec_load_balancer_set_external_ids(sbrec_lb, &external_ids);
> +        free(lb_id);
> +    } else {
> +        sb_lb->sb_uuid = sbrec_lb->header_.uuid;
> +        sbrec_ls_dp_group =
> +            chassis_features->ls_dpg_column
> +            ? sbrec_lb->ls_datapath_group
> +            : sbrec_lb->datapath_group; /* deprecated */
> +
> +        sbrec_lr_dp_group = sbrec_lb->lr_datapath_group;
> +    }
> +
> +    if (lb_dps->n_nb_ls) {
> +        sb_lb->ls_dpg = ovn_dp_group_get(&sb_lbs->ls_dp_groups,
> +                                         lb_dps->n_nb_ls,
> +                                         lb_dps->nb_ls_map,
> +                                         ods_size(ls_datapaths));
> +        if (sb_lb->ls_dpg) {
> +            /* Update the dpg's sb dp_group. */
> +            sb_lb->ls_dpg->dp_group =
> +                sbrec_logical_dp_group_table_get_for_uuid(sb_dpgrp_table,
> +                                                    &sb_lb->ls_dpg->dpg_uuid);
> +            if (!sb_lb->ls_dpg->dp_group) {
> +                /* Ideally this should not happen.  But it can still happen
> +                 * due to 2 reasons:
> +                 * 1. There is a bug in the dp_group management.  We should
> +                 *    perhaps assert here.
> +                 * 2. A User or CMS may delete the logical_dp_groups in SB DB
> +                 *    or clear the SB:Load_balancer.ls_datapath_group column
> +                 *    (intentionally or accidentally)
> +                 *
> +                 * Because of (2) it is better to return false instead of
> +                 * assert,so that we recover from th inconsistent SB DB.
> +                 */
> +                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +                VLOG_WARN_RL(&rl, "SB Load balancer [%s]'s ls_dp_group column "
> +                            "is not set (which is unexpected).  It should "
> +                            "have been referencing the dp group ["UUID_FMT"]",
> +                            sb_lb->lb_dps->lb->nlb->name,
> +                            UUID_ARGS(&sb_lb->ls_dpg->dpg_uuid));
> +                return false;
> +            }
> +        } else {
> +            sb_lb->ls_dpg = ovn_dp_group_create(
> +                ovnsb_txn, &sb_lbs->ls_dp_groups, sbrec_ls_dp_group,
> +                lb_dps->n_nb_ls, lb_dps->nb_ls_map,
> +                ods_size(ls_datapaths), true,
> +                ls_datapaths, lr_datapaths);
> +        }
> +
> +        if (chassis_features->ls_dpg_column) {
> +            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb,
> +                                                      sb_lb->ls_dpg->dp_group);
> +            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
> +        } else {
> +            /* datapath_group column is deprecated. */
> +            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
> +            sbrec_load_balancer_set_datapath_group(sbrec_lb,
> +                                                   sb_lb->ls_dpg->dp_group);
> +
> +        }
> +    } else {
> +        sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
> +        sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
> +    }
> +
> +
> +    if (lb_dps->n_nb_lr) {
> +        sb_lb->lr_dpg = ovn_dp_group_get(&sb_lbs->lr_dp_groups,
> +                                         lb_dps->n_nb_lr,
> +                                         lb_dps->nb_lr_map,
> +                                         ods_size(lr_datapaths));
> +        if (sb_lb->lr_dpg) {
> +            /* Update the dpg's sb dp_group. */
> +            sb_lb->lr_dpg->dp_group =
> +                sbrec_logical_dp_group_table_get_for_uuid(sb_dpgrp_table,
> +                                                    &sb_lb->lr_dpg->dpg_uuid);
> +            if (!sb_lb->lr_dpg->dp_group) {
> +                /* Ideally this should not happen.  But it can still happen
> +                 * due to 2 reasons:
> +                 * 1. There is a bug in the dp_group management.  We should
> +                 *    perhaps assert here.
> +                 * 2. A User or CMS may delete the logical_dp_groups in SB DB
> +                 *    or clear the SB:Load_balancer.lr_datapath_group column
> +                 *    (intentionally or accidentally)
> +                 *
> +                 * Because of (2) it is better to return false instead of
> +                 * assert,so that we recover from th inconsistent SB DB.
> +                 */
> +                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
> +                VLOG_WARN_RL(&rl, "SB Load balancer [%s]'s lr_dp_group column "
> +                            "is not set (which is unexpected).  It should "
> +                            "have been referencing the dp group ["UUID_FMT"]",
> +                            sb_lb->lb_dps->lb->nlb->name,
> +                            UUID_ARGS(&sb_lb->lr_dpg->dpg_uuid));
> +                return false;
> +            }
> +        } else {
> +            sb_lb->lr_dpg = ovn_dp_group_create(
> +                ovnsb_txn, &sb_lbs->lr_dp_groups, sbrec_lr_dp_group,
> +                lb_dps->n_nb_lr, lb_dps->nb_lr_map,
> +                ods_size(lr_datapaths), false,
> +                ls_datapaths, lr_datapaths);
> +        }
> +
> +        sbrec_load_balancer_set_lr_datapath_group(sbrec_lb,
> +                                                  sb_lb->lr_dpg->dp_group);
> +    } else {
> +        sbrec_load_balancer_set_lr_datapath_group(sbrec_lb, NULL);
> +    }
> +
> +    if (pre_sync_ls_dpg != sb_lb->ls_dpg) {
> +        if (sb_lb->ls_dpg) {
> +            inc_ovn_dp_group_ref(sb_lb->ls_dpg);
> +        }
> +        if (pre_sync_ls_dpg) {
> +            dec_ovn_dp_group_ref(&sb_lbs->ls_dp_groups, pre_sync_ls_dpg);
> +        }
> +    }
> +
> +    if (pre_sync_lr_dpg != sb_lb->lr_dpg) {
> +        if (sb_lb->lr_dpg) {
> +            inc_ovn_dp_group_ref(sb_lb->lr_dpg);
> +        }
> +        if (pre_sync_lr_dpg) {
> +            dec_ovn_dp_group_ref(&sb_lbs->lr_dp_groups, pre_sync_lr_dpg);
> +        }
> +    }
> +
> +    /* Update columns. */
> +    sbrec_load_balancer_set_name(sbrec_lb, lb_dps->lb->nlb->name);
> +    sbrec_load_balancer_set_vips(sbrec_lb,
> +                                 ovn_northd_lb_get_vips(lb_dps->lb));
> +    sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
> +
> +    /* Store the fact that northd provides the original (destination IP +
> +     * transport port) tuple.
> +     */
> +    struct smap options;
> +    smap_clone(&options, &lb_dps->lb->nlb->options);
> +    smap_replace(&options, "hairpin_orig_tuple", "true");
> +    sbrec_load_balancer_set_options(sbrec_lb, &options);
> +    /* Clearing 'datapaths' column, since 'dp_group' is in use. */
> +    sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
> +    smap_destroy(&options);
> +
> +    return true;
> +}
> +
> +static bool
> +sync_changed_lbs(struct sb_lb_table *sb_lbs,
> +                 struct ovsdb_idl_txn *ovnsb_txn,
> +                 const struct sbrec_load_balancer_table *sb_lb_table,
> +                 const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
> +                 struct tracked_lbs *trk_lbs,
> +                 struct ovn_datapaths *ls_datapaths,
> +                 struct ovn_datapaths *lr_datapaths,
> +                 struct chassis_features *chassis_features)
> +{
> +    struct ovn_lb_datapaths *lb_dps;
> +    struct hmapx_node *hmapx_node;
> +    struct sb_lb_record *sb_lb;
> +
> +    HMAPX_FOR_EACH (hmapx_node, &trk_lbs->deleted) {
> +        lb_dps = hmapx_node->data;
> +
> +        sb_lb = sb_lb_table_find(&sb_lbs->entries,
> +                                 &lb_dps->lb->nlb->header_.uuid);
> +        if (sb_lb) {
> +            const struct sbrec_load_balancer *sbrec_lb =
> +                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
> +                                                       &sb_lb->sb_uuid);
> +            if (sbrec_lb) {
> +                sbrec_load_balancer_delete(sbrec_lb);
> +            }
> +
> +            hmap_remove(&sb_lbs->entries, &sb_lb->key_node);
> +            free(sb_lb);
> +        }
> +    }
> +
> +    HMAPX_FOR_EACH (hmapx_node, &trk_lbs->crupdated) {
> +        lb_dps = hmapx_node->data;
> +
> +        sb_lb = sb_lb_table_find(&sb_lbs->entries,
> +                                 &lb_dps->lb->nlb->header_.uuid);
> +
> +        if (!sb_lb && !lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
> +            continue;
> +        }
> +
> +        if (!sb_lb) {
> +            sb_lb = xzalloc(sizeof *sb_lb);
> +            sb_lb->lb_dps = lb_dps;
> +            hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
> +                        uuid_hash(&lb_dps->lb->nlb->header_.uuid));
> +        } else {
> +            sb_lb->sbrec_lb =
> +                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
> +                                                       &sb_lb->sb_uuid);
> +        }
> +
> +        if (sb_lb && !lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
> +            const struct sbrec_load_balancer *sbrec_lb =
> +                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
> +                                                       &sb_lb->sb_uuid);
> +            if (sbrec_lb) {
> +                sbrec_load_balancer_delete(sbrec_lb);
> +            }
> +
> +            hmap_remove(&sb_lbs->entries, &sb_lb->key_node);
> +            free(sb_lb);
> +        }
> +
> +        if (!sync_sb_lb_record(sb_lb, sb_lb->sbrec_lb, sb_dpgrp_table, sb_lbs,
> +                               ovnsb_txn, ls_datapaths, lr_datapaths,
> +                               chassis_features)) {
> +            return false;
> +        }
> +    }
> +
> +    return true;
> +}
> +
> +static bool
> +check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
> +{
> +    struct sset existing_nb_lb_uuids =
> +        SSET_INITIALIZER(&existing_nb_lb_uuids);
> +    const struct sbrec_load_balancer *sbrec_lb;
> +    bool duplicates = false;
> +
> +    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
> +        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
> +        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
> +            duplicates = true;
> +            break;
> +        }
> +    }
> +
> +    sset_destroy(&existing_nb_lb_uuids);
> +    return duplicates;
> +}
> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> index 40a9e5e06c..d215c7792b 100644
> --- a/northd/inc-proc-northd.c
> +++ b/northd/inc-proc-northd.c
> @@ -264,6 +264,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
>                       sync_to_sb_lb_northd_handler);
>      engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
>                       sync_to_sb_lb_sb_load_balancer);
> +    engine_add_input(&en_sync_to_sb_lb, &en_sb_logical_dp_group, NULL);
>  
>      engine_add_input(&en_sync_to_sb_pb, &en_northd,
>                       sync_to_sb_pb_northd_handler);
> diff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c
> index d81b13a25c..d09dc7acb5 100644
> --- a/northd/lflow-mgr.c
> +++ b/northd/lflow-mgr.c
> @@ -70,21 +70,6 @@ static struct ovs_mutex *lflow_hash_lock(const struct hmap *lflow_table,
>                                           uint32_t hash);
>  static void lflow_hash_unlock(struct ovs_mutex *hash_lock);
>  
> -static struct ovn_dp_group *ovn_dp_group_get(
> -    struct hmap *dp_groups, size_t desired_n,
> -    const unsigned long *desired_bitmap,
> -    size_t bitmap_len);
> -static struct ovn_dp_group *ovn_dp_group_create(
> -    struct ovsdb_idl_txn *ovnsb_txn, struct hmap *dp_groups,
> -    struct sbrec_logical_dp_group *, size_t desired_n,
> -    const unsigned long *desired_bitmap,
> -    size_t bitmap_len, bool is_switch,
> -    const struct ovn_datapaths *ls_datapaths,
> -    const struct ovn_datapaths *lr_datapaths);
> -static struct ovn_dp_group *ovn_dp_group_get(
> -    struct hmap *dp_groups, size_t desired_n,
> -    const unsigned long *desired_bitmap,
> -    size_t bitmap_len);
>  static struct sbrec_logical_dp_group *ovn_sb_insert_or_update_logical_dp_group(
>      struct ovsdb_idl_txn *ovnsb_txn,
>      struct sbrec_logical_dp_group *,
> @@ -564,31 +549,81 @@ lflow_table_add_lflow_default_drop(struct lflow_table *lflow_table,
>                            where, lflow_ref);
>  }
>  
> -/* Given a desired bitmap, finds a datapath group in 'dp_groups'.  If it
> - * doesn't exist, creates a new one and adds it to 'dp_groups'.
> +struct ovn_dp_group *
> +ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
> +                 const unsigned long *desired_bitmap,
> +                 size_t bitmap_len)
> +{
> +    uint32_t hash;
> +
> +    hash = hash_int(desired_n, 0);
> +    return ovn_dp_group_find(dp_groups, desired_bitmap, bitmap_len, hash);
> +}
> +
> +/* Creates a new datapath group and adds it to 'dp_groups'.
>   * If 'sb_group' is provided, function will try to re-use this group by
> - * either taking it directly, or by modifying, if it's not already in use. */
> + * either taking it directly, or by modifying, if it's not already in use.
> + * Caller should first call ovn_dp_group_get() before calling this function. */
>  struct ovn_dp_group *
> -ovn_dp_group_get_or_create(struct ovsdb_idl_txn *ovnsb_txn,
> -                           struct hmap *dp_groups,
> -                           struct sbrec_logical_dp_group *sb_group,
> -                           size_t desired_n,
> -                           const unsigned long *desired_bitmap,
> -                           size_t bitmap_len,
> -                           bool is_switch,
> -                           const struct ovn_datapaths *ls_datapaths,
> -                           const struct ovn_datapaths *lr_datapaths)
> +ovn_dp_group_create(struct ovsdb_idl_txn *ovnsb_txn,
> +                    struct hmap *dp_groups,
> +                    struct sbrec_logical_dp_group *sb_group,
> +                    size_t desired_n,
> +                    const unsigned long *desired_bitmap,
> +                    size_t bitmap_len,
> +                    bool is_switch,
> +                    const struct ovn_datapaths *ls_datapaths,
> +                    const struct ovn_datapaths *lr_datapaths)
>  {
>      struct ovn_dp_group *dpg;
>  
> -    dpg = ovn_dp_group_get(dp_groups, desired_n, desired_bitmap, bitmap_len);
> -    if (dpg) {
> -        return dpg;
> +    bool update_dp_group = false, can_modify = false;
> +    unsigned long *dpg_bitmap;
> +    size_t i, n = 0;
> +
> +    dpg_bitmap = sb_group ? bitmap_allocate(bitmap_len) : NULL;
> +    for (i = 0; sb_group && i < sb_group->n_datapaths; i++) {
> +        struct ovn_datapath *datapath_od;
> +
> +        datapath_od = ovn_datapath_from_sbrec(
> +                        ls_datapaths ? &ls_datapaths->datapaths : NULL,
> +                        lr_datapaths ? &lr_datapaths->datapaths : NULL,
> +                        sb_group->datapaths[i]);
> +        if (!datapath_od || ovn_datapath_is_stale(datapath_od)) {
> +            break;
> +        }
> +        bitmap_set1(dpg_bitmap, datapath_od->index);
> +        n++;
> +    }
> +    if (!sb_group || i != sb_group->n_datapaths) {
> +        /* No group or stale group.  Not going to be used. */
> +        update_dp_group = true;
> +        can_modify = true;
> +    } else if (!bitmap_equal(dpg_bitmap, desired_bitmap, bitmap_len)) {
> +        /* The group in Sb is different. */
> +        update_dp_group = true;
> +        /* We can modify existing group if it's not already in use. */
> +        can_modify = !ovn_dp_group_find(dp_groups, dpg_bitmap,
> +                                        bitmap_len, hash_int(n, 0));
>      }
>  
> -    return ovn_dp_group_create(ovnsb_txn, dp_groups, sb_group, desired_n,
> -                               desired_bitmap, bitmap_len, is_switch,
> -                               ls_datapaths, lr_datapaths);
> +    bitmap_free(dpg_bitmap);
> +
> +    dpg = xzalloc(sizeof *dpg);
> +    dpg->bitmap = bitmap_clone(desired_bitmap, bitmap_len);
> +    if (!update_dp_group) {
> +        dpg->dp_group = sb_group;
> +    } else {
> +        dpg->dp_group = ovn_sb_insert_or_update_logical_dp_group(
> +                            ovnsb_txn,
> +                            can_modify ? sb_group : NULL,
> +                            desired_bitmap,
> +                            is_switch ? ls_datapaths : lr_datapaths);
> +    }
> +    dpg->dpg_uuid = dpg->dp_group->header_.uuid;
> +    hmap_insert(dp_groups, &dpg->node, hash_int(desired_n, 0));
> +
> +    return dpg;
>  }
>  
>  void
> @@ -987,24 +1022,6 @@ ovn_dp_group_find(const struct hmap *dp_groups,
>      return NULL;
>  }
>  
> -static void
> -inc_ovn_dp_group_ref(struct ovn_dp_group *dpg)
> -{
> -    dpg->refcnt++;
> -}
> -
> -static void
> -dec_ovn_dp_group_ref(struct hmap *dp_groups, struct ovn_dp_group *dpg)
> -{
> -    dpg->refcnt--;
> -
> -    if (!dpg->refcnt) {
> -        hmap_remove(dp_groups, &dpg->node);
> -        free(dpg->bitmap);
> -        free(dpg);
> -    }
> -}
> -
>  static struct sbrec_logical_dp_group *
>  ovn_sb_insert_or_update_logical_dp_group(
>                              struct ovsdb_idl_txn *ovnsb_txn,
> @@ -1031,83 +1048,6 @@ ovn_sb_insert_or_update_logical_dp_group(
>      return dp_group;
>  }
>  
> -static struct ovn_dp_group *
> -ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
> -                 const unsigned long *desired_bitmap,
> -                 size_t bitmap_len)
> -{
> -    uint32_t hash;
> -
> -    hash = hash_int(desired_n, 0);
> -    return ovn_dp_group_find(dp_groups, desired_bitmap, bitmap_len, hash);
> -}
> -
> -/* Creates a new datapath group and adds it to 'dp_groups'.
> - * If 'sb_group' is provided, function will try to re-use this group by
> - * either taking it directly, or by modifying, if it's not already in use.
> - * Caller should first call ovn_dp_group_get() before calling this function. */
> -static struct ovn_dp_group *
> -ovn_dp_group_create(struct ovsdb_idl_txn *ovnsb_txn,
> -                    struct hmap *dp_groups,
> -                    struct sbrec_logical_dp_group *sb_group,
> -                    size_t desired_n,
> -                    const unsigned long *desired_bitmap,
> -                    size_t bitmap_len,
> -                    bool is_switch,
> -                    const struct ovn_datapaths *ls_datapaths,
> -                    const struct ovn_datapaths *lr_datapaths)
> -{
> -    struct ovn_dp_group *dpg;
> -
> -    bool update_dp_group = false, can_modify = false;
> -    unsigned long *dpg_bitmap;
> -    size_t i, n = 0;
> -
> -    dpg_bitmap = sb_group ? bitmap_allocate(bitmap_len) : NULL;
> -    for (i = 0; sb_group && i < sb_group->n_datapaths; i++) {
> -        struct ovn_datapath *datapath_od;
> -
> -        datapath_od = ovn_datapath_from_sbrec(
> -                        ls_datapaths ? &ls_datapaths->datapaths : NULL,
> -                        lr_datapaths ? &lr_datapaths->datapaths : NULL,
> -                        sb_group->datapaths[i]);
> -        if (!datapath_od || ovn_datapath_is_stale(datapath_od)) {
> -            break;
> -        }
> -        bitmap_set1(dpg_bitmap, datapath_od->index);
> -        n++;
> -    }
> -    if (!sb_group || i != sb_group->n_datapaths) {
> -        /* No group or stale group.  Not going to be used. */
> -        update_dp_group = true;
> -        can_modify = true;
> -    } else if (!bitmap_equal(dpg_bitmap, desired_bitmap, bitmap_len)) {
> -        /* The group in Sb is different. */
> -        update_dp_group = true;
> -        /* We can modify existing group if it's not already in use. */
> -        can_modify = !ovn_dp_group_find(dp_groups, dpg_bitmap,
> -                                        bitmap_len, hash_int(n, 0));
> -    }
> -
> -    bitmap_free(dpg_bitmap);
> -
> -    dpg = xzalloc(sizeof *dpg);
> -    dpg->bitmap = bitmap_clone(desired_bitmap, bitmap_len);
> -    if (!update_dp_group) {
> -        dpg->dp_group = sb_group;
> -    } else {
> -        dpg->dp_group = ovn_sb_insert_or_update_logical_dp_group(
> -                            ovnsb_txn,
> -                            can_modify ? sb_group : NULL,
> -                            desired_bitmap,
> -                            is_switch ? ls_datapaths : lr_datapaths);
> -    }
> -    dpg->dpg_uuid = dpg->dp_group->header_.uuid;
> -    hmap_insert(dp_groups, &dpg->node, hash_int(desired_n, 0));
> -
> -    return dpg;
> -}
> -
>  /* Adds an OVN datapath to a datapath group of existing logical flow.
>   * Version to use when hash bucket locking is NOT required or the corresponding
>   * hash lock is already taken. */
> diff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h
> index f215891b97..00554ef78a 100644
> --- a/northd/lflow-mgr.h
> +++ b/northd/lflow-mgr.h
> @@ -157,7 +157,10 @@ ovn_dp_groups_init(struct hmap *dp_groups)
>  
>  void ovn_dp_groups_clear(struct hmap *dp_groups);
>  void ovn_dp_groups_destroy(struct hmap *dp_groups);
> -struct ovn_dp_group *ovn_dp_group_get_or_create(
> +struct ovn_dp_group *ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
> +                                      const unsigned long *desired_bitmap,
> +                                      size_t bitmap_len);
> +struct ovn_dp_group *ovn_dp_group_create(
>      struct ovsdb_idl_txn *ovnsb_txn, struct hmap *dp_groups,
>      struct sbrec_logical_dp_group *sb_group,
>      size_t desired_n, const unsigned long *desired_bitmap,
> @@ -165,4 +168,22 @@ struct ovn_dp_group *ovn_dp_group_get_or_create(
>      const struct ovn_datapaths *ls_datapaths,
>      const struct ovn_datapaths *lr_datapaths);
>  
> +static inline void
> +inc_ovn_dp_group_ref(struct ovn_dp_group *dpg)
> +{
> +    dpg->refcnt++;
> +}
> +
> +static inline void
> +dec_ovn_dp_group_ref(struct hmap *dp_groups, struct ovn_dp_group *dpg)
> +{
> +    dpg->refcnt--;
> +
> +    if (!dpg->refcnt) {
> +        hmap_remove(dp_groups, &dpg->node);
> +        free(dpg->bitmap);
> +        free(dpg);
> +    }
> +}
> +
>  #endif /* LFLOW_MGR_H */
> \ No newline at end of file
> diff --git a/northd/northd.c b/northd/northd.c
> index 96a5b52127..d21a070fdd 100644
> --- a/northd/northd.c
> +++ b/northd/northd.c
> @@ -3745,244 +3745,6 @@ build_lb_port_related_data(
>      build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
>  }
>  
> -struct sb_lb {
> -    struct hmap_node hmap_node;
> -
> -    const struct sbrec_load_balancer *slb;
> -    struct ovn_dp_group *dpg;
> -    struct ovn_dp_group *lr_dpg;
> -    struct uuid lb_uuid;
> -};
> -
> -static struct sb_lb *
> -find_slb_in_sb_lbs(struct hmap *sb_lbs, const struct uuid *lb_uuid)
> -{
> -    struct sb_lb *sb_lb;
> -    HMAP_FOR_EACH_WITH_HASH (sb_lb, hmap_node, uuid_hash(lb_uuid), sb_lbs) {
> -        if (uuid_equals(&sb_lb->lb_uuid, lb_uuid)) {
> -            return sb_lb;
> -        }
> -    }
> -
> -    return NULL;
> -}
> -
> -/* Syncs relevant load balancers (applied to logical switches) to the
> - * Southbound database.
> - */
> -void
> -sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
> -         const struct sbrec_load_balancer_table *sbrec_load_balancer_table,
> -         struct ovn_datapaths *ls_datapaths,
> -         struct ovn_datapaths *lr_datapaths,
> -         struct hmap *lb_dps_map,
> -         struct chassis_features *chassis_features)
> -{
> -    struct hmap ls_dp_groups = HMAP_INITIALIZER(&ls_dp_groups);
> -    struct hmap lr_dp_groups = HMAP_INITIALIZER(&lr_dp_groups);
> -    struct ovn_lb_datapaths *lb_dps;
> -    struct hmap sb_lbs = HMAP_INITIALIZER(&sb_lbs);
> -
> -    /* Delete any stale SB load balancer rows and create datapath
> -     * groups for existing ones. */
> -    struct hmapx existing_lbs = HMAPX_INITIALIZER(&existing_lbs);
> -    const struct sbrec_load_balancer *sbrec_lb;
> -    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_SAFE (sbrec_lb,
> -                            sbrec_load_balancer_table) {
> -        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
> -        struct uuid lb_uuid;
> -        if (!nb_lb_uuid || !uuid_from_string(&lb_uuid, nb_lb_uuid)) {
> -            sbrec_load_balancer_delete(sbrec_lb);
> -            continue;
> -        }
> -
> -        /* Delete any SB load balancer entries that refer to NB load balancers
> -         * that don't exist anymore or are not applied to switches/routers
> -         * anymore.
> -         *
> -         * There is also a special case in which duplicate LBs might be created
> -         * in the SB, e.g., due to the fact that OVSDB only ensures
> -         * "at-least-once" consistency for clustered database tables that
> -         * are not indexed in any way.
> -         */
> -        lb_dps = ovn_lb_datapaths_find(lb_dps_map, &lb_uuid);
> -        if (!lb_dps || (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) ||
> -            !hmapx_add(&existing_lbs, lb_dps)) {
> -            sbrec_load_balancer_delete(sbrec_lb);
> -            continue;
> -        }
> -
> -        struct sb_lb *sb_lb = xzalloc(sizeof *sb_lb);
> -        sb_lb->lb_uuid = lb_uuid;
> -        sb_lb->slb = sbrec_lb;
> -        hmap_insert(&sb_lbs, &sb_lb->hmap_node, uuid_hash(&lb_uuid));
> -
> -        /* Find or create datapath group for this load balancer. */
> -        if (lb_dps->n_nb_ls) {
> -            struct sbrec_logical_dp_group *ls_datapath_group
> -                = chassis_features->ls_dpg_column
> -                    ? sb_lb->slb->ls_datapath_group
> -                    : sb_lb->slb->datapath_group; /* deprecated */
> -            sb_lb->dpg = ovn_dp_group_get_or_create(
> -                    ovnsb_txn, &ls_dp_groups,
> -                    ls_datapath_group,
> -                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
> -                    ods_size(ls_datapaths), true,
> -                    ls_datapaths, NULL);
> -        }
> -        if (lb_dps->n_nb_lr) {
> -            sb_lb->lr_dpg = ovn_dp_group_get_or_create(
> -                    ovnsb_txn, &lr_dp_groups,
> -                    sb_lb->slb->lr_datapath_group,
> -                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
> -                    ods_size(lr_datapaths), false,
> -                    NULL, lr_datapaths);
> -        }
> -    }
> -    hmapx_destroy(&existing_lbs);
> -
> -    /* Create SB Load balancer records if not present and sync
> -     * the SB load balancer columns. */
> -    HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
> -
> -        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
> -            continue;
> -        }
> -
> -        /* Store the fact that northd provides the original (destination IP +
> -         * transport port) tuple.
> -         */
> -        struct smap options;
> -        smap_clone(&options, &lb_dps->lb->nlb->options);
> -        smap_replace(&options, "hairpin_orig_tuple", "true");
> -
> -        struct sb_lb *sb_lb = find_slb_in_sb_lbs(&sb_lbs,
> -                                            &lb_dps->lb->nlb->header_.uuid);
> -        ovs_assert(!sb_lb || (sb_lb->slb && (sb_lb->dpg || sb_lb->lr_dpg)));
> -        struct ovn_dp_group *lb_dpg = NULL, *lb_lr_dpg = NULL;
> -        if (!sb_lb) {
> -            sbrec_lb = sbrec_load_balancer_insert(ovnsb_txn);
> -            char *lb_id = xasprintf(
> -                UUID_FMT, UUID_ARGS(&lb_dps->lb->nlb->header_.uuid));
> -            const struct smap external_ids =
> -                SMAP_CONST1(&external_ids, "lb_id", lb_id);
> -            sbrec_load_balancer_set_external_ids(sbrec_lb, &external_ids);
> -            free(lb_id);
> -        } else {
> -            sbrec_lb = sb_lb->slb;
> -            lb_dpg = sb_lb->dpg;
> -            lb_lr_dpg = sb_lb->lr_dpg;
> -        }
> -
> -        /* Find or create datapath group for this load balancer. */
> -        if (!lb_dpg && lb_dps->n_nb_ls) {
> -            struct sbrec_logical_dp_group *ls_datapath_group
> -                = chassis_features->ls_dpg_column
> -                    ? sbrec_lb->ls_datapath_group
> -                    : sbrec_lb->datapath_group; /* deprecated */
> -            lb_dpg = ovn_dp_group_get_or_create(
> -                    ovnsb_txn, &ls_dp_groups,
> -                    ls_datapath_group,
> -                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
> -                    ods_size(ls_datapaths), true,
> -                    ls_datapaths, NULL);
> -        }
> -        if (!lb_lr_dpg && lb_dps->n_nb_lr) {
> -            lb_lr_dpg = ovn_dp_group_get_or_create(
> -                    ovnsb_txn, &lr_dp_groups,
> -                    sbrec_lb->lr_datapath_group,
> -                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
> -                    ods_size(lr_datapaths), false,
> -                    NULL, lr_datapaths);
> -        }
> -
> -        /* Update columns. */
> -        sbrec_load_balancer_set_name(sbrec_lb, lb_dps->lb->nlb->name);
> -        sbrec_load_balancer_set_vips(sbrec_lb,
> -                                     ovn_northd_lb_get_vips(lb_dps->lb));
> -        sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
> -
> -        if (chassis_features->ls_dpg_column) {
> -            sbrec_load_balancer_set_ls_datapath_group(
> -                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
> -            );
> -            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
> -        } else {
> -            /* datapath_group column is deprecated. */
> -            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
> -            sbrec_load_balancer_set_datapath_group(
> -                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
> -            );
> -        }
> -
> -        sbrec_load_balancer_set_lr_datapath_group(
> -            sbrec_lb, lb_lr_dpg ? lb_lr_dpg->dp_group : NULL
> -        );
> -        sbrec_load_balancer_set_options(sbrec_lb, &options);
> -        /* Clearing 'datapaths' column, since 'dp_group' is in use. */
> -        sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
> -        smap_destroy(&options);
> -    }
> -
> -    struct ovn_dp_group *dpg;
> -    HMAP_FOR_EACH_POP (dpg, node, &ls_dp_groups) {
> -        bitmap_free(dpg->bitmap);
> -        free(dpg);
> -    }
> -    hmap_destroy(&ls_dp_groups);
> -
> -    HMAP_FOR_EACH_POP (dpg, node, &lr_dp_groups) {
> -        bitmap_free(dpg->bitmap);
> -        free(dpg);
> -    }
> -    hmap_destroy(&lr_dp_groups);
> -
> -    struct sb_lb *sb_lb;
> -    HMAP_FOR_EACH_POP (sb_lb, hmap_node, &sb_lbs) {
> -        free(sb_lb);
> -    }
> -    hmap_destroy(&sb_lbs);
> -
> -    /* Datapath_Binding.load_balancers is not used anymore, it's still in the
> -     * schema for compatibility reasons.  Reset it to empty, just in case.
> -     */
> -    struct ovn_datapath *od;
> -    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
> -        ovs_assert(od->nbs);
> -
> -        if (od->sb->n_load_balancers) {
> -            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
> -        }
> -    }
> -    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
> -        ovs_assert(od->nbr);
> -
> -        if (od->sb->n_load_balancers) {
> -            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
> -        }
> -    }
> -}
> -
> -bool
> -check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
> -{
> -    struct sset existing_nb_lb_uuids =
> -        SSET_INITIALIZER(&existing_nb_lb_uuids);
> -    const struct sbrec_load_balancer *sbrec_lb;
> -    bool duplicates = false;
> -
> -    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
> -        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
> -        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
> -            duplicates = true;
> -            break;
> -        }
> -    }
> -
> -    sset_destroy(&existing_nb_lb_uuids);
> -    return duplicates;
> -}
> -
>  /* Syncs the SB port binding for the ovn_port 'op' of a logical switch port.
>   * Caller should make sure that the OVN SB IDL txn is not NULL.  Presently it
>   * only syncs the nat column of port binding corresponding to the 'op->nbsp' */
> diff --git a/northd/northd.h b/northd/northd.h
> index 297b1a65b9..579edb8bdf 100644
> --- a/northd/northd.h
> +++ b/northd/northd.h
> @@ -715,12 +715,6 @@ const char *northd_get_svc_monitor_mac(void);
>  
>  const struct ovn_datapath *northd_get_datapath_for_port(
>      const struct hmap *ls_ports, const char *port_name);
> -void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
> -              struct ovn_datapaths *ls_datapaths,
> -              struct ovn_datapaths *lr_datapaths,
> -              struct hmap *lbs,
> -              struct chassis_features *chassis_features);
> -bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
>  
>  struct lr_stateful_table;
>  void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports,
> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> index 263eb343eb..5c33a8a6c2 100644
> --- a/tests/ovn-northd.at
> +++ b/tests/ovn-northd.at
> @@ -2938,6 +2938,8 @@ sw1_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw1)
>  echo "$sw0_sb_uuid" > sw_sb_uuids
>  echo "$sw1_sb_uuid" >> sw_sb_uuids
>  
> +lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
> +
>  echo
>  echo "__file__:__line__: Check that SB lb0 has sw0 and sw1 in datapaths column."
>  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
> @@ -2945,6 +2947,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
>  $(cat sw_sb_uuids | sort)
>  ])
>  
> +lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
> +
>  echo
>  echo "__file__:__line__: Check that SB lbg0 has sw0 and sw1 in datapaths column."
>  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
> @@ -3014,6 +3018,64 @@ echo "__file__:__line__: Set hairpin_snat_ip on lb1 and check that SB DB is upda
>  check ovn-nbctl --wait=sb set Load_Balancer lb1 options:hairpin_snat_ip="42.42.42.42 4242::4242"
>  check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 options='{hairpin_orig_tuple="true", hairpin_snat_ip="42.42.42.42 4242::4242"}'
>  
> +echo
> +echo "__file__:__line__: Set option:bar=foo on lbg1 and check that sync_to_sb_lb engine node didn't recompute."
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-nbctl --wait=sb set load_balancer lbg1 options:bar=foo
> +check_engine_stats sync_to_sb_lb norecompute compute
> +
> +# Manually clear the sb:load_balancer.ls_datapath_group column for
> +# all load balancers and check that it is resynced back when the
> +# NB lbg1 is modified.
> +
> +echo
> +echo "__file__:__line__: Clear the ls_datapath_group column of all the SB load balancers."
> +
> +for l in $(ovn-sbctl --bare --columns _uuid list load_balancer)
> +do
> +  check ovn-sbctl clear load_balancer $l ls_datapath_group
> +done
> +
> +lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
> +AT_CHECK([test "$lbg1_dp_group" = ""])
> +
> +echo
> +echo "__file__:__line__: Set option:foo=bar on lbg1 and check that sync_to_sb_lb engine node recomputed."
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-nbctl --wait=sb set load_balancer lbg1 options:foo=bar
> +check_engine_stats sync_to_sb_lb recompute compute
> +
> +# Manually clear the sb:load_balancer.lr_datapath_group column for
> +# all load balancers and check that it is resynced back when the
> +# NB lbg1 is modified.
> +
> +echo
> +echo "__file__:__line__: Clear the lr_datapath_group column of all the SB load balancers."
> +
> +for l in $(ovn-sbctl --bare --columns _uuid list load_balancer)
> +do
> +  check ovn-sbctl clear load_balancer $l lr_datapath_group
> +done
> +
> +echo
> +echo "__file__:__line__: Set option:foo=foo on lbg1 and check that sync_to_sb_lb engine node recomputed."
> +
> +check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> +check ovn-nbctl --wait=sb set load_balancer lbg1 options:foo=foo
> +check_engine_stats sync_to_sb_lb recompute compute
> +
> +lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
> +
> +echo
> +echo "__file__:__line__: Check that SB lbg1 has sw0 and sw1 in datapaths column."
> +
> +AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
> +                    | grep -A1 $lbg1_dp_group | tail -1 | tr ' ' '\n' | sort], [0], [dnl
> +$(cat sw_sb_uuids | sort)
> +])
> +
>  echo
>  echo "__file__:__line__: Delete load balancers lb1 and lbg1 and check that datapath sw1's load_balancers is still empty."
>  
> @@ -3024,6 +3086,8 @@ echo
>  echo "__file__:__line__: Delete switch sw0."
>  check ovn-nbctl --wait=sb ls-del sw0
>  
> +lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
> +
>  echo
>  echo "__file__:__line__: Check that SB lb0 has only sw1 in datapaths column."
>  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
> @@ -3031,6 +3095,8 @@ AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
>  $sw1_sb_uuid
>  ])
>  
> +lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
> +
>  echo
>  echo "__file__:__line__: Check that SB lbg0 has only sw1 in datapaths column."
>  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
> @@ -10521,8 +10587,7 @@ check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
>  check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> -
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>  
> @@ -10531,7 +10596,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10541,7 +10606,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
>  
> @@ -10550,7 +10615,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10560,7 +10625,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10618,7 +10683,7 @@ check_engine_stats lr_stateful norecompute compute
>  # A LB applied to a switch/router triggers:
>  # - a recompute in the first iteration (handling northd change)
>  # - a compute in the second iteration (handling SB update)
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE((1))
> @@ -10631,7 +10696,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  
>  # Cleanup the vip of lb1.
> @@ -10642,7 +10707,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  
>  # Set the vips of lb1 back
> @@ -10653,7 +10718,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  
>  # Add another vip to lb1
> @@ -10664,7 +10729,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
>  
>  # Disassociate lb1 from sw0. There should be a full recompute of northd engine node.
> @@ -10687,7 +10752,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10708,7 +10773,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Modify the backend of the lb1 vip
> @@ -10718,7 +10783,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Cleanup the vip of lb1.
> @@ -10728,7 +10793,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Set the vips of lb1 back
> @@ -10738,7 +10803,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Add another vip to lb1
> @@ -10748,7 +10813,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10779,7 +10844,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10796,7 +10861,7 @@ check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
>  check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10806,7 +10871,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  
>  # Update lb and this should not result in northd recompute
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10816,7 +10881,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  
>  # Modify the backend of the lb1 vip
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10826,7 +10891,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Cleanup the vip of lb1.
> @@ -10837,7 +10902,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Set the vips of lb1 back
> @@ -10848,7 +10913,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Add another vip to lb1
> @@ -10859,7 +10924,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10875,7 +10940,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Modify the backend of the lb1 vip
> @@ -10885,7 +10950,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Cleanup the vip of lb1.
> @@ -10895,7 +10960,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Set the vips of lb1 back
> @@ -10905,7 +10970,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Add another vip to lb1
> @@ -10915,7 +10980,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10935,7 +11000,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10979,7 +11044,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10989,7 +11054,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -10998,7 +11063,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11008,7 +11073,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11018,7 +11083,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11028,7 +11093,7 @@ check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11059,7 +11124,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Deleting lb2 should result in lflow recompute as it is
> @@ -11071,7 +11136,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats ls_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
> -check_engine_stats sync_to_sb_lb recompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11281,6 +11346,7 @@ check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
>  check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lflow norecompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Clear the VIPs of lb1
> @@ -11289,6 +11355,7 @@ check ovn-nbctl --wait=sb clear load_balancer . vips
>  check_engine_stats lb_data norecompute compute
>  check_engine_stats northd norecompute compute
>  check_engine_stats lflow norecompute compute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
> @@ -11296,6 +11363,7 @@ check ovn-nbctl --wait=sb lb-del lb1
>  check_engine_stats lb_data norecompute compute
>  check_engine_stats northd recompute nocompute
>  check_engine_stats lflow recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  AT_CLEANUP
> @@ -11544,6 +11612,7 @@ check_engine_stats northd recompute nocompute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11557,6 +11626,7 @@ check_engine_stats northd recompute compute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11569,6 +11639,7 @@ check_engine_stats northd recompute compute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11595,6 +11666,7 @@ check_engine_stats northd recompute nocompute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11607,6 +11679,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Update the NAT options column
> @@ -11616,6 +11689,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Update the NAT external_ip column
> @@ -11626,6 +11700,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Update the NAT logical_ip column
> @@ -11636,6 +11711,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Update the NAT type
> @@ -11646,6 +11722,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Create a dnat_and_snat NAT with external_mac and logical_port
> @@ -11656,6 +11733,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  nat2_uuid=$(ovn-nbctl --bare --columns _uuid find nat logical_ip=10.0.0.4)
> @@ -11667,6 +11745,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Create a load balancer and add the lb vip as NAT
> @@ -11683,6 +11762,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11694,6 +11774,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11705,6 +11786,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11716,6 +11798,7 @@ check_engine_stats northd norecompute compute
>  check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11727,6 +11810,7 @@ check_engine_stats lr_nat norecompute compute
>  check_engine_stats lr_stateful norecompute compute
>  check_engine_stats lflow norecompute compute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb norecompute compute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
>  # Create router Policy
> @@ -11736,6 +11820,7 @@ check_engine_stats northd recompute nocompute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>  
> @@ -11745,6 +11830,7 @@ check_engine_stats northd recompute nocompute
>  check_engine_stats lr_nat recompute nocompute
>  check_engine_stats lr_stateful recompute nocompute
>  check_engine_stats sync_to_sb_pb recompute nocompute
> +check_engine_stats sync_to_sb_lb recompute nocompute
>  check_engine_stats lflow recompute nocompute
>  CHECK_NO_CHANGE_AFTER_RECOMPUTE
>
diff mbox series

Patch

diff --git a/northd/en-sync-sb.c b/northd/en-sync-sb.c
index d39cbbf2e6..80f3621bb9 100644
--- a/northd/en-sync-sb.c
+++ b/northd/en-sync-sb.c
@@ -18,17 +18,21 @@ 
 #include <stdlib.h>
 #include <stdio.h>
 
+/* OVS includes. */
 #include "lib/svec.h"
 #include "openvswitch/util.h"
 
+/* OVN includes. */
 #include "en-lr-nat.h"
 #include "en-lr-stateful.h"
 #include "en-sync-sb.h"
+#include "lb.h"
 #include "lib/inc-proc-eng.h"
 #include "lib/lb.h"
 #include "lib/ovn-nb-idl.h"
 #include "lib/ovn-sb-idl.h"
 #include "lib/ovn-util.h"
+#include "lflow-mgr.h"
 #include "northd.h"
 
 #include "openvswitch/vlog.h"
@@ -51,6 +55,40 @@  static void build_port_group_address_set(const struct nbrec_port_group *,
                                          struct svec *ipv4_addrs,
                                          struct svec *ipv6_addrs);
 
+struct sb_lb_table;
+struct sb_lb_record;
+static void sb_lb_table_init(struct sb_lb_table *);
+static void sb_lb_table_clear(struct sb_lb_table *);
+static void sb_lb_table_destroy(struct sb_lb_table *);
+
+static struct sb_lb_record *sb_lb_table_find(struct hmap *sb_lbs,
+                                             const struct uuid *);
+static void sb_lb_table_build_and_sync(struct sb_lb_table *,
+                                struct ovsdb_idl_txn *ovnsb_txn,
+                                const struct sbrec_load_balancer_table *,
+                                const struct sbrec_logical_dp_group_table *,
+                                struct hmap *lb_dps_map,
+                                struct ovn_datapaths *ls_datapaths,
+                                struct ovn_datapaths *lr_datapaths,
+                                struct chassis_features *);
+static bool sync_sb_lb_record(struct sb_lb_record *,
+                              const struct sbrec_load_balancer *,
+                              const struct sbrec_logical_dp_group_table *,
+                              struct sb_lb_table *,
+                              struct ovsdb_idl_txn *ovnsb_txn,
+                              struct ovn_datapaths *ls_datapaths,
+                              struct ovn_datapaths *lr_datapaths,
+                              struct chassis_features *);
+static bool sync_changed_lbs(struct sb_lb_table *,
+                             struct ovsdb_idl_txn *ovnsb_txn,
+                             const struct sbrec_load_balancer_table *,
+                             const struct sbrec_logical_dp_group_table *,
+                             struct tracked_lbs *,
+                             struct ovn_datapaths *ls_datapaths,
+                             struct ovn_datapaths *lr_datapaths,
+                             struct chassis_features *);
+static bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
+
 void *
 en_sync_to_sb_init(struct engine_node *node OVS_UNUSED,
                 struct engine_arg *arg OVS_UNUSED)
@@ -206,49 +244,99 @@  sync_to_sb_addr_set_nb_port_group_handler(struct engine_node *node,
 /* sync_to_sb_lb engine node functions.
  * This engine node syncs the SB load balancers.
  */
+struct sb_lb_record {
+    struct hmap_node key_node;  /* Index on 'nblb->header_.uuid'. */
+
+    struct ovn_lb_datapaths *lb_dps;
+    const struct sbrec_load_balancer *sbrec_lb;
+    struct ovn_dp_group *ls_dpg;
+    struct ovn_dp_group *lr_dpg;
+    struct uuid sb_uuid;
+};
+
+struct sb_lb_table {
+    struct hmap entries; /* Stores struct sb_lb_record. */
+    struct hmap ls_dp_groups;
+    struct hmap lr_dp_groups;
+};
+
+struct ed_type_sync_to_sb_lb_data {
+    struct sb_lb_table sb_lbs;
+};
+
 void *
 en_sync_to_sb_lb_init(struct engine_node *node OVS_UNUSED,
                       struct engine_arg *arg OVS_UNUSED)
 {
-    return NULL;
+    struct ed_type_sync_to_sb_lb_data *data = xzalloc(sizeof *data);
+    sb_lb_table_init(&data->sb_lbs);
+
+    return data;
 }
 
 void
-en_sync_to_sb_lb_run(struct engine_node *node, void *data OVS_UNUSED)
+en_sync_to_sb_lb_run(struct engine_node *node, void *data_)
 {
+    struct northd_data *northd_data = engine_get_input_data("northd", node);
     const struct sbrec_load_balancer_table *sb_load_balancer_table =
         EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
+    const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
+        EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
     const struct engine_context *eng_ctx = engine_get_context();
-    struct northd_data *northd_data = engine_get_input_data("northd", node);
+    struct ed_type_sync_to_sb_lb_data *data = data_;
+
+    sb_lb_table_clear(&data->sb_lbs);
+    sb_lb_table_build_and_sync(&data->sb_lbs, eng_ctx->ovnsb_idl_txn,
+                               sb_load_balancer_table,
+                               sb_dpgrp_table,
+                               &northd_data->lb_datapaths_map,
+                               &northd_data->ls_datapaths,
+                               &northd_data->lr_datapaths,
+                               &northd_data->features);
 
-    sync_lbs(eng_ctx->ovnsb_idl_txn, sb_load_balancer_table,
-             &northd_data->ls_datapaths, &northd_data->lr_datapaths,
-             &northd_data->lb_datapaths_map, &northd_data->features);
     engine_set_node_state(node, EN_UPDATED);
 }
 
 void
-en_sync_to_sb_lb_cleanup(void *data OVS_UNUSED)
+en_sync_to_sb_lb_cleanup(void *data_)
 {
-
+    struct ed_type_sync_to_sb_lb_data *data = data_;
+    sb_lb_table_destroy(&data->sb_lbs);
 }
 
 bool
-sync_to_sb_lb_northd_handler(struct engine_node *node, void *data OVS_UNUSED)
+sync_to_sb_lb_northd_handler(struct engine_node *node, void *data_)
 {
     struct northd_data *nd = engine_get_input_data("northd", node);
 
-    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. */
+    if (!northd_has_tracked_data(&nd->trk_data)) {
+        /* Return false if no tracking data. */
         return false;
     }
 
+    if (!northd_has_lbs_in_tracked_data(&nd->trk_data)) {
+        return true;
+    }
 
-    /* There are only NB LSP related changes and these can be safely
-     * ignore and returned true.  However in case the northd engine
-     * tracking data includes other changes, we need to do additional
-     * checks before safely ignoring. */
+    const struct engine_context *eng_ctx = engine_get_context();
+    if (!eng_ctx->ovnsb_idl_txn) {
+        return false;
+    }
+
+    const struct sbrec_logical_dp_group_table *sb_dpgrp_table =
+        EN_OVSDB_GET(engine_get_input("SB_logical_dp_group", node));
+    const struct sbrec_load_balancer_table *sb_lb_table =
+        EN_OVSDB_GET(engine_get_input("SB_load_balancer", node));
+    struct ed_type_sync_to_sb_lb_data *data = data_;
+
+    if (!sync_changed_lbs(&data->sb_lbs, eng_ctx->ovnsb_idl_txn, sb_lb_table,
+                          sb_dpgrp_table, &nd->trk_data.trk_lbs,
+                          &nd->ls_datapaths, &nd->lr_datapaths,
+                          &nd->features)) {
+        return false;
+    }
+
+    engine_set_node_state(node, EN_UPDATED);
     return true;
 }
 
@@ -519,3 +607,388 @@  sb_address_set_lookup_by_name(struct ovsdb_idl_index *sbrec_addr_set_by_name,
 
     return retval;
 }
+
+/* static functions related to sync_to_sb_lb */
+
+static void
+sb_lb_table_init(struct sb_lb_table *sb_lbs)
+{
+    hmap_init(&sb_lbs->entries);
+    ovn_dp_groups_init(&sb_lbs->ls_dp_groups);
+    ovn_dp_groups_init(&sb_lbs->lr_dp_groups);
+}
+
+static void
+sb_lb_table_clear(struct sb_lb_table *sb_lbs)
+{
+    struct sb_lb_record *sb_lb;
+    HMAP_FOR_EACH_POP (sb_lb, key_node, &sb_lbs->entries) {
+        free(sb_lb);
+    }
+
+    ovn_dp_groups_clear(&sb_lbs->ls_dp_groups);
+    ovn_dp_groups_clear(&sb_lbs->lr_dp_groups);
+}
+
+static void
+sb_lb_table_destroy(struct sb_lb_table *sb_lbs)
+{
+    sb_lb_table_clear(sb_lbs);
+    hmap_destroy(&sb_lbs->entries);
+    ovn_dp_groups_destroy(&sb_lbs->ls_dp_groups);
+    ovn_dp_groups_destroy(&sb_lbs->lr_dp_groups);
+}
+
+static struct sb_lb_record *
+sb_lb_table_find(struct hmap *sb_lbs, const struct uuid *lb_uuid)
+{
+    struct sb_lb_record *sb_lb;
+    HMAP_FOR_EACH_WITH_HASH (sb_lb, key_node, uuid_hash(lb_uuid),
+                             sb_lbs) {
+        if (uuid_equals(&sb_lb->lb_dps->lb->nlb->header_.uuid, lb_uuid)) {
+            return sb_lb;
+        }
+    }
+
+    return NULL;
+}
+
+static void
+sb_lb_table_build_and_sync(struct sb_lb_table *sb_lbs,
+                    struct ovsdb_idl_txn *ovnsb_txn,
+                    const struct sbrec_load_balancer_table *sb_lb_table,
+                    const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
+                    struct hmap *lb_dps_map,
+                    struct ovn_datapaths *ls_datapaths,
+                    struct ovn_datapaths *lr_datapaths,
+                    struct chassis_features *chassis_features)
+{
+    struct hmap tmp_sb_lbs = HMAP_INITIALIZER(&tmp_sb_lbs);
+    struct ovn_lb_datapaths *lb_dps;
+    struct sb_lb_record *sb_lb;
+
+    HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
+        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
+            continue;
+        }
+
+        sb_lb = xzalloc(sizeof *sb_lb);
+        sb_lb->lb_dps = lb_dps;
+        hmap_insert(&tmp_sb_lbs, &sb_lb->key_node,
+                    uuid_hash(&lb_dps->lb->nlb->header_.uuid));
+    }
+
+    const struct sbrec_load_balancer *sbrec_lb;
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_SAFE (sbrec_lb,
+                                             sb_lb_table) {
+        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
+        struct uuid lb_uuid;
+        if (!nb_lb_uuid || !uuid_from_string(&lb_uuid, nb_lb_uuid)) {
+            sbrec_load_balancer_delete(sbrec_lb);
+            continue;
+        }
+
+        sb_lb = sb_lb_table_find(&tmp_sb_lbs, &lb_uuid);
+        if (sb_lb) {
+            sb_lb->sbrec_lb = sbrec_lb;
+            bool success = sync_sb_lb_record(sb_lb, sbrec_lb, sb_dpgrp_table,
+                                             sb_lbs, ovnsb_txn, ls_datapaths,
+                                             lr_datapaths, chassis_features);
+            /* Since we are rebuilding and syncing,  sync_sb_lb_record should
+             * not return false. */
+            ovs_assert(success);
+
+            hmap_remove(&tmp_sb_lbs, &sb_lb->key_node);
+            hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
+                        uuid_hash(&sb_lb->lb_dps->lb->nlb->header_.uuid));
+        } else {
+            sbrec_load_balancer_delete(sbrec_lb);
+        }
+    }
+
+    HMAP_FOR_EACH_POP (sb_lb, key_node, &tmp_sb_lbs) {
+        bool success = sync_sb_lb_record(sb_lb, NULL, sb_dpgrp_table, sb_lbs,
+                                         ovnsb_txn, ls_datapaths, lr_datapaths,
+                                         chassis_features);
+        /* Since we are rebuilding and syncing,  sync_sb_lb_record should not
+         * return false. */
+        ovs_assert(success);
+
+        hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
+                    uuid_hash(&sb_lb->lb_dps->lb->nlb->header_.uuid));
+    }
+
+    hmap_destroy(&tmp_sb_lbs);
+}
+
+static bool
+sync_sb_lb_record(struct sb_lb_record *sb_lb,
+                  const struct sbrec_load_balancer *sbrec_lb,
+                  const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
+                  struct sb_lb_table *sb_lbs,
+                  struct ovsdb_idl_txn *ovnsb_txn,
+                  struct ovn_datapaths *ls_datapaths,
+                  struct ovn_datapaths *lr_datapaths,
+                  struct chassis_features *chassis_features)
+{
+    struct sbrec_logical_dp_group *sbrec_ls_dp_group = NULL;
+    struct sbrec_logical_dp_group *sbrec_lr_dp_group = NULL;
+    const struct ovn_lb_datapaths *lb_dps;
+    struct ovn_dp_group *pre_sync_ls_dpg;
+    struct ovn_dp_group *pre_sync_lr_dpg;
+
+    lb_dps = sb_lb->lb_dps;
+    pre_sync_ls_dpg = sb_lb->ls_dpg;
+    pre_sync_lr_dpg = sb_lb->lr_dpg;
+
+    if (!sbrec_lb) {
+        sb_lb->sb_uuid = uuid_random();
+        sbrec_lb =  sbrec_load_balancer_insert_persist_uuid(ovnsb_txn,
+                                                            &sb_lb->sb_uuid);
+        char *lb_id = xasprintf(
+            UUID_FMT, UUID_ARGS(&lb_dps->lb->nlb->header_.uuid));
+        const struct smap external_ids =
+            SMAP_CONST1(&external_ids, "lb_id", lb_id);
+        sbrec_load_balancer_set_external_ids(sbrec_lb, &external_ids);
+        free(lb_id);
+    } else {
+        sb_lb->sb_uuid = sbrec_lb->header_.uuid;
+        sbrec_ls_dp_group =
+            chassis_features->ls_dpg_column
+            ? sbrec_lb->ls_datapath_group
+            : sbrec_lb->datapath_group; /* deprecated */
+
+        sbrec_lr_dp_group = sbrec_lb->lr_datapath_group;
+    }
+
+    if (lb_dps->n_nb_ls) {
+        sb_lb->ls_dpg = ovn_dp_group_get(&sb_lbs->ls_dp_groups,
+                                         lb_dps->n_nb_ls,
+                                         lb_dps->nb_ls_map,
+                                         ods_size(ls_datapaths));
+        if (sb_lb->ls_dpg) {
+            /* Update the dpg's sb dp_group. */
+            sb_lb->ls_dpg->dp_group =
+                sbrec_logical_dp_group_table_get_for_uuid(sb_dpgrp_table,
+                                                    &sb_lb->ls_dpg->dpg_uuid);
+            if (!sb_lb->ls_dpg->dp_group) {
+                /* Ideally this should not happen.  But it can still happen
+                 * due to 2 reasons:
+                 * 1. There is a bug in the dp_group management.  We should
+                 *    perhaps assert here.
+                 * 2. A User or CMS may delete the logical_dp_groups in SB DB
+                 *    or clear the SB:Load_balancer.ls_datapath_group column
+                 *    (intentionally or accidentally)
+                 *
+                 * Because of (2) it is better to return false instead of
+                 * assert,so that we recover from th inconsistent SB DB.
+                 */
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "SB Load balancer [%s]'s ls_dp_group column "
+                            "is not set (which is unexpected).  It should "
+                            "have been referencing the dp group ["UUID_FMT"]",
+                            sb_lb->lb_dps->lb->nlb->name,
+                            UUID_ARGS(&sb_lb->ls_dpg->dpg_uuid));
+                return false;
+            }
+        } else {
+            sb_lb->ls_dpg = ovn_dp_group_create(
+                ovnsb_txn, &sb_lbs->ls_dp_groups, sbrec_ls_dp_group,
+                lb_dps->n_nb_ls, lb_dps->nb_ls_map,
+                ods_size(ls_datapaths), true,
+                ls_datapaths, lr_datapaths);
+        }
+
+        if (chassis_features->ls_dpg_column) {
+            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb,
+                                                      sb_lb->ls_dpg->dp_group);
+            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
+        } else {
+            /* datapath_group column is deprecated. */
+            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
+            sbrec_load_balancer_set_datapath_group(sbrec_lb,
+                                                   sb_lb->ls_dpg->dp_group);
+
+        }
+    } else {
+        sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
+        sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
+    }
+
+
+    if (lb_dps->n_nb_lr) {
+        sb_lb->lr_dpg = ovn_dp_group_get(&sb_lbs->lr_dp_groups,
+                                         lb_dps->n_nb_lr,
+                                         lb_dps->nb_lr_map,
+                                         ods_size(lr_datapaths));
+        if (sb_lb->lr_dpg) {
+            /* Update the dpg's sb dp_group. */
+            sb_lb->lr_dpg->dp_group =
+                sbrec_logical_dp_group_table_get_for_uuid(sb_dpgrp_table,
+                                                    &sb_lb->lr_dpg->dpg_uuid);
+            if (!sb_lb->lr_dpg->dp_group) {
+                /* Ideally this should not happen.  But it can still happen
+                 * due to 2 reasons:
+                 * 1. There is a bug in the dp_group management.  We should
+                 *    perhaps assert here.
+                 * 2. A User or CMS may delete the logical_dp_groups in SB DB
+                 *    or clear the SB:Load_balancer.lr_datapath_group column
+                 *    (intentionally or accidentally)
+                 *
+                 * Because of (2) it is better to return false instead of
+                 * assert,so that we recover from th inconsistent SB DB.
+                 */
+                static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+                VLOG_WARN_RL(&rl, "SB Load balancer [%s]'s lr_dp_group column "
+                            "is not set (which is unexpected).  It should "
+                            "have been referencing the dp group ["UUID_FMT"]",
+                            sb_lb->lb_dps->lb->nlb->name,
+                            UUID_ARGS(&sb_lb->lr_dpg->dpg_uuid));
+                return false;
+            }
+        } else {
+            sb_lb->lr_dpg = ovn_dp_group_create(
+                ovnsb_txn, &sb_lbs->lr_dp_groups, sbrec_lr_dp_group,
+                lb_dps->n_nb_lr, lb_dps->nb_lr_map,
+                ods_size(lr_datapaths), false,
+                ls_datapaths, lr_datapaths);
+        }
+
+        sbrec_load_balancer_set_lr_datapath_group(sbrec_lb,
+                                                  sb_lb->lr_dpg->dp_group);
+    } else {
+        sbrec_load_balancer_set_lr_datapath_group(sbrec_lb, NULL);
+    }
+
+    if (pre_sync_ls_dpg != sb_lb->ls_dpg) {
+        if (sb_lb->ls_dpg) {
+            inc_ovn_dp_group_ref(sb_lb->ls_dpg);
+        }
+        if (pre_sync_ls_dpg) {
+            dec_ovn_dp_group_ref(&sb_lbs->ls_dp_groups, pre_sync_ls_dpg);
+        }
+    }
+
+    if (pre_sync_lr_dpg != sb_lb->lr_dpg) {
+        if (sb_lb->lr_dpg) {
+            inc_ovn_dp_group_ref(sb_lb->lr_dpg);
+        }
+        if (pre_sync_lr_dpg) {
+            dec_ovn_dp_group_ref(&sb_lbs->lr_dp_groups, pre_sync_lr_dpg);
+        }
+    }
+
+    /* Update columns. */
+    sbrec_load_balancer_set_name(sbrec_lb, lb_dps->lb->nlb->name);
+    sbrec_load_balancer_set_vips(sbrec_lb,
+                                 ovn_northd_lb_get_vips(lb_dps->lb));
+    sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
+
+    /* Store the fact that northd provides the original (destination IP +
+     * transport port) tuple.
+     */
+    struct smap options;
+    smap_clone(&options, &lb_dps->lb->nlb->options);
+    smap_replace(&options, "hairpin_orig_tuple", "true");
+    sbrec_load_balancer_set_options(sbrec_lb, &options);
+    /* Clearing 'datapaths' column, since 'dp_group' is in use. */
+    sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
+    smap_destroy(&options);
+
+    return true;
+}
+
+static bool
+sync_changed_lbs(struct sb_lb_table *sb_lbs,
+                 struct ovsdb_idl_txn *ovnsb_txn,
+                 const struct sbrec_load_balancer_table *sb_lb_table,
+                 const struct sbrec_logical_dp_group_table *sb_dpgrp_table,
+                 struct tracked_lbs *trk_lbs,
+                 struct ovn_datapaths *ls_datapaths,
+                 struct ovn_datapaths *lr_datapaths,
+                 struct chassis_features *chassis_features)
+{
+    struct ovn_lb_datapaths *lb_dps;
+    struct hmapx_node *hmapx_node;
+    struct sb_lb_record *sb_lb;
+
+    HMAPX_FOR_EACH (hmapx_node, &trk_lbs->deleted) {
+        lb_dps = hmapx_node->data;
+
+        sb_lb = sb_lb_table_find(&sb_lbs->entries,
+                                 &lb_dps->lb->nlb->header_.uuid);
+        if (sb_lb) {
+            const struct sbrec_load_balancer *sbrec_lb =
+                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
+                                                       &sb_lb->sb_uuid);
+            if (sbrec_lb) {
+                sbrec_load_balancer_delete(sbrec_lb);
+            }
+
+            hmap_remove(&sb_lbs->entries, &sb_lb->key_node);
+            free(sb_lb);
+        }
+    }
+
+    HMAPX_FOR_EACH (hmapx_node, &trk_lbs->crupdated) {
+        lb_dps = hmapx_node->data;
+
+        sb_lb = sb_lb_table_find(&sb_lbs->entries,
+                                 &lb_dps->lb->nlb->header_.uuid);
+
+        if (!sb_lb && !lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
+            continue;
+        }
+
+        if (!sb_lb) {
+            sb_lb = xzalloc(sizeof *sb_lb);
+            sb_lb->lb_dps = lb_dps;
+            hmap_insert(&sb_lbs->entries, &sb_lb->key_node,
+                        uuid_hash(&lb_dps->lb->nlb->header_.uuid));
+        } else {
+            sb_lb->sbrec_lb =
+                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
+                                                       &sb_lb->sb_uuid);
+        }
+
+        if (sb_lb && !lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
+            const struct sbrec_load_balancer *sbrec_lb =
+                sbrec_load_balancer_table_get_for_uuid(sb_lb_table,
+                                                       &sb_lb->sb_uuid);
+            if (sbrec_lb) {
+                sbrec_load_balancer_delete(sbrec_lb);
+            }
+
+            hmap_remove(&sb_lbs->entries, &sb_lb->key_node);
+            free(sb_lb);
+        }
+
+        if (!sync_sb_lb_record(sb_lb, sb_lb->sbrec_lb, sb_dpgrp_table, sb_lbs,
+                               ovnsb_txn, ls_datapaths, lr_datapaths,
+                               chassis_features)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool
+check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
+{
+    struct sset existing_nb_lb_uuids =
+        SSET_INITIALIZER(&existing_nb_lb_uuids);
+    const struct sbrec_load_balancer *sbrec_lb;
+    bool duplicates = false;
+
+    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
+        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
+        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
+            duplicates = true;
+            break;
+        }
+    }
+
+    sset_destroy(&existing_nb_lb_uuids);
+    return duplicates;
+}
diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
index 40a9e5e06c..d215c7792b 100644
--- a/northd/inc-proc-northd.c
+++ b/northd/inc-proc-northd.c
@@ -264,6 +264,7 @@  void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
                      sync_to_sb_lb_northd_handler);
     engine_add_input(&en_sync_to_sb_lb, &en_sb_load_balancer,
                      sync_to_sb_lb_sb_load_balancer);
+    engine_add_input(&en_sync_to_sb_lb, &en_sb_logical_dp_group, NULL);
 
     engine_add_input(&en_sync_to_sb_pb, &en_northd,
                      sync_to_sb_pb_northd_handler);
diff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c
index d81b13a25c..d09dc7acb5 100644
--- a/northd/lflow-mgr.c
+++ b/northd/lflow-mgr.c
@@ -70,21 +70,6 @@  static struct ovs_mutex *lflow_hash_lock(const struct hmap *lflow_table,
                                          uint32_t hash);
 static void lflow_hash_unlock(struct ovs_mutex *hash_lock);
 
-static struct ovn_dp_group *ovn_dp_group_get(
-    struct hmap *dp_groups, size_t desired_n,
-    const unsigned long *desired_bitmap,
-    size_t bitmap_len);
-static struct ovn_dp_group *ovn_dp_group_create(
-    struct ovsdb_idl_txn *ovnsb_txn, struct hmap *dp_groups,
-    struct sbrec_logical_dp_group *, size_t desired_n,
-    const unsigned long *desired_bitmap,
-    size_t bitmap_len, bool is_switch,
-    const struct ovn_datapaths *ls_datapaths,
-    const struct ovn_datapaths *lr_datapaths);
-static struct ovn_dp_group *ovn_dp_group_get(
-    struct hmap *dp_groups, size_t desired_n,
-    const unsigned long *desired_bitmap,
-    size_t bitmap_len);
 static struct sbrec_logical_dp_group *ovn_sb_insert_or_update_logical_dp_group(
     struct ovsdb_idl_txn *ovnsb_txn,
     struct sbrec_logical_dp_group *,
@@ -564,31 +549,81 @@  lflow_table_add_lflow_default_drop(struct lflow_table *lflow_table,
                           where, lflow_ref);
 }
 
-/* Given a desired bitmap, finds a datapath group in 'dp_groups'.  If it
- * doesn't exist, creates a new one and adds it to 'dp_groups'.
+struct ovn_dp_group *
+ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
+                 const unsigned long *desired_bitmap,
+                 size_t bitmap_len)
+{
+    uint32_t hash;
+
+    hash = hash_int(desired_n, 0);
+    return ovn_dp_group_find(dp_groups, desired_bitmap, bitmap_len, hash);
+}
+
+/* Creates a new datapath group and adds it to 'dp_groups'.
  * If 'sb_group' is provided, function will try to re-use this group by
- * either taking it directly, or by modifying, if it's not already in use. */
+ * either taking it directly, or by modifying, if it's not already in use.
+ * Caller should first call ovn_dp_group_get() before calling this function. */
 struct ovn_dp_group *
-ovn_dp_group_get_or_create(struct ovsdb_idl_txn *ovnsb_txn,
-                           struct hmap *dp_groups,
-                           struct sbrec_logical_dp_group *sb_group,
-                           size_t desired_n,
-                           const unsigned long *desired_bitmap,
-                           size_t bitmap_len,
-                           bool is_switch,
-                           const struct ovn_datapaths *ls_datapaths,
-                           const struct ovn_datapaths *lr_datapaths)
+ovn_dp_group_create(struct ovsdb_idl_txn *ovnsb_txn,
+                    struct hmap *dp_groups,
+                    struct sbrec_logical_dp_group *sb_group,
+                    size_t desired_n,
+                    const unsigned long *desired_bitmap,
+                    size_t bitmap_len,
+                    bool is_switch,
+                    const struct ovn_datapaths *ls_datapaths,
+                    const struct ovn_datapaths *lr_datapaths)
 {
     struct ovn_dp_group *dpg;
 
-    dpg = ovn_dp_group_get(dp_groups, desired_n, desired_bitmap, bitmap_len);
-    if (dpg) {
-        return dpg;
+    bool update_dp_group = false, can_modify = false;
+    unsigned long *dpg_bitmap;
+    size_t i, n = 0;
+
+    dpg_bitmap = sb_group ? bitmap_allocate(bitmap_len) : NULL;
+    for (i = 0; sb_group && i < sb_group->n_datapaths; i++) {
+        struct ovn_datapath *datapath_od;
+
+        datapath_od = ovn_datapath_from_sbrec(
+                        ls_datapaths ? &ls_datapaths->datapaths : NULL,
+                        lr_datapaths ? &lr_datapaths->datapaths : NULL,
+                        sb_group->datapaths[i]);
+        if (!datapath_od || ovn_datapath_is_stale(datapath_od)) {
+            break;
+        }
+        bitmap_set1(dpg_bitmap, datapath_od->index);
+        n++;
+    }
+    if (!sb_group || i != sb_group->n_datapaths) {
+        /* No group or stale group.  Not going to be used. */
+        update_dp_group = true;
+        can_modify = true;
+    } else if (!bitmap_equal(dpg_bitmap, desired_bitmap, bitmap_len)) {
+        /* The group in Sb is different. */
+        update_dp_group = true;
+        /* We can modify existing group if it's not already in use. */
+        can_modify = !ovn_dp_group_find(dp_groups, dpg_bitmap,
+                                        bitmap_len, hash_int(n, 0));
     }
 
-    return ovn_dp_group_create(ovnsb_txn, dp_groups, sb_group, desired_n,
-                               desired_bitmap, bitmap_len, is_switch,
-                               ls_datapaths, lr_datapaths);
+    bitmap_free(dpg_bitmap);
+
+    dpg = xzalloc(sizeof *dpg);
+    dpg->bitmap = bitmap_clone(desired_bitmap, bitmap_len);
+    if (!update_dp_group) {
+        dpg->dp_group = sb_group;
+    } else {
+        dpg->dp_group = ovn_sb_insert_or_update_logical_dp_group(
+                            ovnsb_txn,
+                            can_modify ? sb_group : NULL,
+                            desired_bitmap,
+                            is_switch ? ls_datapaths : lr_datapaths);
+    }
+    dpg->dpg_uuid = dpg->dp_group->header_.uuid;
+    hmap_insert(dp_groups, &dpg->node, hash_int(desired_n, 0));
+
+    return dpg;
 }
 
 void
@@ -987,24 +1022,6 @@  ovn_dp_group_find(const struct hmap *dp_groups,
     return NULL;
 }
 
-static void
-inc_ovn_dp_group_ref(struct ovn_dp_group *dpg)
-{
-    dpg->refcnt++;
-}
-
-static void
-dec_ovn_dp_group_ref(struct hmap *dp_groups, struct ovn_dp_group *dpg)
-{
-    dpg->refcnt--;
-
-    if (!dpg->refcnt) {
-        hmap_remove(dp_groups, &dpg->node);
-        free(dpg->bitmap);
-        free(dpg);
-    }
-}
-
 static struct sbrec_logical_dp_group *
 ovn_sb_insert_or_update_logical_dp_group(
                             struct ovsdb_idl_txn *ovnsb_txn,
@@ -1031,83 +1048,6 @@  ovn_sb_insert_or_update_logical_dp_group(
     return dp_group;
 }
 
-static struct ovn_dp_group *
-ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
-                 const unsigned long *desired_bitmap,
-                 size_t bitmap_len)
-{
-    uint32_t hash;
-
-    hash = hash_int(desired_n, 0);
-    return ovn_dp_group_find(dp_groups, desired_bitmap, bitmap_len, hash);
-}
-
-/* Creates a new datapath group and adds it to 'dp_groups'.
- * If 'sb_group' is provided, function will try to re-use this group by
- * either taking it directly, or by modifying, if it's not already in use.
- * Caller should first call ovn_dp_group_get() before calling this function. */
-static struct ovn_dp_group *
-ovn_dp_group_create(struct ovsdb_idl_txn *ovnsb_txn,
-                    struct hmap *dp_groups,
-                    struct sbrec_logical_dp_group *sb_group,
-                    size_t desired_n,
-                    const unsigned long *desired_bitmap,
-                    size_t bitmap_len,
-                    bool is_switch,
-                    const struct ovn_datapaths *ls_datapaths,
-                    const struct ovn_datapaths *lr_datapaths)
-{
-    struct ovn_dp_group *dpg;
-
-    bool update_dp_group = false, can_modify = false;
-    unsigned long *dpg_bitmap;
-    size_t i, n = 0;
-
-    dpg_bitmap = sb_group ? bitmap_allocate(bitmap_len) : NULL;
-    for (i = 0; sb_group && i < sb_group->n_datapaths; i++) {
-        struct ovn_datapath *datapath_od;
-
-        datapath_od = ovn_datapath_from_sbrec(
-                        ls_datapaths ? &ls_datapaths->datapaths : NULL,
-                        lr_datapaths ? &lr_datapaths->datapaths : NULL,
-                        sb_group->datapaths[i]);
-        if (!datapath_od || ovn_datapath_is_stale(datapath_od)) {
-            break;
-        }
-        bitmap_set1(dpg_bitmap, datapath_od->index);
-        n++;
-    }
-    if (!sb_group || i != sb_group->n_datapaths) {
-        /* No group or stale group.  Not going to be used. */
-        update_dp_group = true;
-        can_modify = true;
-    } else if (!bitmap_equal(dpg_bitmap, desired_bitmap, bitmap_len)) {
-        /* The group in Sb is different. */
-        update_dp_group = true;
-        /* We can modify existing group if it's not already in use. */
-        can_modify = !ovn_dp_group_find(dp_groups, dpg_bitmap,
-                                        bitmap_len, hash_int(n, 0));
-    }
-
-    bitmap_free(dpg_bitmap);
-
-    dpg = xzalloc(sizeof *dpg);
-    dpg->bitmap = bitmap_clone(desired_bitmap, bitmap_len);
-    if (!update_dp_group) {
-        dpg->dp_group = sb_group;
-    } else {
-        dpg->dp_group = ovn_sb_insert_or_update_logical_dp_group(
-                            ovnsb_txn,
-                            can_modify ? sb_group : NULL,
-                            desired_bitmap,
-                            is_switch ? ls_datapaths : lr_datapaths);
-    }
-    dpg->dpg_uuid = dpg->dp_group->header_.uuid;
-    hmap_insert(dp_groups, &dpg->node, hash_int(desired_n, 0));
-
-    return dpg;
-}
-
 /* Adds an OVN datapath to a datapath group of existing logical flow.
  * Version to use when hash bucket locking is NOT required or the corresponding
  * hash lock is already taken. */
diff --git a/northd/lflow-mgr.h b/northd/lflow-mgr.h
index f215891b97..00554ef78a 100644
--- a/northd/lflow-mgr.h
+++ b/northd/lflow-mgr.h
@@ -157,7 +157,10 @@  ovn_dp_groups_init(struct hmap *dp_groups)
 
 void ovn_dp_groups_clear(struct hmap *dp_groups);
 void ovn_dp_groups_destroy(struct hmap *dp_groups);
-struct ovn_dp_group *ovn_dp_group_get_or_create(
+struct ovn_dp_group *ovn_dp_group_get(struct hmap *dp_groups, size_t desired_n,
+                                      const unsigned long *desired_bitmap,
+                                      size_t bitmap_len);
+struct ovn_dp_group *ovn_dp_group_create(
     struct ovsdb_idl_txn *ovnsb_txn, struct hmap *dp_groups,
     struct sbrec_logical_dp_group *sb_group,
     size_t desired_n, const unsigned long *desired_bitmap,
@@ -165,4 +168,22 @@  struct ovn_dp_group *ovn_dp_group_get_or_create(
     const struct ovn_datapaths *ls_datapaths,
     const struct ovn_datapaths *lr_datapaths);
 
+static inline void
+inc_ovn_dp_group_ref(struct ovn_dp_group *dpg)
+{
+    dpg->refcnt++;
+}
+
+static inline void
+dec_ovn_dp_group_ref(struct hmap *dp_groups, struct ovn_dp_group *dpg)
+{
+    dpg->refcnt--;
+
+    if (!dpg->refcnt) {
+        hmap_remove(dp_groups, &dpg->node);
+        free(dpg->bitmap);
+        free(dpg);
+    }
+}
+
 #endif /* LFLOW_MGR_H */
\ No newline at end of file
diff --git a/northd/northd.c b/northd/northd.c
index 96a5b52127..d21a070fdd 100644
--- a/northd/northd.c
+++ b/northd/northd.c
@@ -3745,244 +3745,6 @@  build_lb_port_related_data(
     build_lswitch_lbs_from_lrouter(lr_datapaths, lb_dps_map, lb_group_dps_map);
 }
 
-struct sb_lb {
-    struct hmap_node hmap_node;
-
-    const struct sbrec_load_balancer *slb;
-    struct ovn_dp_group *dpg;
-    struct ovn_dp_group *lr_dpg;
-    struct uuid lb_uuid;
-};
-
-static struct sb_lb *
-find_slb_in_sb_lbs(struct hmap *sb_lbs, const struct uuid *lb_uuid)
-{
-    struct sb_lb *sb_lb;
-    HMAP_FOR_EACH_WITH_HASH (sb_lb, hmap_node, uuid_hash(lb_uuid), sb_lbs) {
-        if (uuid_equals(&sb_lb->lb_uuid, lb_uuid)) {
-            return sb_lb;
-        }
-    }
-
-    return NULL;
-}
-
-/* Syncs relevant load balancers (applied to logical switches) to the
- * Southbound database.
- */
-void
-sync_lbs(struct ovsdb_idl_txn *ovnsb_txn,
-         const struct sbrec_load_balancer_table *sbrec_load_balancer_table,
-         struct ovn_datapaths *ls_datapaths,
-         struct ovn_datapaths *lr_datapaths,
-         struct hmap *lb_dps_map,
-         struct chassis_features *chassis_features)
-{
-    struct hmap ls_dp_groups = HMAP_INITIALIZER(&ls_dp_groups);
-    struct hmap lr_dp_groups = HMAP_INITIALIZER(&lr_dp_groups);
-    struct ovn_lb_datapaths *lb_dps;
-    struct hmap sb_lbs = HMAP_INITIALIZER(&sb_lbs);
-
-    /* Delete any stale SB load balancer rows and create datapath
-     * groups for existing ones. */
-    struct hmapx existing_lbs = HMAPX_INITIALIZER(&existing_lbs);
-    const struct sbrec_load_balancer *sbrec_lb;
-    SBREC_LOAD_BALANCER_TABLE_FOR_EACH_SAFE (sbrec_lb,
-                            sbrec_load_balancer_table) {
-        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
-        struct uuid lb_uuid;
-        if (!nb_lb_uuid || !uuid_from_string(&lb_uuid, nb_lb_uuid)) {
-            sbrec_load_balancer_delete(sbrec_lb);
-            continue;
-        }
-
-        /* Delete any SB load balancer entries that refer to NB load balancers
-         * that don't exist anymore or are not applied to switches/routers
-         * anymore.
-         *
-         * There is also a special case in which duplicate LBs might be created
-         * in the SB, e.g., due to the fact that OVSDB only ensures
-         * "at-least-once" consistency for clustered database tables that
-         * are not indexed in any way.
-         */
-        lb_dps = ovn_lb_datapaths_find(lb_dps_map, &lb_uuid);
-        if (!lb_dps || (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) ||
-            !hmapx_add(&existing_lbs, lb_dps)) {
-            sbrec_load_balancer_delete(sbrec_lb);
-            continue;
-        }
-
-        struct sb_lb *sb_lb = xzalloc(sizeof *sb_lb);
-        sb_lb->lb_uuid = lb_uuid;
-        sb_lb->slb = sbrec_lb;
-        hmap_insert(&sb_lbs, &sb_lb->hmap_node, uuid_hash(&lb_uuid));
-
-        /* Find or create datapath group for this load balancer. */
-        if (lb_dps->n_nb_ls) {
-            struct sbrec_logical_dp_group *ls_datapath_group
-                = chassis_features->ls_dpg_column
-                    ? sb_lb->slb->ls_datapath_group
-                    : sb_lb->slb->datapath_group; /* deprecated */
-            sb_lb->dpg = ovn_dp_group_get_or_create(
-                    ovnsb_txn, &ls_dp_groups,
-                    ls_datapath_group,
-                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
-                    ods_size(ls_datapaths), true,
-                    ls_datapaths, NULL);
-        }
-        if (lb_dps->n_nb_lr) {
-            sb_lb->lr_dpg = ovn_dp_group_get_or_create(
-                    ovnsb_txn, &lr_dp_groups,
-                    sb_lb->slb->lr_datapath_group,
-                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
-                    ods_size(lr_datapaths), false,
-                    NULL, lr_datapaths);
-        }
-    }
-    hmapx_destroy(&existing_lbs);
-
-    /* Create SB Load balancer records if not present and sync
-     * the SB load balancer columns. */
-    HMAP_FOR_EACH (lb_dps, hmap_node, lb_dps_map) {
-
-        if (!lb_dps->n_nb_ls && !lb_dps->n_nb_lr) {
-            continue;
-        }
-
-        /* Store the fact that northd provides the original (destination IP +
-         * transport port) tuple.
-         */
-        struct smap options;
-        smap_clone(&options, &lb_dps->lb->nlb->options);
-        smap_replace(&options, "hairpin_orig_tuple", "true");
-
-        struct sb_lb *sb_lb = find_slb_in_sb_lbs(&sb_lbs,
-                                            &lb_dps->lb->nlb->header_.uuid);
-        ovs_assert(!sb_lb || (sb_lb->slb && (sb_lb->dpg || sb_lb->lr_dpg)));
-        struct ovn_dp_group *lb_dpg = NULL, *lb_lr_dpg = NULL;
-        if (!sb_lb) {
-            sbrec_lb = sbrec_load_balancer_insert(ovnsb_txn);
-            char *lb_id = xasprintf(
-                UUID_FMT, UUID_ARGS(&lb_dps->lb->nlb->header_.uuid));
-            const struct smap external_ids =
-                SMAP_CONST1(&external_ids, "lb_id", lb_id);
-            sbrec_load_balancer_set_external_ids(sbrec_lb, &external_ids);
-            free(lb_id);
-        } else {
-            sbrec_lb = sb_lb->slb;
-            lb_dpg = sb_lb->dpg;
-            lb_lr_dpg = sb_lb->lr_dpg;
-        }
-
-        /* Find or create datapath group for this load balancer. */
-        if (!lb_dpg && lb_dps->n_nb_ls) {
-            struct sbrec_logical_dp_group *ls_datapath_group
-                = chassis_features->ls_dpg_column
-                    ? sbrec_lb->ls_datapath_group
-                    : sbrec_lb->datapath_group; /* deprecated */
-            lb_dpg = ovn_dp_group_get_or_create(
-                    ovnsb_txn, &ls_dp_groups,
-                    ls_datapath_group,
-                    lb_dps->n_nb_ls, lb_dps->nb_ls_map,
-                    ods_size(ls_datapaths), true,
-                    ls_datapaths, NULL);
-        }
-        if (!lb_lr_dpg && lb_dps->n_nb_lr) {
-            lb_lr_dpg = ovn_dp_group_get_or_create(
-                    ovnsb_txn, &lr_dp_groups,
-                    sbrec_lb->lr_datapath_group,
-                    lb_dps->n_nb_lr, lb_dps->nb_lr_map,
-                    ods_size(lr_datapaths), false,
-                    NULL, lr_datapaths);
-        }
-
-        /* Update columns. */
-        sbrec_load_balancer_set_name(sbrec_lb, lb_dps->lb->nlb->name);
-        sbrec_load_balancer_set_vips(sbrec_lb,
-                                     ovn_northd_lb_get_vips(lb_dps->lb));
-        sbrec_load_balancer_set_protocol(sbrec_lb, lb_dps->lb->nlb->protocol);
-
-        if (chassis_features->ls_dpg_column) {
-            sbrec_load_balancer_set_ls_datapath_group(
-                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
-            );
-            sbrec_load_balancer_set_datapath_group(sbrec_lb, NULL);
-        } else {
-            /* datapath_group column is deprecated. */
-            sbrec_load_balancer_set_ls_datapath_group(sbrec_lb, NULL);
-            sbrec_load_balancer_set_datapath_group(
-                sbrec_lb, lb_dpg ? lb_dpg->dp_group : NULL
-            );
-        }
-
-        sbrec_load_balancer_set_lr_datapath_group(
-            sbrec_lb, lb_lr_dpg ? lb_lr_dpg->dp_group : NULL
-        );
-        sbrec_load_balancer_set_options(sbrec_lb, &options);
-        /* Clearing 'datapaths' column, since 'dp_group' is in use. */
-        sbrec_load_balancer_set_datapaths(sbrec_lb, NULL, 0);
-        smap_destroy(&options);
-    }
-
-    struct ovn_dp_group *dpg;
-    HMAP_FOR_EACH_POP (dpg, node, &ls_dp_groups) {
-        bitmap_free(dpg->bitmap);
-        free(dpg);
-    }
-    hmap_destroy(&ls_dp_groups);
-
-    HMAP_FOR_EACH_POP (dpg, node, &lr_dp_groups) {
-        bitmap_free(dpg->bitmap);
-        free(dpg);
-    }
-    hmap_destroy(&lr_dp_groups);
-
-    struct sb_lb *sb_lb;
-    HMAP_FOR_EACH_POP (sb_lb, hmap_node, &sb_lbs) {
-        free(sb_lb);
-    }
-    hmap_destroy(&sb_lbs);
-
-    /* Datapath_Binding.load_balancers is not used anymore, it's still in the
-     * schema for compatibility reasons.  Reset it to empty, just in case.
-     */
-    struct ovn_datapath *od;
-    HMAP_FOR_EACH (od, key_node, &ls_datapaths->datapaths) {
-        ovs_assert(od->nbs);
-
-        if (od->sb->n_load_balancers) {
-            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
-        }
-    }
-    HMAP_FOR_EACH (od, key_node, &lr_datapaths->datapaths) {
-        ovs_assert(od->nbr);
-
-        if (od->sb->n_load_balancers) {
-            sbrec_datapath_binding_set_load_balancers(od->sb, NULL, 0);
-        }
-    }
-}
-
-bool
-check_sb_lb_duplicates(const struct sbrec_load_balancer_table *table)
-{
-    struct sset existing_nb_lb_uuids =
-        SSET_INITIALIZER(&existing_nb_lb_uuids);
-    const struct sbrec_load_balancer *sbrec_lb;
-    bool duplicates = false;
-
-    SBREC_LOAD_BALANCER_TABLE_FOR_EACH (sbrec_lb, table) {
-        const char *nb_lb_uuid = smap_get(&sbrec_lb->external_ids, "lb_id");
-        if (nb_lb_uuid && !sset_add(&existing_nb_lb_uuids, nb_lb_uuid)) {
-            duplicates = true;
-            break;
-        }
-    }
-
-    sset_destroy(&existing_nb_lb_uuids);
-    return duplicates;
-}
-
 /* Syncs the SB port binding for the ovn_port 'op' of a logical switch port.
  * Caller should make sure that the OVN SB IDL txn is not NULL.  Presently it
  * only syncs the nat column of port binding corresponding to the 'op->nbsp' */
diff --git a/northd/northd.h b/northd/northd.h
index 297b1a65b9..579edb8bdf 100644
--- a/northd/northd.h
+++ b/northd/northd.h
@@ -715,12 +715,6 @@  const char *northd_get_svc_monitor_mac(void);
 
 const struct ovn_datapath *northd_get_datapath_for_port(
     const struct hmap *ls_ports, const char *port_name);
-void sync_lbs(struct ovsdb_idl_txn *, const struct sbrec_load_balancer_table *,
-              struct ovn_datapaths *ls_datapaths,
-              struct ovn_datapaths *lr_datapaths,
-              struct hmap *lbs,
-              struct chassis_features *chassis_features);
-bool check_sb_lb_duplicates(const struct sbrec_load_balancer_table *);
 
 struct lr_stateful_table;
 void sync_pbs(struct ovsdb_idl_txn *, struct hmap *ls_ports,
diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
index 263eb343eb..5c33a8a6c2 100644
--- a/tests/ovn-northd.at
+++ b/tests/ovn-northd.at
@@ -2938,6 +2938,8 @@  sw1_sb_uuid=$(fetch_column datapath_binding _uuid external_ids:name=sw1)
 echo "$sw0_sb_uuid" > sw_sb_uuids
 echo "$sw1_sb_uuid" >> sw_sb_uuids
 
+lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
+
 echo
 echo "__file__:__line__: Check that SB lb0 has sw0 and sw1 in datapaths column."
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
@@ -2945,6 +2947,8 @@  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
 $(cat sw_sb_uuids | sort)
 ])
 
+lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
+
 echo
 echo "__file__:__line__: Check that SB lbg0 has sw0 and sw1 in datapaths column."
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
@@ -3014,6 +3018,64 @@  echo "__file__:__line__: Set hairpin_snat_ip on lb1 and check that SB DB is upda
 check ovn-nbctl --wait=sb set Load_Balancer lb1 options:hairpin_snat_ip="42.42.42.42 4242::4242"
 check_column "$lb1_uuid" sb:load_balancer _uuid name=lb1 options='{hairpin_orig_tuple="true", hairpin_snat_ip="42.42.42.42 4242::4242"}'
 
+echo
+echo "__file__:__line__: Set option:bar=foo on lbg1 and check that sync_to_sb_lb engine node didn't recompute."
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lbg1 options:bar=foo
+check_engine_stats sync_to_sb_lb norecompute compute
+
+# Manually clear the sb:load_balancer.ls_datapath_group column for
+# all load balancers and check that it is resynced back when the
+# NB lbg1 is modified.
+
+echo
+echo "__file__:__line__: Clear the ls_datapath_group column of all the SB load balancers."
+
+for l in $(ovn-sbctl --bare --columns _uuid list load_balancer)
+do
+  check ovn-sbctl clear load_balancer $l ls_datapath_group
+done
+
+lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
+AT_CHECK([test "$lbg1_dp_group" = ""])
+
+echo
+echo "__file__:__line__: Set option:foo=bar on lbg1 and check that sync_to_sb_lb engine node recomputed."
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lbg1 options:foo=bar
+check_engine_stats sync_to_sb_lb recompute compute
+
+# Manually clear the sb:load_balancer.lr_datapath_group column for
+# all load balancers and check that it is resynced back when the
+# NB lbg1 is modified.
+
+echo
+echo "__file__:__line__: Clear the lr_datapath_group column of all the SB load balancers."
+
+for l in $(ovn-sbctl --bare --columns _uuid list load_balancer)
+do
+  check ovn-sbctl clear load_balancer $l lr_datapath_group
+done
+
+echo
+echo "__file__:__line__: Set option:foo=foo on lbg1 and check that sync_to_sb_lb engine node recomputed."
+
+check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
+check ovn-nbctl --wait=sb set load_balancer lbg1 options:foo=foo
+check_engine_stats sync_to_sb_lb recompute compute
+
+lbg1_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg1)
+
+echo
+echo "__file__:__line__: Check that SB lbg1 has sw0 and sw1 in datapaths column."
+
+AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
+                    | grep -A1 $lbg1_dp_group | tail -1 | tr ' ' '\n' | sort], [0], [dnl
+$(cat sw_sb_uuids | sort)
+])
+
 echo
 echo "__file__:__line__: Delete load balancers lb1 and lbg1 and check that datapath sw1's load_balancers is still empty."
 
@@ -3024,6 +3086,8 @@  echo
 echo "__file__:__line__: Delete switch sw0."
 check ovn-nbctl --wait=sb ls-del sw0
 
+lb0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lb0)
+
 echo
 echo "__file__:__line__: Check that SB lb0 has only sw1 in datapaths column."
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
@@ -3031,6 +3095,8 @@  AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Gr
 $sw1_sb_uuid
 ])
 
+lbg0_dp_group=$(fetch_column sb:load_balancer ls_datapath_group name=lbg0)
+
 echo
 echo "__file__:__line__: Check that SB lbg0 has only sw1 in datapaths column."
 AT_CHECK_UNQUOTED([ovn-sbctl --bare --columns _uuid,datapaths find Logical_DP_Group dnl
@@ -10521,8 +10587,7 @@  check ovn-nbctl --wait=sb lb-add lb1 10.0.0.10:80 10.0.0.3:80
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
-
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 
@@ -10531,7 +10596,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10541,7 +10606,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
 
@@ -10550,7 +10615,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10560,7 +10625,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10618,7 +10683,7 @@  check_engine_stats lr_stateful norecompute compute
 # A LB applied to a switch/router triggers:
 # - a recompute in the first iteration (handling northd change)
 # - a compute in the second iteration (handling SB update)
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE((1))
@@ -10631,7 +10696,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Cleanup the vip of lb1.
@@ -10642,7 +10707,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Set the vips of lb1 back
@@ -10653,7 +10718,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Add another vip to lb1
@@ -10664,7 +10729,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE(1)
 
 # Disassociate lb1 from sw0. There should be a full recompute of northd engine node.
@@ -10687,7 +10752,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10708,7 +10773,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Modify the backend of the lb1 vip
@@ -10718,7 +10783,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10728,7 +10793,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10738,7 +10803,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10748,7 +10813,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10779,7 +10844,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10796,7 +10861,7 @@  check ovn-nbctl --wait=sb add load_balancer_group . load_Balancer $lb1_uuid
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10806,7 +10871,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 
 # Update lb and this should not result in northd recompute
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10816,7 +10881,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 
 # Modify the backend of the lb1 vip
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10826,7 +10891,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10837,7 +10902,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10848,7 +10913,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10859,7 +10924,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10875,7 +10940,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Modify the backend of the lb1 vip
@@ -10885,7 +10950,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Cleanup the vip of lb1.
@@ -10895,7 +10960,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Set the vips of lb1 back
@@ -10905,7 +10970,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Add another vip to lb1
@@ -10915,7 +10980,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10935,7 +11000,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10979,7 +11044,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10989,7 +11054,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -10998,7 +11063,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11008,7 +11073,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11018,7 +11083,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11028,7 +11093,7 @@  check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11059,7 +11124,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Deleting lb2 should result in lflow recompute as it is
@@ -11071,7 +11136,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats ls_stateful norecompute compute
 check_engine_stats lflow norecompute compute
-check_engine_stats sync_to_sb_lb recompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11281,6 +11346,7 @@  check ovn-nbctl --wait=sb ls-lb-add sw0 lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow norecompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Clear the VIPs of lb1
@@ -11289,6 +11355,7 @@  check ovn-nbctl --wait=sb clear load_balancer . vips
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd norecompute compute
 check_engine_stats lflow norecompute compute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats
@@ -11296,6 +11363,7 @@  check ovn-nbctl --wait=sb lb-del lb1
 check_engine_stats lb_data norecompute compute
 check_engine_stats northd recompute nocompute
 check_engine_stats lflow recompute nocompute
+check_engine_stats sync_to_sb_lb recompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 AT_CLEANUP
@@ -11544,6 +11612,7 @@  check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11557,6 +11626,7 @@  check_engine_stats northd recompute compute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11569,6 +11639,7 @@  check_engine_stats northd recompute compute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11595,6 +11666,7 @@  check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11607,6 +11679,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Update the NAT options column
@@ -11616,6 +11689,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Update the NAT external_ip column
@@ -11626,6 +11700,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Update the NAT logical_ip column
@@ -11636,6 +11711,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Update the NAT type
@@ -11646,6 +11722,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Create a dnat_and_snat NAT with external_mac and logical_port
@@ -11656,6 +11733,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 nat2_uuid=$(ovn-nbctl --bare --columns _uuid find nat logical_ip=10.0.0.4)
@@ -11667,6 +11745,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Create a load balancer and add the lb vip as NAT
@@ -11683,6 +11762,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11694,6 +11774,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11705,6 +11786,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11716,6 +11798,7 @@  check_engine_stats northd norecompute compute
 check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11727,6 +11810,7 @@  check_engine_stats lr_nat norecompute compute
 check_engine_stats lr_stateful norecompute compute
 check_engine_stats lflow norecompute compute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb norecompute compute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
 # Create router Policy
@@ -11736,6 +11820,7 @@  check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE
 
@@ -11745,6 +11830,7 @@  check_engine_stats northd recompute nocompute
 check_engine_stats lr_nat recompute nocompute
 check_engine_stats lr_stateful recompute nocompute
 check_engine_stats sync_to_sb_pb recompute nocompute
+check_engine_stats sync_to_sb_lb recompute nocompute
 check_engine_stats lflow recompute nocompute
 CHECK_NO_CHANGE_AFTER_RECOMPUTE