Skip to content

Commit

Permalink
Enhance Country Leaderboard Availability During Refresh (#1099)
Browse files Browse the repository at this point in the history
The initial approach to refreshing the country leaderboard, which involved deleting all entries at once, led to data unavailability and HTTP 502 errors for users during updates. We've revised the process to sequentially delete and rebuild entries for each country and time duration. This adjustment ensures that leaderboard data remains accessible to users while updates for other countries are in progress.
  • Loading branch information
ljdelight committed Jan 16, 2024
1 parent 3882d48 commit 0900af4
Showing 1 changed file with 111 additions and 63 deletions.
174 changes: 111 additions & 63 deletions app/org/maproulette/jobs/SchedulerActor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -486,76 +486,116 @@ class SchedulerActor @Inject() (
val start = System.currentTimeMillis
logger.info(s"Scheduled Task '$action': Starting run")

db.withConnection { implicit c =>
// Clear TABLEs
SQL("DELETE FROM user_leaderboard WHERE country_code IS NOT NULL").executeUpdate()
SQL("DELETE FROM user_top_challenges WHERE country_code IS NOT NULL").executeUpdate()

val countryCodeMap = boundingBoxFinder.boundingBoxforAll()
for ((countryCode, boundingBox) <- countryCodeMap) {
SQL(
LeaderboardHelper.rebuildChallengesLeaderboardSQLCountry(
SchedulerActor.ONE_MONTH,
countryCode,
boundingBox,
config
)
).executeUpdate()
SQL(
LeaderboardHelper.rebuildChallengesLeaderboardSQLCountry(
SchedulerActor.THREE_MONTHS,
countryCode,
boundingBox,
config
)
).executeUpdate()
SQL(
LeaderboardHelper.rebuildChallengesLeaderboardSQLCountry(
SchedulerActor.SIX_MONTHS,
countryCode,
boundingBox,
config
)
).executeUpdate()
SQL(
LeaderboardHelper.rebuildChallengesLeaderboardSQLCountry(
SchedulerActor.TWELVE_MONTHS,
countryCode,
boundingBox,
config
)
).executeUpdate()
def deleteAndUpdateCountryLeaderboardForTimePeriod(
monthDuration: Int,
countryCode: String,
boundingBox: String
): Unit = {
logger.info(
s"Scheduled Task '$action': updating user_leaderboard monthDuration=$monthDuration countryCode=$countryCode"
)
db.withConnection { implicit c =>
// Delete the existing entries for the country and time period
SQL(
LeaderboardHelper.rebuildChallengesLeaderboardSQLCountry(
SchedulerActor.ALL_TIME,
countryCode,
boundingBox,
config
)
).executeUpdate()
s"DELETE FROM user_leaderboard WHERE country_code = {countryCode} AND month_duration = {monthDuration}"
).on(Symbol("countryCode") -> countryCode, Symbol("monthDuration") -> monthDuration)
.executeUpdate()
// Insert the new entries for the country and time period
SQL(
LeaderboardHelper.rebuildTopChallengesSQLCountry(
SchedulerActor.TWELVE_MONTHS,
countryCode,
boundingBox,
config
)
).executeUpdate()
SQL(
LeaderboardHelper.rebuildTopChallengesSQLCountry(
SchedulerActor.ALL_TIME,
countryCode,
boundingBox,
config
)
LeaderboardHelper
.rebuildChallengesLeaderboardSQLCountry(monthDuration, countryCode, boundingBox, config)
).executeUpdate()
}

val totalTime = System.currentTimeMillis - start
logger.info(
s"Scheduled Task '$action': Finished run. Time spent: ${String.format("%1d", totalTime)}ms"
s"Scheduled Task '$action': finished updating user_leaderboard monthDuration=$monthDuration countryCode=$countryCode"
)
}

// TODO(ljdelight): If the loop order is inverted where each country loops over the monthDuration, will this be faster?
// The database may be able to more effectively cache the per-country results vs the time-based outer loop.
SchedulerActor.MONTH_DURATIONS.foreach(monthDuration => {
val countryCodeMap = boundingBoxFinder.boundingBoxforAll()
for ((countryCode, boundingBox) <- countryCodeMap) {
try {
deleteAndUpdateCountryLeaderboardForTimePeriod(monthDuration, countryCode, boundingBox)
} catch {
// If an exception occurs, log it and continue to the next country
case e: Exception =>
logger.error(
s"Scheduled Task '$action': Failed to update user_leaderboard monthDuration=$monthDuration countryCode=$countryCode",
e
)
}
}
})

db.withConnection { implicit c =>
val countryCodeMap = boundingBoxFinder.boundingBoxforAll()
for ((countryCode, boundingBox) <- countryCodeMap) {
try {
logger.info(
s"Scheduled Task '$action': updating user_top_challenges monthDuration=12 countryCode=$countryCode"
)
// Delete the existing entries for the country and time period
SQL(
"DELETE FROM user_top_challenges WHERE country_code = {countryCode} AND month_duration = {monthDuration}"
).on(
Symbol("countryCode") -> countryCode,
Symbol("monthDuration") -> 12
)
.executeUpdate()

SQL(
LeaderboardHelper.rebuildTopChallengesSQLCountry(
SchedulerActor.TWELVE_MONTHS,
countryCode,
boundingBox,
config
)
).executeUpdate()
} catch {
case e: Exception =>
logger.error(
s"Scheduled Task '$action': Error updating user_top_challenges for monthDuration=12 countryCode=$countryCode",
e
)
}

try {
logger.info(
s"Scheduled Task '$action': updating user_top_challenges monthDuration=-1 countryCode=$countryCode"
)
// Delete the existing entries for the country and time period
SQL(
"DELETE FROM user_top_challenges WHERE country_code = {countryCode} AND month_duration = {monthDuration}"
).on(
Symbol("countryCode") -> countryCode,
Symbol("monthDuration") -> -1
)
.executeUpdate()

SQL(
LeaderboardHelper.rebuildTopChallengesSQLCountry(
SchedulerActor.ALL_TIME,
countryCode,
boundingBox,
config
)
).executeUpdate()
} catch {
case e: Exception =>
logger.error(
s"Scheduled Task '$action': Error updating user_top_challenges for monthDuration=12 countryCode=$countryCode",
e
)
}
}
}

val totalTime = System.currentTimeMillis - start
logger.info(
s"Scheduled Task '$action': Finished run. Time spent: ${String.format("%1d", totalTime)}ms"
)
}

def sendImmediateNotificationEmails(action: String) = {
Expand Down Expand Up @@ -846,6 +886,14 @@ object SchedulerActor {
private val TWELVE_MONTHS = 12
private val ALL_TIME = -1

private val MONTH_DURATIONS = List(
SchedulerActor.ONE_MONTH,
SchedulerActor.THREE_MONTHS,
SchedulerActor.SIX_MONTHS,
SchedulerActor.TWELVE_MONTHS,
SchedulerActor.ALL_TIME
).sorted

def props = Props[SchedulerActor]()

case class RunJob(name: String, action: String = "")
Expand Down

0 comments on commit 0900af4

Please sign in to comment.