diff mbox series

[ovs-dev,v2,4/5] json: Add yielding json destroy function.

Message ID 20240112230018.34044-5-frode.nordahl@canonical.com
State Changes Requested
Delegated to: Ilya Maximets
Headers show
Series Introduce cooperative multitasking to improve OVSDB RAFT cluster operation. | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test success github build: passed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Frode Nordahl Jan. 12, 2024, 11 p.m. UTC
Destroying JSON objects may be time consuming.

Add json_destroy_with_yield() function that make use of the
cooperative multitasking module to yield during processing,
allowing time sensitive tasks in other parts of the program
to be completed during processing.

We keep this new function private to OVS by adding a new
lib/json.h header file.

The include guard in the public include/openvswitch/json.h is
updated to contain the OPENVSWITCH prefix to be in line with the
other public header files, allowing us to use the non-prefixed
version in our private lib/json.h.

Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com>
---
 include/openvswitch/json.h | 10 +++++-----
 lib/automake.mk            |  1 +
 lib/json.c                 | 36 +++++++++++++++++++++++++++---------
 lib/json.h                 | 31 +++++++++++++++++++++++++++++++
 4 files changed, 64 insertions(+), 14 deletions(-)
 create mode 100644 lib/json.h

Comments

Ilya Maximets Jan. 16, 2024, 8:44 p.m. UTC | #1
On 1/13/24 00:00, Frode Nordahl wrote:
> Destroying JSON objects may be time consuming.
> 
> Add json_destroy_with_yield() function that make use of the
> cooperative multitasking module to yield during processing,
> allowing time sensitive tasks in other parts of the program
> to be completed during processing.
> 
> We keep this new function private to OVS by adding a new
> lib/json.h header file.
> 
> The include guard in the public include/openvswitch/json.h is
> updated to contain the OPENVSWITCH prefix to be in line with the
> other public header files, allowing us to use the non-prefixed
> version in our private lib/json.h.
> 
> Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com>
> ---
>  include/openvswitch/json.h | 10 +++++-----
>  lib/automake.mk            |  1 +
>  lib/json.c                 | 36 +++++++++++++++++++++++++++---------
>  lib/json.h                 | 31 +++++++++++++++++++++++++++++++
>  4 files changed, 64 insertions(+), 14 deletions(-)
>  create mode 100644 lib/json.h

This one looks fine.  Though I disagree with your previous statement
that we don't need to yield while serializing. :)

Running 500-node density-heavy with ovn-heater and election timer
set to 1 second I see that while scaling up the cluster, new clients
are connecting, and somewhere at 300 clients the database size grows
enough, so building an initial reply starts to significantly overrun
the yield threshold json_destory_with_yield() in ovsdb_monitor_get_update().
At this point we need to start yielding in json_serialized_object_create()
as well, so the cluster keeps to be stable.

So, I added back your previous code with slight modifications.  And
with that I can scale 500 nodes just fine.  And I see it yielding
a lot there at high scale:

diff --git a/lib/json.c b/lib/json.c
index 44a62ffe3..001f6e6ab 100644
--- a/lib/json.c
+++ b/lib/json.c
@@ -33,6 +33,12 @@
 #include "util.h"
 #include "uuid.h"
 
+/* Non-public JSSF_* flags.  Must not overlap with public ones defined
+ * in include/openvswitch/json.h. */
+enum {
+    JSSF_YIELD = 1 << 7,
+};
+
 /* The type of a JSON token. */
 enum json_token_type {
     T_EOF = 0,
@@ -191,6 +197,14 @@ json_serialized_object_create(const struct json *src)
     return json;
 }
 
+struct json *
+json_serialized_object_create_with_yield(const struct json *src)
+{
+    struct json *json = json_create(JSON_SERIALIZED_OBJECT);
+    json->string = json_to_string(src, JSSF_SORT | JSSF_YIELD);
+    return json;
+}
+
 struct json *
 json_array_create_empty(void)
 {
@@ -1682,6 +1696,10 @@ json_serialize_object(const struct shash *object, struct json_serializer *s)
     s->depth++;
     indent_line(s);
 
+    if (s->flags & JSSF_YIELD) {
+        cooperative_multitasking_yield();
+    }
+
     if (s->flags & JSSF_SORT) {
         const struct shash_node **nodes;
         size_t n, i;
@@ -1715,6 +1733,10 @@ json_serialize_array(const struct json_array *array, struct json_serializer *s)
     ds_put_char(ds, '[');
     s->depth++;
 
+    if (s->flags & JSSF_YIELD) {
+        cooperative_multitasking_yield();
+    }
+
     if (array->n > 0) {
         indent_line(s);
 
diff --git a/lib/json.h b/lib/json.h
index 350943d13..4ad440b39 100644
--- a/lib/json.h
+++ b/lib/json.h
@@ -27,5 +27,6 @@ json_destroy_with_yield(struct json *json)
     }
 }
 
+struct json *json_serialized_object_create_with_yield(const struct json *);
 
 #endif /* JSON_H */
diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
index 718f16db0..c3bfae3d2 100644
--- a/ovsdb/monitor.c
+++ b/ovsdb/monitor.c
@@ -1293,7 +1293,7 @@ ovsdb_monitor_get_update(
                         /* Pre-serializing the object to avoid doing this
                          * for every client. */
                         json_serialized =
-                            json_serialized_object_create(json);
+                            json_serialized_object_create_with_yield(json);
                         json_destroy_with_yield(json);
                         json = json_serialized;
                     }
---

I'm still hitting some overruns at higher scale, but I guess 1 second
is just really not enough there.  My guess is that we spend significant
amount of time in socket I/O...  But that's a story for another time
and likely much more debugging to actually understand what is going on.

For now, I'd suggest to get the changes above.
What do you think?

Best regards, Ilya Maximets.
Frode Nordahl Jan. 16, 2024, 10:01 p.m. UTC | #2
On Tue, Jan 16, 2024 at 9:44 PM Ilya Maximets <i.maximets@ovn.org> wrote:
>
> On 1/13/24 00:00, Frode Nordahl wrote:
> > Destroying JSON objects may be time consuming.
> >
> > Add json_destroy_with_yield() function that make use of the
> > cooperative multitasking module to yield during processing,
> > allowing time sensitive tasks in other parts of the program
> > to be completed during processing.
> >
> > We keep this new function private to OVS by adding a new
> > lib/json.h header file.
> >
> > The include guard in the public include/openvswitch/json.h is
> > updated to contain the OPENVSWITCH prefix to be in line with the
> > other public header files, allowing us to use the non-prefixed
> > version in our private lib/json.h.
> >
> > Signed-off-by: Frode Nordahl <frode.nordahl@canonical.com>
> > ---
> >  include/openvswitch/json.h | 10 +++++-----
> >  lib/automake.mk            |  1 +
> >  lib/json.c                 | 36 +++++++++++++++++++++++++++---------
> >  lib/json.h                 | 31 +++++++++++++++++++++++++++++++
> >  4 files changed, 64 insertions(+), 14 deletions(-)
> >  create mode 100644 lib/json.h
>
> This one looks fine.  Though I disagree with your previous statement
> that we don't need to yield while serializing. :)
>
> Running 500-node density-heavy with ovn-heater and election timer
> set to 1 second I see that while scaling up the cluster, new clients
> are connecting, and somewhere at 300 clients the database size grows
> enough, so building an initial reply starts to significantly overrun
> the yield threshold json_destory_with_yield() in ovsdb_monitor_get_update().
> At this point we need to start yielding in json_serialized_object_create()
> as well, so the cluster keeps to be stable.
>
> So, I added back your previous code with slight modifications.  And
> with that I can scale 500 nodes just fine.  And I see it yielding
> a lot there at high scale:
>
> diff --git a/lib/json.c b/lib/json.c
> index 44a62ffe3..001f6e6ab 100644
> --- a/lib/json.c
> +++ b/lib/json.c
> @@ -33,6 +33,12 @@
>  #include "util.h"
>  #include "uuid.h"
>
> +/* Non-public JSSF_* flags.  Must not overlap with public ones defined
> + * in include/openvswitch/json.h. */
> +enum {
> +    JSSF_YIELD = 1 << 7,
> +};
> +
>  /* The type of a JSON token. */
>  enum json_token_type {
>      T_EOF = 0,
> @@ -191,6 +197,14 @@ json_serialized_object_create(const struct json *src)
>      return json;
>  }
>
> +struct json *
> +json_serialized_object_create_with_yield(const struct json *src)
> +{
> +    struct json *json = json_create(JSON_SERIALIZED_OBJECT);
> +    json->string = json_to_string(src, JSSF_SORT | JSSF_YIELD);
> +    return json;
> +}
> +
>  struct json *
>  json_array_create_empty(void)
>  {
> @@ -1682,6 +1696,10 @@ json_serialize_object(const struct shash *object, struct json_serializer *s)
>      s->depth++;
>      indent_line(s);
>
> +    if (s->flags & JSSF_YIELD) {
> +        cooperative_multitasking_yield();
> +    }
> +
>      if (s->flags & JSSF_SORT) {
>          const struct shash_node **nodes;
>          size_t n, i;
> @@ -1715,6 +1733,10 @@ json_serialize_array(const struct json_array *array, struct json_serializer *s)
>      ds_put_char(ds, '[');
>      s->depth++;
>
> +    if (s->flags & JSSF_YIELD) {
> +        cooperative_multitasking_yield();
> +    }
> +
>      if (array->n > 0) {
>          indent_line(s);
>
> diff --git a/lib/json.h b/lib/json.h
> index 350943d13..4ad440b39 100644
> --- a/lib/json.h
> +++ b/lib/json.h
> @@ -27,5 +27,6 @@ json_destroy_with_yield(struct json *json)
>      }
>  }
>
> +struct json *json_serialized_object_create_with_yield(const struct json *);
>
>  #endif /* JSON_H */
> diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
> index 718f16db0..c3bfae3d2 100644
> --- a/ovsdb/monitor.c
> +++ b/ovsdb/monitor.c
> @@ -1293,7 +1293,7 @@ ovsdb_monitor_get_update(
>                          /* Pre-serializing the object to avoid doing this
>                           * for every client. */
>                          json_serialized =
> -                            json_serialized_object_create(json);
> +                            json_serialized_object_create_with_yield(json);
>                          json_destroy_with_yield(json);
>                          json = json_serialized;
>                      }
> ---
>
> I'm still hitting some overruns at higher scale, but I guess 1 second
> is just really not enough there.  My guess is that we spend significant
> amount of time in socket I/O...  But that's a story for another time
> and likely much more debugging to actually understand what is going on.
>
> For now, I'd suggest to get the changes above.
> What do you think?

Yes, I concluded that actually supporting the use of a second or
sub-second election timer would require a different ballpark of effort
than making this work well for reasonably configured clusters.  We
have to start somewhere :)

Thanks for your help in figuring out where to yield in the
json_serialized_object_create() call path, I got a bit lost there in
my initial attempt, I'll incorporate your proposal in v3.
diff mbox series

Patch

diff --git a/include/openvswitch/json.h b/include/openvswitch/json.h
index 35b403c29..3da05bf5c 100644
--- a/include/openvswitch/json.h
+++ b/include/openvswitch/json.h
@@ -14,8 +14,8 @@ 
  * limitations under the License.
  */
 
-#ifndef JSON_H
-#define JSON_H 1
+#ifndef OPENVSWITCH_JSON_H
+#define OPENVSWITCH_JSON_H 1
 
 /* This is an implementation of JavaScript Object Notation (JSON) as specified
  * by RFC 4627.  It is intended to fully comply with RFC 4627, with the
@@ -158,14 +158,14 @@  json_clone(const struct json *json_)
     return json;
 }
 
-void json_destroy__(struct json *json);
+void json_destroy__(struct json *json, bool);
 
 /* Frees 'json' and everything it points to, recursively. */
 static inline void
 json_destroy(struct json *json)
 {
     if (json && !--json->count) {
-        json_destroy__(json);
+        json_destroy__(json, false);
     }
 }
 
@@ -173,4 +173,4 @@  json_destroy(struct json *json)
 }
 #endif
 
-#endif /* json.h */
+#endif /* OPENVSWITCH_JSON_H */
diff --git a/lib/automake.mk b/lib/automake.mk
index 8596171c6..78d6e6516 100644
--- a/lib/automake.mk
+++ b/lib/automake.mk
@@ -178,6 +178,7 @@  lib_libopenvswitch_la_SOURCES = \
 	lib/jhash.c \
 	lib/jhash.h \
 	lib/json.c \
+	lib/json.h \
 	lib/jsonrpc.c \
 	lib/jsonrpc.h \
 	lib/lacp.c \
diff --git a/lib/json.c b/lib/json.c
index aded8bb01..e65ce2b23 100644
--- a/lib/json.c
+++ b/lib/json.c
@@ -24,8 +24,10 @@ 
 #include <limits.h>
 #include <string.h>
 
+#include "cooperative-multitasking.h"
 #include "openvswitch/dynamic-string.h"
 #include "hash.h"
+#include "json.h"
 #include "openvswitch/shash.h"
 #include "unicode.h"
 #include "util.h"
@@ -360,20 +362,20 @@  json_integer(const struct json *json)
     return json->integer;
 }
 
-static void json_destroy_object(struct shash *object);
-static void json_destroy_array(struct json_array *array);
+static void json_destroy_object(struct shash *object, bool yield);
+static void json_destroy_array(struct json_array *array, bool yield);
 
 /* Frees 'json' and everything it points to, recursively. */
 void
-json_destroy__(struct json *json)
+json_destroy__(struct json *json, bool yield)
 {
     switch (json->type) {
     case JSON_OBJECT:
-        json_destroy_object(json->object);
+        json_destroy_object(json->object, yield);
         break;
 
     case JSON_ARRAY:
-        json_destroy_array(&json->array);
+        json_destroy_array(&json->array, yield);
         break;
 
     case JSON_STRING:
@@ -395,14 +397,22 @@  json_destroy__(struct json *json)
 }
 
 static void
-json_destroy_object(struct shash *object)
+json_destroy_object(struct shash *object, bool yield)
 {
     struct shash_node *node;
 
+    if (yield) {
+        cooperative_multitasking_yield();
+    }
+
     SHASH_FOR_EACH_SAFE (node, object) {
         struct json *value = node->data;
 
-        json_destroy(value);
+        if (yield) {
+            json_destroy_with_yield(value);
+        } else {
+            json_destroy(value);
+        }
         shash_delete(object, node);
     }
     shash_destroy(object);
@@ -410,12 +420,20 @@  json_destroy_object(struct shash *object)
 }
 
 static void
-json_destroy_array(struct json_array *array)
+json_destroy_array(struct json_array *array, bool yield)
 {
     size_t i;
 
+    if (yield) {
+        cooperative_multitasking_yield();
+    }
+
     for (i = 0; i < array->n; i++) {
-        json_destroy(array->elems[i]);
+        if (yield) {
+            json_destroy_with_yield(array->elems[i]);
+        } else {
+            json_destroy(array->elems[i]);
+        }
     }
     free(array->elems);
 }
diff --git a/lib/json.h b/lib/json.h
new file mode 100644
index 000000000..350943d13
--- /dev/null
+++ b/lib/json.h
@@ -0,0 +1,31 @@ 
+/*
+ * Copyright (c) 2024 Canonical Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JSON_H
+#define JSON_H 1
+
+#include "openvswitch/json.h"
+
+static inline void
+json_destroy_with_yield(struct json *json)
+{
+    if (json && !--json->count) {
+        json_destroy__(json, true);
+    }
+}
+
+
+#endif /* JSON_H */