Skip to content
This repository has been archived by the owner on Aug 6, 2024. It is now read-only.

Commit

Permalink
feat: split state to chunks to avoid exceeding d1 row size limit
Browse files Browse the repository at this point in the history
  • Loading branch information
Tsuk1ko committed Jul 7, 2024
1 parent 59f8aa7 commit 5485170
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 21 deletions.
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
Expand Down
45 changes: 27 additions & 18 deletions util/state.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,45 @@
interface StateRow {
idx: number
val: string
}

// D1 max row size 1MB, so we split the state into chunks of 900KB and store them in separate rows.
const SPLIT_SIZE = 9e5

export class UptimeFlareStateDb<T> {
private stateExist = false
private stateSize = 0

constructor(private db: D1Database) {}

async get(): Promise<T | null> {
const result = await this.db
.prepare("SELECT * FROM kv WHERE key = 'state'")
.first<{ key: 'state'; value: string }>()
const { results } = await this.db
.prepare('SELECT * FROM state ORDER BY idx ASC')
.all<StateRow>()

this.stateExist = !!result
this.stateSize = results.length

return result ? this.safeJsonParse(result.value) : null
return this.stateSize ? this.safeJsonParse(results.map(({ val }) => val).join('')) : null
}

async set(value: T) {
if (this.stateExist) {
await this.db
.prepare("UPDATE kv SET value = ? WHERE key = 'state'")
.bind(JSON.stringify(value))
.run()
} else {
await this.db
.prepare("INSERT INTO kv (key, value) VALUES ('state', ?)")
.bind(JSON.stringify(value))
.run()
this.stateExist = true
const chunks = []
for (let str = JSON.stringify(value); str.length; str = str.slice(SPLIT_SIZE)) {
chunks.push(str.slice(0, SPLIT_SIZE))
}
for (const [i, chunk] of chunks.entries()) {
if (i < this.stateSize) {
await this.db.prepare('UPDATE state SET val = ? WHERE idx = ?').bind(chunk, i).run()
} else {
await this.db.prepare('INSERT INTO state (idx, val) VALUES (?, ?)').bind(i, chunk).run()
}
}
this.stateSize = chunks.length
}

private safeJsonParse(str: string) {
try {
return JSON.parse(str)
const val = JSON.parse(str)
return val && typeof val === 'object' ? val : null
} catch {
return null
}
Expand Down
4 changes: 2 additions & 2 deletions worker/setup.sql
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
-- DROP TABLE IF EXISTS kv;
CREATE TABLE IF NOT EXISTS kv (key TEXT PRIMARY KEY, value TEXT);
-- DROP TABLE IF EXISTS state;
CREATE TABLE IF NOT EXISTS state (idx TINYINT UNSIGNED PRIMARY KEY, val TEXT);

0 comments on commit 5485170

Please sign in to comment.