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

fix: Prioritize load higher than region. #1206

Merged
merged 2 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,75 +16,14 @@
package org.jitsi.jicofo.bridge

import org.jitsi.utils.logging2.Logger
import org.jitsi.utils.logging2.LoggerImpl
import org.json.simple.JSONObject
import org.jitsi.utils.logging2.createLogger
import org.jitsi.jicofo.bridge.BridgeConfig.Companion.config as config

/**
* Represents an algorithm for bridge selection.
*/
abstract class BridgeSelectionStrategy {
/**
* Total number of times selection succeeded because there was a bridge
* already in the conference, in the desired region that was not
* overloaded.
*/
private var totalNotLoadedAlreadyInConferenceInRegion = 0

/**
* Total number of times selection succeeded because there was a bridge
* already in the conference, in the desired region group that was not
* overloaded.
*/
private var totalNotLoadedAlreadyInConferenceInRegionGroup = 0

/**
* Total number of times selection succeeded because there was a bridge
* in the desired region that was not overloaded.
*/
private var totalNotLoadedInRegion = 0

/**
* Total number of times selection succeeded because there was a bridge
* in the desired region group that was not overloaded.
*/
private var totalNotLoadedInRegionGroup = 0

/**
* Total number of times selection succeeded because there was a bridge
* already in the conference, in the desired region.
*/
private var totalLeastLoadedAlreadyInConferenceInRegion = 0

/**
* Total number of times selection succeeded because there was a bridge
* already in the conference, in the desired region group.
*/
private var totalLeastLoadedAlreadyInConferenceInRegionGroup = 0

/**
* Total number of times selection succeeded because there was a bridge
* in the desired region.
*/
private var totalLeastLoadedInRegion = 0

/**
* Total number of times selection succeeded because there was a bridge
* in the desired region group.
*/
private var totalLeastLoadedInRegionGroup = 0

/**
* Total number of times selection succeeded because there was a bridge
* already in the conference.
*/
private var totalLeastLoadedAlreadyInConference = 0

/**
* Total number of times selection succeeded because there was any bridge
* available.
*/
private var totalLeastLoaded = 0
private val logger: Logger = createLogger()

/**
* Selects a bridge to be used for a new participant in a conference.
Expand Down Expand Up @@ -155,11 +94,10 @@ abstract class BridgeSelectionStrategy {
desiredRegion: String?
): Bridge? {
val result = bridges
.filterNot { isOverloaded(it, conferenceBridges) }
.filterNot { it.isOverloaded(conferenceBridges) }
.intersect(conferenceBridges.keys)
.firstOrNull { desiredRegion != null && it.region.equals(desiredRegion) }
if (result != null) {
totalNotLoadedAlreadyInConferenceInRegion++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
Expand All @@ -173,11 +111,10 @@ abstract class BridgeSelectionStrategy {
): Bridge? {
val regionGroup = config.getRegionGroup(desiredRegion)
val result = bridges
.filterNot { isOverloaded(it, conferenceBridges) }
.filterNot { it.isOverloaded(conferenceBridges) }
.intersect(conferenceBridges.keys)
.firstOrNull { regionGroup.contains(it.region) }
if (result != null) {
totalNotLoadedAlreadyInConferenceInRegionGroup++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
Expand Down Expand Up @@ -214,10 +151,9 @@ abstract class BridgeSelectionStrategy {
desiredRegion: String?
): Bridge? {
val result = bridges
.filterNot { isOverloaded(it, conferenceBridges) }
.filterNot { it.isOverloaded(conferenceBridges) }
.firstOrNull { desiredRegion != null && it.region.equals(desiredRegion) }
if (result != null) {
totalNotLoadedInRegion++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
Expand All @@ -231,15 +167,26 @@ abstract class BridgeSelectionStrategy {
): Bridge? {
val regionGroup = config.getRegionGroup(desiredRegion)
val result = bridges
.filterNot { isOverloaded(it, conferenceBridges) }
.filterNot { it.isOverloaded(conferenceBridges) }
.firstOrNull { regionGroup.contains(it.region) }
if (result != null) {
totalNotLoadedInRegionGroup++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
}

fun notLoaded(
bridges: List<Bridge>,
conferenceBridges: Map<Bridge, ConferenceBridgeProperties>,
participantProperties: ParticipantProperties
): Bridge? {
val result = bridges.firstOrNull { !it.isOverloaded }
if (result != null) {
logSelection(result, conferenceBridges, participantProperties)
}
return result
}

/**
* Finds the least loaded conference bridge in the desired region that
* is already handling the conference.
Expand All @@ -262,25 +209,21 @@ abstract class BridgeSelectionStrategy {
.intersect(conferenceBridges.keys)
.firstOrNull { desiredRegion != null && it.region.equals(desiredRegion) }
if (result != null) {
totalLeastLoadedAlreadyInConferenceInRegion++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
}

fun leastLoadedAlreadyInConferenceInRegionGroup(
fun leastLoadedNotMaxedAlreadyInConference(
bridges: List<Bridge>,
conferenceBridges: Map<Bridge, ConferenceBridgeProperties>,
participantProperties: ParticipantProperties,
desiredRegion: String?
participantProperties: ParticipantProperties
): Bridge? {
val regionGroup = config.getRegionGroup(desiredRegion)
val result = bridges
.intersect(conferenceBridges.keys)
.firstOrNull { regionGroup.contains(it.region) }
.firstOrNull { !it.hasMaxParticipantsInConference(conferenceBridges) }
if (result != null) {
totalLeastLoadedAlreadyInConferenceInRegionGroup++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
logSelection(result, conferenceBridges, participantProperties)
}
return result
}
Expand All @@ -303,23 +246,6 @@ abstract class BridgeSelectionStrategy {
val result = bridges
.firstOrNull { desiredRegion != null && it.region.equals(desiredRegion) }
if (result != null) {
totalLeastLoadedInRegion++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
}

fun leastLoadedInRegionGroup(
bridges: List<Bridge>,
conferenceBridges: Map<Bridge, ConferenceBridgeProperties>,
participantProperties: ParticipantProperties,
desiredRegion: String?
): Bridge? {
val regionGroup = config.getRegionGroup(desiredRegion)
val result = bridges
.firstOrNull { regionGroup.contains(it.region) }
if (result != null) {
totalLeastLoadedInRegionGroup++
logSelection(result, conferenceBridges, participantProperties, desiredRegion)
}
return result
Expand All @@ -334,17 +260,16 @@ abstract class BridgeSelectionStrategy {
*
* @return the least loaded bridge that is already in the conference, if it exists, or null
*/
fun nonLoadedAlreadyInConference(
fun notLoadedAlreadyInConference(
bridges: List<Bridge>,
conferenceBridges: Map<Bridge, ConferenceBridgeProperties>,
participantProperties: ParticipantProperties
): Bridge? {
val result = bridges
.filterNot { isOverloaded(it, conferenceBridges) }
.filterNot { it.isOverloaded(conferenceBridges) }
.intersect(conferenceBridges.keys)
.firstOrNull()
if (result != null) {
totalLeastLoadedAlreadyInConference++
logSelection(result, conferenceBridges, participantProperties)
}
return result
Expand All @@ -364,7 +289,6 @@ abstract class BridgeSelectionStrategy {
): Bridge? {
val result = bridges.firstOrNull()
if (result != null) {
totalLeastLoaded++
logSelection(result, conferenceBridges, participantProperties)
}
return result
Expand Down Expand Up @@ -394,36 +318,15 @@ abstract class BridgeSelectionStrategy {
* @param conferenceBridges the bridges in the conference
* @return `true` if the bridge should be considered overloaded.
*/
private fun isOverloaded(bridge: Bridge, conferenceBridges: Map<Bridge, ConferenceBridgeProperties>): Boolean {
return bridge.isOverloaded || (
config.maxBridgeParticipants > 0 &&
conferenceBridges.containsKey(bridge) &&
conferenceBridges[bridge]!!.participantCount >= config.maxBridgeParticipants
)
private fun Bridge.isOverloaded(conferenceBridges: Map<Bridge, ConferenceBridgeProperties>): Boolean {
return isOverloaded || hasMaxParticipantsInConference(conferenceBridges)
}

val stats: JSONObject
get() {
val json = JSONObject()
json["total_not_loaded_in_region_in_conference"] = totalNotLoadedAlreadyInConferenceInRegion
json["total_not_loaded_in_region_group_in_conference"] = totalNotLoadedAlreadyInConferenceInRegionGroup
json["total_not_loaded_in_region"] = totalNotLoadedInRegion
json["total_not_loaded_in_region_group"] = totalNotLoadedInRegionGroup
json["total_least_loaded_in_region_in_conference"] = totalLeastLoadedAlreadyInConferenceInRegion
json["total_least_loaded_in_region_group_in_conference"] = totalLeastLoadedAlreadyInConferenceInRegionGroup
json["total_least_loaded_in_region"] = totalLeastLoadedInRegion
json["total_least_loaded_in_region_group"] = totalLeastLoadedInRegionGroup
json["total_least_loaded_in_conference"] = totalLeastLoadedAlreadyInConference
json["total_least_loaded"] = totalLeastLoaded
return json
}

companion object {
/**
* The logger.
*/
private val logger: Logger = LoggerImpl(
BridgeSelectionStrategy::class.java.name
)
private fun Bridge.hasMaxParticipantsInConference(
conferenceBridges: Map<Bridge, ConferenceBridgeProperties>
): Boolean {
return config.maxBridgeParticipants > 0 &&
conferenceBridges.containsKey(this) &&
conferenceBridges[this]!!.participantCount >= config.maxBridgeParticipants
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ class BridgeSelector @JvmOverloads constructor(

val stats: JSONObject
@Synchronized
get() = bridgeSelectionStrategy.stats.apply {
get() = JSONObject().apply {
// We want to avoid exposing unnecessary hierarchy levels in the stats,
// so we'll merge stats from different "child" objects here.
this["bridge_count"] = bridgeCount.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,9 @@ class RegionBasedBridgeSelectionStrategy : BridgeSelectionStrategy() {
?: notLoadedAlreadyInConferenceInRegionGroup(bridges, conferenceBridges, participantProperties, region)
?: notLoadedInRegion(bridges, conferenceBridges, participantProperties, region)
?: notLoadedInRegionGroup(bridges, conferenceBridges, participantProperties, region)
?: leastLoadedAlreadyInConferenceInRegion(bridges, conferenceBridges, participantProperties, region)
?: leastLoadedAlreadyInConferenceInRegionGroup(bridges, conferenceBridges, participantProperties, region)
?: leastLoadedInRegion(bridges, conferenceBridges, participantProperties, region)
?: leastLoadedInRegionGroup(bridges, conferenceBridges, participantProperties, region)
?: nonLoadedAlreadyInConference(bridges, conferenceBridges, participantProperties)
?: notLoadedAlreadyInConference(bridges, conferenceBridges, participantProperties)
?: notLoaded(bridges, conferenceBridges, participantProperties)
?: leastLoadedNotMaxedAlreadyInConference(bridges, conferenceBridges, participantProperties)
?: leastLoaded(bridges, conferenceBridges, participantProperties)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ class BridgeSelectionStrategyTest : ShouldSpec() {
val propsNull = ParticipantProperties(null)

val conferenceBridges = mutableMapOf<Bridge, ConferenceBridgeProperties>()
// Initial selection should select a bridge in the participant's region.
strategy.select(allBridges, conferenceBridges, highStressProps, true) shouldBe highStressBridge
// Initial selection should select a non-overloaded bridge in the participant's region if possible. If not,
// it should select the lowest loaded bridge.
strategy.select(allBridges, conferenceBridges, highStressProps, true) shouldBe lowStressBridge
strategy.select(allBridges, conferenceBridges, mediumStressProps, true) shouldBe mediumStressBridge
strategy.select(allBridges, conferenceBridges, propsInvalid, true) shouldBe lowStressBridge

Expand Down Expand Up @@ -135,8 +136,9 @@ class BridgeSelectionStrategyTest : ShouldSpec() {

val conferenceBridges = mutableMapOf<Bridge, ConferenceBridgeProperties>()

// Initial selection should select a bridge in the participant's region.
strategy.select(allBridges, conferenceBridges, highStressProps, true) shouldBe highStressBridge
// Initial selection should select a non-overloaded bridge in the participant's region if possible. If not,
// it should select the lowest loaded bridge.
strategy.select(allBridges, conferenceBridges, highStressProps, true) shouldBe mediumStressBridge1
strategy.select(allBridges, conferenceBridges, mediumStressProps2, true) shouldBe mediumStressBridge2
strategy.select(allBridges, conferenceBridges, propsInvalid, true) shouldBe mediumStressBridge1
strategy.select(allBridges, conferenceBridges, propsNull, true) shouldBe mediumStressBridge1
Expand Down
Loading