Skip to content

Commit

Permalink
Save board snapshot every 5000 events to avoid excessively long board…
Browse files Browse the repository at this point in the history
… load time due to snapshot being late
  • Loading branch information
raimohanska committed Jan 28, 2024
1 parent 10926ce commit f2c67b9
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 8 deletions.
30 changes: 26 additions & 4 deletions backend/src/board-state.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { boardReducer } from "../../common/src/board-reducer"
import { Board, BoardCursorPositions, BoardHistoryEntry, Id, ItemLocks, Serial } from "../../common/src/domain"
import { Locks } from "./locker"
import { createAccessToken, createBoard, fetchBoard, saveRecentEvents } from "./board-store"
import {
createAccessToken,
createBoard,
fetchBoard,
mkSnapshot,
saveBoardSnapshot,
saveRecentEvents,
} from "./board-store"
import { broadcastItemLocks, getBoardSessionCount, getSessionCount } from "./websocket-sessions"
import { compactBoardHistory, quickCompactBoardHistory } from "./compact-history"
import { sleep } from "../../common/src/sleep"
import { UserSession } from "./websocket-sessions"
import { inTransaction } from "./db"
// A mutable state object for server side state
export type ServerSideBoardState = {
ready: true
snapshotSerial: Serial
board: Board
recentEvents: BoardHistoryEntry[]
storingEvents: BoardHistoryEntry[]
Expand All @@ -35,7 +44,7 @@ export async function getBoard(id: Id): Promise<ServerSideBoardState | null> {
const fetchState = async () => {
const boardData = await fetchBoard(id)
if (!boardData) return null
const { board, accessTokens } = boardData
const { board, accessTokens, snapshotSerial } = boardData
return {
ready: true,
board,
Expand All @@ -46,6 +55,7 @@ export async function getBoard(id: Id): Promise<ServerSideBoardState | null> {
cursorsMoved: false,
cursorPositions: {},
sessions: [],
snapshotSerial,
} as ServerSideBoardState
}
const fetch = fetchState()
Expand Down Expand Up @@ -100,10 +110,10 @@ export function updateBoards(boardState: ServerSideBoardState, appEvent: BoardHi
export async function addBoard(board: Board, createToken?: boolean): Promise<ServerSideBoardState> {
await createBoard(board)
const accessTokens = createToken ? [await createAccessToken(board)] : []
const boardState = {
const boardState: ServerSideBoardState = {
ready: true as const,
board,
serial: 0,
snapshotSerial: 0,
recentEvents: [],
storingEvents: [],
locks: Locks((changedLocks) => broadcastItemLocks(board.id, changedLocks)),
Expand Down Expand Up @@ -145,6 +155,18 @@ async function saveBoardChanges(state: ServerSideBoardState) {
)
try {
await saveRecentEvents(state.board.id, state.storingEvents)
if (state.board.serial - state.snapshotSerial > 5000) {
console.log(
`Saving snapshot for board ${state.board.id} with ${
state.board.serial - state.snapshotSerial
} new events`,
)
await inTransaction(async (client) => {
await saveBoardSnapshot(mkSnapshot(state.board, state.board.serial), client)
})
state.snapshotSerial = state.board.serial
}
// TODO: also update snapshot if more than 1000 events since snapshot
} catch (e) {
// Push event back to the head of save list for retrying later
state.recentEvents = [...state.storingEvents, ...state.recentEvents]
Expand Down
9 changes: 5 additions & 4 deletions backend/src/board-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { Board, BoardAccessPolicy, BoardHistoryEntry, Id, isBoardEmpty, Serial }
import { migrateBoard, migrateEvent, mkBootStrapEvent } from "../../common/src/migration"
import { inTransaction, withDBClient } from "./db"

export type BoardAndAccessTokens = {
export type BoardFetchResult = {
board: Board
accessTokens: string[]
snapshotSerial: Serial
}

export type BoardInfo = {
Expand Down Expand Up @@ -37,7 +38,7 @@ select id,
where id=$1
`

export async function fetchBoard(id: Id): Promise<BoardAndAccessTokens | null> {
export async function fetchBoard(id: Id): Promise<BoardFetchResult | null> {
return await inTransaction(async (client) => {
const started = new Date().getTime()
const result = await client.query(selectBoardQuery, [id])
Expand Down Expand Up @@ -100,7 +101,7 @@ export async function fetchBoard(id: Id): Promise<BoardAndAccessTokens | null> {
await client.query("SELECT token FROM board_api_token WHERE board_id=$1", [id])
).rows.map((row) => row.token)

return { board: { ...board, serial }, accessTokens }
return { board: { ...board, serial }, accessTokens, snapshotSerial: snapshot.serial }
}
})
}
Expand Down Expand Up @@ -180,7 +181,7 @@ export async function createAccessToken(board: Board): Promise<string> {
}

export async function saveRecentEvents(id: Id, recentEvents: BoardHistoryEntry[]) {
await inTransaction(async (client) => storeEventHistoryBundle(id, recentEvents, client))
await inTransaction(async (client) => await storeEventHistoryBundle(id, recentEvents, client))
}

type StreamingBoardEventCallback = (chunk: BoardHistoryEntry[]) => void
Expand Down

0 comments on commit f2c67b9

Please sign in to comment.