Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport lazy collection init #3634

Closed
wants to merge 5 commits into from

Conversation

SchoolGuy
Copy link
Member

Linked Items

Fixes #3596

Description

This PR attempts to backport the lazy collection init. This will be off per default.

Behaviour changes

Old: Cobbler wasn't available until all items are fully loaded.

New: Cobbler will startup up with only the item names in memory until the item is first accessed.

Category

This is related to a:

  • Bugfix
  • Feature
  • Packaging
  • Docs
  • Code Quality
  • Refactoring
  • Miscellaneous

Tests

  • Unit-Tests were created
  • System-Tests were created
  • Code is already covered by Unit-Tests
  • Code is already covered by System-Tests
  • No tests required

@SchoolGuy SchoolGuy added the backport Backported changes and features. label Feb 29, 2024
@SchoolGuy SchoolGuy added this to the v3.3.5 milestone Feb 29, 2024
@SchoolGuy SchoolGuy linked an issue Feb 29, 2024 that may be closed by this pull request
3 tasks
@SchoolGuy SchoolGuy changed the title Increase application version to 3.3.5 Backport lazy collection init Feb 29, 2024
@SchoolGuy SchoolGuy force-pushed the backport/lazy-collection-init branch from 51e67ed to 4b6f2f2 Compare March 1, 2024 16:44
This is a backport of a25eb46.

The items can be loaded when accessing the properties of the elements.
@SchoolGuy SchoolGuy force-pushed the backport/lazy-collection-init branch from 4b6f2f2 to a8e8da9 Compare March 1, 2024 16:55
@SchoolGuy SchoolGuy requested a review from a team March 1, 2024 17:13
@SchoolGuy SchoolGuy marked this pull request as ready for review March 1, 2024 17:13
@SchoolGuy
Copy link
Member Author

SchoolGuy commented Mar 1, 2024

I will try to test the PR as soon as possible regarding the performance gains that it potentially presents. (The number of target items is 500000).

@SchoolGuy
Copy link
Member Author

@tpw56j Do you have an idea for the backport of how to prevent the recursion error for the IP addresses? I don't want to introduce breaking changes for this one and as such I am not sure where to start debugging/fixing at the moment...

@tpw56j
Copy link
Contributor

tpw56j commented Mar 5, 2024

What does an IP addresses recursion error look like?

@SchoolGuy
Copy link
Member Author

SchoolGuy commented Mar 5, 2024

@tpw56j Something like this:

Mar 05 11:50:58 cobbler cobblerd[2602]: INFO | Exception occurred: <class 'RecursionError'>
Mar 05 11:50:58 cobbler cobblerd[2602]: INFO | Exception value: maximum recursion depth exceeded while calling a Python object
Mar 05 11:50:58 cobbler cobblerd[2602]: INFO | Exception Info:
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/remote.py", line 3771, in _dispatch
Mar 05 11:50:58 cobbler cobblerd[2602]:     return method_handle(*params)
Mar 05 11:50:58 cobbler cobblerd[2602]:            ^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/remote.py", line 941, in get_items
Mar 05 11:50:58 cobbler cobblerd[2602]:     items = [x.to_dict() for x in self.api.get_items(what)]
Mar 05 11:50:58 cobbler cobblerd[2602]:             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/remote.py", line 941, in <listcomp>
Mar 05 11:50:58 cobbler cobblerd[2602]:     items = [x.to_dict() for x in self.api.get_items(what)]
Mar 05 11:50:58 cobbler cobblerd[2602]:              ^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 957, in to_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     self.deserialize()
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
Mar 05 11:50:58 cobbler cobblerd[2602]:     self.from_dict(item_dict)
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     super().from_dict(dictionary)
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     setattr(self, lowered_key, dictionary[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
Mar 05 11:50:58 cobbler cobblerd[2602]:     network_iface.from_dict(value[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     setattr(self, key, dictionary[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
Mar 05 11:50:58 cobbler cobblerd[2602]:     matched = self.__api.find_items("system", {"ip_address": address})
Mar 05 11:50:58 cobbler cobblerd[2602]:               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
Mar 05 11:50:58 cobbler cobblerd[2602]:     return self.__find_with_collection(
Mar 05 11:50:58 cobbler cobblerd[2602]:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
Mar 05 11:50:58 cobbler cobblerd[2602]:     return items.find(
Mar 05 11:50:58 cobbler cobblerd[2602]:            ^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
Mar 05 11:50:58 cobbler cobblerd[2602]:     obj.deserialize()
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
Mar 05 11:50:58 cobbler cobblerd[2602]:     self.from_dict(item_dict)
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     super().from_dict(dictionary)
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     setattr(self, lowered_key, dictionary[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
Mar 05 11:50:58 cobbler cobblerd[2602]:     network_iface.from_dict(value[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
Mar 05 11:50:58 cobbler cobblerd[2602]:     setattr(self, key, dictionary[key])
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
Mar 05 11:50:58 cobbler cobblerd[2602]:     matched = self.__api.find_items("system", {"ip_address": address})
Mar 05 11:50:58 cobbler cobblerd[2602]:               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
Mar 05 11:50:58 cobbler cobblerd[2602]:     return self.__find_with_collection(
Mar 05 11:50:58 cobbler cobblerd[2602]:            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
Mar 05 11:50:58 cobbler cobblerd[2602]:     return items.find(
Mar 05 11:50:58 cobbler cobblerd[2602]:            ^^^^^^^^^^^
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
Mar 05 11:50:58 cobbler cobblerd[2602]:     obj.deserialize()
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
Mar 05 11:50:58 cobbler cobblerd[2602]:     self.from_dict(item_dict)
Mar 05 11:50:58 cobbler cobblerd[2602]:   File "/usr/lib/python3.11/site-packages/cob
....

Edit: The issue is that since not everything is deserialized it tries to deserialize during deserializing. Do we just introduce a check for if the object is inmemory?

@tpw56j
Copy link
Contributor

tpw56j commented Mar 5, 2024

One possible error could be here:

serialized_interfaces[interface_key] = interfaces[interface_key].to_dict()

It should be like this:
serialized_interfaces[interface_key] = interfaces[interface_key].to_dict(resolved)

@SchoolGuy
Copy link
Member Author

@tpw56j Yes that is certainly a bug but this is the inverse direction that is shown in the stacktrace. Any more ideas?

@tpw56j
Copy link
Contributor

tpw56j commented Mar 5, 2024

The recursion occurs here:

@LazyProperty

The interface as a whole must have LazyProperty, but not its individual properties.
Check the NetworkInterface class in the main branch and take the decorators from there.

@SchoolGuy
Copy link
Member Author

Ahhhh! Okay I see that was my "search and replace" moment when I was bulk replacing decorators. Thanks for catching that!

@SchoolGuy
Copy link
Member Author

@tpw56j Sorry to say but it wasn't enough to fix the issue. We still have recursion during adding a new system since it tries to deserialize during deserializing (ip_address):

Traceback (most recent call last):
  File "/root/./fillup.py", line 40, in <module>
    add_system(api, 1, str(next(hosts_iterator)))
  File "/root/./fillup.py", line 20, in add_system
    system.modify_interface({"macaddress-default": "random", "ip_address-default": ip})
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 945, in modify_interface
    self.interfaces[interface].modify_interface({key: interface_values[key]})
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 713, in modify_interface
    self.mac_address = value
    ^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 305, in mac_address
    address = utils.get_random_mac(self.__api)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/utils.py", line 324, in get_random_mac
    while systems.find(mac_address=mac):
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
    return self.__find_with_collection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
    return items.find(
           ^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
    return self.__find_with_collection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
    return items.find(
           ^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
    return self.__find_with_collection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
    return items.find(
           ^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
    return self.__find_with_collection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
    return items.find(
           ^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1020, in find_items
    return self.__find_with_collection(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/api.py", line 1027, in __find_with_collection
    return items.find(
           ^^^^^^^^^^^
  File "/usr/lib/python3.11/site-packages/cobbler/cobbler_collections/collection.py", line 141, in find
    obj.deserialize()
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 1034, in deserialize
    self.from_dict(item_dict)
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 834, in from_dict
    super().from_dict(dictionary)
  File "/usr/lib/python3.11/site-packages/cobbler/items/item.py", line 940, in from_dict
    setattr(self, lowered_key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 931, in interfaces
    network_iface.from_dict(value[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 67, in from_dict
    setattr(self, key, dictionary[key])
  File "/usr/lib/python3.11/site-packages/cobbler/items/system.py", line 274, in ip_address
    matched = self.__api.find_items("system", {"ip_address": address})
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

@SchoolGuy
Copy link
Member Author

SchoolGuy commented Mar 5, 2024

This bug is also present on main:

95f3a3d32d93:~ # cobbler system add --name=testsys2 --mac-address="random" --ip-address="192.168.0.6" --profile="testprof"
exception on server: maximum recursion depth exceeded while calling a Python object
95f3a3d32d93:~ # cobbler version
Cobbler 3.4.0
  source: ?, ?
  build time: Tue Mar  5 17:20:55 2024

This bug is present for ip_address, ipv6_address, mac_address, and dns_name.

Edit: If NetworkInterfaces would be a dedicated collection this would still be a problem as we require all data to be loaded to validate if the data that is loaded is valid. The reason why this is not a problem with lazy_start: false is due to the fact that data is loaded sequentially.

@SchoolGuy
Copy link
Member Author

@tpw56j I have pushed an experimental commit, it solves the chicken-egg problem that I described. I would love it if you would play the devil's advocate for my change and propose a better solution if you can offer one!

@tpw56j
Copy link
Contributor

tpw56j commented Mar 5, 2024

Your commit breaks the logic of collection.find and in other places in the code it will stop working as expected.
The problem here is that we are trying to deserialize an object that has not yet been initialized. So I propose another fix:

     def deserialize(self) -> None:
         """
         Deserializes the object itself and, if necessary, recursively all the objects it depends on.
         """
 
         def deserialize_ancestor(ancestor_item_type: str, ancestor_name: str):
             if ancestor_name not in {"", enums.VALUE_INHERITED}:
                 ancestor = self.api.get_items(ancestor_item_type).get(ancestor_name)
                 if ancestor is not None and not ancestor.inmemory:
                     ancestor.deserialize()
 
+        if not self._has_initialized:
+            return
+
         item_dict = self.api.deserialize_item(self)
         if item_dict["inmemory"]:
             for ancestor_item_type, ancestor_deps in Item.TYPE_DEPENDENCIES.items():

This solves the chicken-egg problem.

Along the way, I found another bug in utils.get_random_mac, which makes searching by passing an array parameter instead of a string to collection.find:

# cobbler system add --name=testsys2 --mac-address="random" --ip-address="192.168.0.7" --profile="profile02"
exception on server: 'list' object has no attribute 'startswith'

Fix:

     else:
         raise CX("virt mac assignment not yet supported")
 
     result = ":".join([f"{x:02x}" for x in mac])
     systems = api_handle.systems()
-    while systems.find(mac_address=mac):
+    while systems.find(mac_address=result):
         result = get_random_mac(api_handle)
 
     return result

@SchoolGuy
Copy link
Member Author

SchoolGuy commented Mar 5, 2024

@tpw56j Thanks for the help! I will try it out after I slept. Recovering my k8s cluster atm. 😴

@tpw56j
Copy link
Contributor

tpw56j commented Mar 6, 2024

I was haunted last night by your idea of a lazy start with 500,000 target items.
The first search after the start in the collection by an attribute that is not a name will lead to the need to deserialize all items of the collection. This will take a long time and will result in a time-out error while executing such a command.

Therefore I suggest:

  1. return to the original version of my commit with autostart of the task when cobbler starts, which deserializes items during idle time.
  2. check whether the deserialization task has completed when executing commands that require the presence of all collection items in memory. If not all items of the desired collection have not yet been deserialized, then return a message like this "Please be patient, cobbler is not yet ready to execute this command. Retry the command in approximately NNN hours/min/sec." We can estimate the readiness time by the number of items already deserialized by this task.

@SchoolGuy
Copy link
Member Author

@tpw56j Thanks a lot :)

@tpw56j
Copy link
Contributor

tpw56j commented Mar 9, 2024

I tested on my laptop the effect of lazy_start on the time of deserialization of collections during cobbler startup.
Most of the time is spent parsing json regardless of the database type used. Replacing python-json with python-orjson or python-ujson did not improve performance. I will prepare a PR for the main branch on bugs regarding lazy_start found during testing.

In the current implementation, lazy_start only defers the problem to a later time. After starting with lazy_start=True, I was unable to create the 1001st system due to a timeout during the long execution of deserializing all items of the collection in collection.find(). Therefore, I will try to prepare a PR to solve it.

Here are my results for 1000 target systems.

Timing of serializers.file deserialize() based on the results of 3 measurements

lazy_start json orjson ujson
False 69.91 - 89.20 70.52 - 72.97 73.21 - 74.13
True 0.0629 - 0.0684

serializers.sqlite

lazy_start json
False 65.55 - 81.53
True 0.0585 - 0.06425

serializers.mongodb

lazy_start json
False 68.82 - 72.24
True 0.0602 - 0.0648

@tpw56j tpw56j mentioned this pull request Mar 9, 2024
12 tasks
@SchoolGuy SchoolGuy force-pushed the backport/lazy-collection-init branch from 419fb3a to f420775 Compare April 2, 2024 11:54
@SchoolGuy
Copy link
Member Author

@tpw56j After being haunted by my infrastructure at work I was finally able to apply your two-line fix to my PR. In terms of functionality do you believe that this PR is finished?

The rest of my performance concerns I would like to discuss in the issue we have for this (#3341).

@SchoolGuy SchoolGuy mentioned this pull request Apr 2, 2024
@tpw56j
Copy link
Contributor

tpw56j commented Apr 2, 2024

@SchoolGuy I think this fix is not enough to solve the problem. To fix the bug in the main branch #3654, I had to additionally monitor the deserialization state of the entire collection to avoid recursion. The problem arises if, when deserializing an item, it is necessary to check the attributes of this item for validity among other items of the same or another collection. For example, check for duplicate mac addresses. In this case, collection.find() should act the same as when loading a collection when the cobbler starts, without a lazy start. Namely, check attributes only among already loaded items and do not try to read remaining items from disk because this collection is already being read in its entirety. And to implement this behaviour I had to add code to the collection.py.
To avoid recursion, we need to include this code here too and carefully check that it completely solves the problem.

@SchoolGuy
Copy link
Member Author

@tpw56j Thanks for your take on this. I will cherry pick the required changes from your PR tomorrow.

@SchoolGuy
Copy link
Member Author

This PR isn't actually closed. We still want the backport.

@SchoolGuy SchoolGuy mentioned this pull request Apr 16, 2024
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport Backported changes and features.
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[Backport] Lazy collection init
2 participants