)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"65be6194d6540553cd4001f22fab2798b74346c7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"a992414d_1cd6e1bc","updated":"2026-05-12 20:01:01.000000000","message":"WIP - still getting more results from simulations","commit_id":"9dee4bd877122a32588a1dc380061dbe802e1820"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"fb589dc66fcbcf6fe2bc87a6c9d31b34e8b1ef71","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"6b0897b4_f892b040","updated":"2026-05-29 12:49:33.000000000","message":"Needs rebase on other fixes","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"232e6419_161d651c","updated":"2026-06-09 16:52:12.000000000","message":"This change should help to find better migration candidates in the simulate_migrations by pre-selecting destination candidates in a more smart way that pure random. This should have the effect of getting better actionplans and reducing SDs with less migrations.\n\nSaid that, we still carry the pre-existing overall implementation which is suboptimal (see my inline comments).\n\nMy tests of this (together with https://review.opendev.org/c/openstack/watcher/+/988533 and the locking improvements) shown that results are good and execution time reduces almost linearly by decreasing source_nodes_percent, what may be good enough for our requirements so far.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"}],"watcher/decision_engine/strategy/strategies/workload_stabilization.py":[{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"d2b071a3948de1532ef372256e62eab3bb6fb699","unresolved":true,"context_lines":[{"line_number":190,"context_line":"                    \"candidates (count set by least_loaded_count).\","},{"line_number":191,"context_line":"                    \"type\": \"string\","},{"line_number":192,"context_line":"                    \"enum\": [\"cycle\", \"retry\", \"fullsearch\", \"least_loaded\"],"},{"line_number":193,"context_line":"                    \"default\": \"retry\","},{"line_number":194,"context_line":"                },"},{"line_number":195,"context_line":"                \"least_loaded_count\": {"},{"line_number":196,"context_line":"                    \"description\": \"Number of least loaded destination hosts \""}],"source_content_type":"text/x-python","patch_set":4,"id":"05ce2cb4_91f618bf","line":193,"updated":"2026-06-10 10:15:38.000000000","message":"As follow up, in a different review, I would consider moving the default to least_loaded as it should provide better results.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":true,"context_lines":[{"line_number":201,"context_line":"                    \"minimum\": 1,"},{"line_number":202,"context_line":"                    \"default\": 1,"},{"line_number":203,"context_line":"                },"},{"line_number":204,"context_line":"                \"least_loaded_percent\": {"},{"line_number":205,"context_line":"                    \"description\": \"Percentage of the lightest hosts (by \""},{"line_number":206,"context_line":"                    \"weighted load) to form the destination candidate pool \""},{"line_number":207,"context_line":"                    \"when host_choice is \u0027least_loaded\u0027. least_loaded_count \""},{"line_number":208,"context_line":"                    \"hosts are then randomly sampled from this pool. A value \""},{"line_number":209,"context_line":"                    \"of 50 means only the lightest half of available nodes \""},{"line_number":210,"context_line":"                    \"are eligible as destinations.\","},{"line_number":211,"context_line":"                    \"type\": \"number\","},{"line_number":212,"context_line":"                    \"minimum\": 1,"},{"line_number":213,"context_line":"                    \"maximum\": 100,"},{"line_number":214,"context_line":"                    \"default\": 50,"},{"line_number":215,"context_line":"                },"},{"line_number":216,"context_line":"                \"retry_count\": {"},{"line_number":217,"context_line":"                    \"description\": \"Count of random returned hosts\","},{"line_number":218,"context_line":"                    \"type\": \"number\","}],"source_content_type":"text/x-python","patch_set":4,"id":"d76f265f_fc3aba81","line":215,"range":{"start_line":204,"start_character":0,"end_line":215,"end_character":18},"updated":"2026-06-09 16:52:12.000000000","message":"What if the default behavior is to include in the least_loaded group all that are below the mean? That may adjust better that a specified percentage.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":16312,"name":"Alfredo Moralejo","email":"amoralej@redhat.com","username":"amoralej"},"change_message_id":"3a66e735e52e55fcc478841060576dd52e8dc97c","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"7d4aa4dd_3c943c06","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"updated":"2026-06-09 16:52:12.000000000","message":"This comment somehow affect this and https://review.opendev.org/c/openstack/watcher/+/988533/, which i\u0027ve tested together.\n\nWith the current implementation, i think this is probably the best we can do. The existing behavior of the strategy is:\n\n1. If std. dev is \u003e threshold, this method simulates migrations for all the VMs in all the nodes and stores the std dev after migraion as an individual migration, it\u0027s like calculating \"migration candidates\". When calculating following VM migration, it does not consider any previous one.\n\n2. do_execute take all the candidates by order, if it reduces SD, it adds a migration action, updates the model, updates the metrics dict and then iterates over the next one.\n\n3. When the resulting sd is below threshold, it exits as it reach the goal.\n\nI find this suboptimal as 1 is very expensive and many of the migration candidates created on it will not be used at all. Reducing the source nodes in simulate_migrations as done in https://review.opendev.org/c/openstack/watcher/+/988533 is a good step and improves the behavior, but I think merging 1 and 2 in a single step may would provide the best performance:\n\n1. If std. dev is \u003e threshold, the strategy get the node with highest utilization and instances on it (order tbd). For each one, it tries to migrate to the lowest used node (no random but deterministically). If it fits (otherwise, check next host) and std dev is reduced, the migration is added and the host_load saved as based for the next migration.  Then, the following instance in the same host is tried until the first case where sd is not decreasing or usage is \u003c threshold.\n\n2. Keep iterating on instances from the most_used to the least_used.\n\n3. When sd is \u003c threshold or all the vms in all the hosts with utilization \u003e mean are tried, exit.\n\nI\u0027m this would be more optimal.\n\nSaid that, this change together with https://review.opendev.org/c/openstack/watcher/+/988533 and the locking changes provides probably a good enough scalability and results. In my tests, 113 hosts and 15000 VMs and a well balanced cluster (it had to iterate over all the vms), it took 9 mins with default config. Setting source_nodes_percent to 50 using #988533, it reduced to ~ 5 minutes.","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":34452,"name":"Joan Gilabert","display_name":"jgilaber","email":"jgilaber@redhat.com","username":"jgilaber"},"change_message_id":"cb4fac3ec83a39c95e56fdeed0ec0580c4b2d0d3","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"bb1de103_97956279","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"in_reply_to":"48386d87_f897049b","updated":"2026-07-27 11:09:34.000000000","message":"I think I agree with Doug\u0027s comment, for changes more profound that what is currently proposed, I feel like introducing a new strategy, with a clear spec is a better use of our efforts and time","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"ff35192a5c4aa017594190e7bca5fa4ddf39b338","unresolved":true,"context_lines":[{"line_number":497,"context_line":"        # Pre-compute once: the lightest least_loaded_percent% of hosts."},{"line_number":498,"context_line":"        # yield_nodes randomly samples least_loaded_count hosts from this pool"},{"line_number":499,"context_line":"        # per source node instead of recomputing the ranking every iteration."},{"line_number":500,"context_line":"        if self.host_choice \u003d\u003d \u0027least_loaded\u0027:"},{"line_number":501,"context_line":"            all_ranked \u003d self.get_least_loaded_hosts(hosts)"},{"line_number":502,"context_line":"            pool_size \u003d max("},{"line_number":503,"context_line":"                1, math.ceil(len(all_ranked) * self.least_loaded_percent / 100)"},{"line_number":504,"context_line":"            )"},{"line_number":505,"context_line":"            _least_loaded_cache \u003d [h for h, _ in all_ranked[:pool_size]]"},{"line_number":506,"context_line":"        else:"},{"line_number":507,"context_line":"            _least_loaded_cache \u003d []"},{"line_number":508,"context_line":""},{"line_number":509,"context_line":"        def yield_nodes(nodes):"},{"line_number":510,"context_line":"            if self.host_choice \u003d\u003d \u0027cycle\u0027:"}],"source_content_type":"text/x-python","patch_set":4,"id":"48386d87_f897049b","line":507,"range":{"start_line":500,"start_character":0,"end_line":507,"end_character":36},"in_reply_to":"7d4aa4dd_3c943c06","updated":"2026-06-11 11:36:55.000000000","message":"Yes, I had the same feeling while working in these improvements. A refactoring in the code can make this more efficient for sure, but it would also change how the strategy was originally implemented, so I am not sure if we should really go far on that. Both changes proposed here are reusing the original algorithym and trying optimize somehow by adding extra funcionality. We should discuss more about that with the team. \nThank you for taking a time and testing both changes!","commit_id":"9094aac6bb45992885e508d92de4349baf7c4023"}]}
