Avoid Postgres connection on cached state reads (#8719)

This commit is contained in:
2026-07-19 12:49:07 -07:00
committed by GitHub
parent ccde86cb81
commit c6b73d24be
2 changed files with 54 additions and 12 deletions
@@ -23,6 +23,7 @@ import net.eagle0.shardok.storage.action_result.ActionResult as ShardokActionRes
class PostgresHistory private[service] (
private val gameId: GameId,
initialCount: Int,
initialStateCache: Map[Int, GameState] = Map.empty,
initialShardokResultCounts: Map[ShardokGameId, Int] = Map.empty,
initialShardokGameStates: Map[ShardokGameId, com.google.protobuf.ByteString] = Map.empty,
initialShardokLiveBattleCaches: Map[ShardokGameId, ShardokLiveBattleCache] = Map.empty,
@@ -36,7 +37,7 @@ class PostgresHistory private[service] (
private var cachedCount: Int = initialCount
private val dbLock = new Object
private val actionCache = mutable.Map.empty[Int, ActionResultT]
private val stateCache = mutable.Map.empty[Int, GameState]
private val stateCache = mutable.Map.from(initialStateCache)
// Shardok reconnects need these values while assembling a subscription. Keep them synchronized with accepted
// write-behind updates so reconnect setup never waits for the shared Postgres connection pool.
private val shardokResultCounts = mutable.Map.from(initialShardokResultCounts)
@@ -338,7 +339,7 @@ class PostgresHistory private[service] (
.getOrElse(Vector.empty)
}
override def stateAfter(count: Int): GameState = withDbLock {
override def stateAfter(count: Int): GameState = withLock {
if count < 0 || count > cachedCount then
throw new EagleInternalException(s"stateAfter($count) out of range; count is $cachedCount")
withTutorialState(JfrEvents.postgresStateReplay("postgres-history", count) { event =>
@@ -351,16 +352,18 @@ class PostgresHistory private[service] (
state
}
.getOrElse {
val (startBoundary, startState) =
bestCachedStateAtOrBefore(count).getOrElse(readLatestSnapshotAtOrBefore(count))
event.snapshotBoundary = startBoundary
event.replayActionCount = count - startBoundary
event.snapshotHit = startBoundary == count
if startBoundary == count then startState
else {
val state = replayStateFrom(startBoundary, startState, endExclusiveSeq = count)
stateCache(count) = state
state
withConnection {
val (startBoundary, startState) =
bestCachedStateAtOrBefore(count).getOrElse(readLatestSnapshotAtOrBefore(count))
event.snapshotBoundary = startBoundary
event.replayActionCount = count - startBoundary
event.snapshotHit = startBoundary == count
if startBoundary == count then startState
else {
val state = replayStateFrom(startBoundary, startState, endExclusiveSeq = count)
stateCache(count) = state
state
}
}
}
})
@@ -7,6 +7,10 @@ import java.util.concurrent.atomic.AtomicReference
import scala.jdk.CollectionConverters.*
import com.google.protobuf.ByteString
import net.eagle0.eagle.model.state.{GameType, RoundPhase}
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.game_state.{EagleMapInfo, GameState}
import net.eagle0.eagle.model.state.run_status.RunStatus
import net.eagle0.shardok.api.action_result_view.ActionResultView as ShardokActionResultView
import net.eagle0.shardok.api.command_descriptor.AvailableCommands as ShardokAvailableCommands
import net.eagle0.shardok.storage.action_result.ActionResult as ShardokActionResult
@@ -301,6 +305,41 @@ class PostgresHistorySchemaBatchTest extends AnyFlatSpec with Matchers with Mock
history.shardokPlayerAvailableCommands("battle", 1) shouldBe Some(availableCommand)
}
"PostgresHistory state cache" should "serve an exact hit without opening a database connection" in {
val cachedState = GameState(
gameId = 0x1234L,
currentRoundId = 7,
currentPhase = RoundPhase.PlayerCommands,
currentDate = Date(1, Date.Month.January),
actionResultCount = 1,
provinces = Map.empty,
heroes = Map.empty,
battalions = Map.empty,
destroyedBattalions = Map.empty,
factions = Map.empty,
factionCommandCounts = Map.empty,
killedHeroes = Map.empty,
destroyedFactions = Map.empty,
outstandingBattles = Vector.empty,
battleCounter = 0,
deferredNotifications = Vector.empty,
runStatus = RunStatus.Running,
victor = None,
battalionTypes = Vector.empty,
randomSeed = 42L,
chronicleEntries = Vector.empty,
gameType = GameType.Normal,
eagleMap = EagleMapInfo(0, 0, "", "")
)
val history = new PostgresHistory(
gameId = cachedState.gameId,
initialCount = 1,
initialStateCache = Map(1 -> cachedState)
)
history.stateAfter(1) shouldBe cachedState
}
"PostgresHistory Shardok write-behind" should "publish accepted updates before a blocked database write completes" in {
val writeStarted = new CountDownLatch(1)
val allowWrite = new CountDownLatch(1)