Skip to content

Commit 0df1d4c

Browse files
committed
- Use own interval sync
- Remove interval endpoint
1 parent 0d0236f commit 0df1d4c

File tree

12 files changed

+53
-151
lines changed

12 files changed

+53
-151
lines changed

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ The `/v2` and `/v3` endpoints are more complex. Turso Edge POP will run all `exe
2828

2929
The app also exposes a `/health` and `/version` informational endpoints.
3030

31-
Lastly, you can use `/sync` endpoint to manually trigger a sync of the _local database_ with the origin database. Please note that this will sync local POP only (ie won't send pubsub message to other POPs).
32-
3331
Turso Edge POP supports HTTP requests only (no websocket support etc), hence you need to use `http`/`https` url when connecting to POP.
3432

3533
When you point your client to the POP, it will either respond with local data or seamlessly proxy your request to the origin server. This process is automatic and does not require any special setup from the client. On successful origin response, POP will also process a `sync` update to fetch a fresh copy if any writes were made.

bun.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"ioredis": "^5.4.2",
1313
"node-sql-parser": "^5.3.6",
1414
"pino": "^9.6.0",
15+
"set-interval-async": "^3.0.3",
1516
"zod": "^3.24.1",
1617
},
1718
"devDependencies": {
@@ -150,6 +151,8 @@
150151

151152
"safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],
152153

154+
"set-interval-async": ["[email protected]", "", {}, "sha512-o4DyBv6mko+A9cH3QKek4SAAT5UyJRkfdTi6JHii6ZCKUYFun8SwgBmQrOXd158JOwBQzA+BnO8BvT64xuCaSw=="],
155+
153156
"sonic-boom": ["[email protected]", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww=="],
154157

155158
"split2": ["[email protected]", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"ioredis": "^5.4.2",
2323
"node-sql-parser": "^5.3.6",
2424
"pino": "^9.6.0",
25+
"set-interval-async": "^3.0.3",
2526
"zod": "^3.24.1"
2627
}
2728
}

src/helpers/env.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const env = createEnv({
77
// Turso
88
TURSO_DATABASE_URL: z.string().url(),
99
TURSO_AUTH_TOKEN: z.string().startsWith("ey"),
10-
TURSO_SYNC_INTERVAL: z.coerce.number().default(60),
10+
TURSO_SYNC_INTERVAL: z.coerce.number().min(3).default(60),
1111

1212
// Local SQL database file
1313
DB_FILEPATH: z.string().default("/app/data/local.db"),

src/helpers/redis.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Redis from "ioredis";
22
import { env } from "./env";
3-
import { tursoClient } from "./turso-client";
3+
import { tursoClientSync } from "./turso-client";
44

55
// why 2 instances?
66
// - _redisPub_ is used for publishing sync events
@@ -23,6 +23,6 @@ export const publishRedisSyncCommand = async () => {
2323
if (redisPub) {
2424
redisPub.publish(env.REDIS_SYNC_CHANNEL, "sync");
2525
} else {
26-
await tursoClient.sync();
26+
await tursoClientSync("api");
2727
}
2828
};

src/helpers/truso-proxy.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { ContentfulStatusCode } from "hono/utils/http-status";
22
import { env } from "./env";
3-
import { tursoClient } from "./turso-client";
43

54
export const tursoProxy = async (requests: string) => {
65
const response = await fetch(`${env.TURSO_DATABASE_URL}/v3/pipeline`, {
@@ -14,11 +13,6 @@ export const tursoProxy = async (requests: string) => {
1413

1514
const status = response.status as ContentfulStatusCode;
1615

17-
// Sync to local database
18-
if (status === 200) {
19-
await tursoClient.sync();
20-
}
21-
2216
return {
2317
status,
2418
result: status === 200 ? await response.json() : await response.text(),

src/helpers/turso-client.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,33 @@
11
import { createClient } from "@libsql/client";
22
import { env } from "./env";
3+
import { logger } from "./logger";
34

5+
let isSyncing = false;
6+
7+
const setIsSyncing = (value = true) => {
8+
isSyncing = value;
9+
};
10+
11+
// we are not using "built in" syncInterval, as this can corrupt the database if run in parallel to manual syncs
12+
// due to this fact, interval syncing is done manually, check src/jobs/interval-sync.ts
413
export const tursoClient = createClient({
514
url: `file:${env.DB_FILEPATH}`,
615
syncUrl: env.TURSO_DATABASE_URL,
716
authToken: env.TURSO_AUTH_TOKEN,
8-
syncInterval: env.TURSO_SYNC_INTERVAL,
917
encryptionKey: env.DB_ENCRYPTION_KEY,
1018
fetch, // use Bun native fetch
1119
});
20+
21+
export const tursoClientSync = async (type: "interval" | "pubsub" | "api") => {
22+
const typeUpperFirst = type.charAt(0).toUpperCase() + type.slice(1);
23+
if (!isSyncing) {
24+
setIsSyncing(true);
25+
await tursoClient.sync();
26+
logger.info(`${typeUpperFirst} Sync triggered`);
27+
setIsSyncing(false);
28+
} else {
29+
logger.info(
30+
`${typeUpperFirst} Sync skipped, parallel sync already running`,
31+
);
32+
}
33+
};

src/index.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import type { Env as HonoPinoEnv } from "hono-pino";
33
import { cors } from "hono/cors";
44
import { appVersion, region } from "./helpers/app-vars";
55
import { env } from "./helpers/env";
6-
import { redisSub } from "./helpers/redis";
7-
import { syncJob } from "./jobs/sync";
6+
import { intervalSyncJob } from "./jobs/interval-sync";
7+
import { syncJobPubsub } from "./jobs/pubsub-sync";
88
import { appendLoggerInfo, registerLogger } from "./middlewares/logger";
99
import healthRoute from "./routes/health";
10-
import syncRoute from "./routes/sync";
1110
import queryRoute from "./routes/v0-query";
1211
import pipelineRoute from "./routes/v2-v3-pipeline";
1312
import versionRoute from "./routes/version";
@@ -17,8 +16,13 @@ const app = new Hono<HonoPinoEnv>()
1716
.use(appendLoggerInfo)
1817
.use(cors());
1918

20-
syncJob();
19+
// listen for pubsub sync
20+
syncJobPubsub();
2121

22+
// run interval sync
23+
intervalSyncJob();
24+
25+
// routes
2226
app.route("/", queryRoute);
2327

2428
app.get("/v2", (c) => c.body(null, 200));
@@ -28,15 +32,14 @@ app.route("/v3/pipeline", pipelineRoute);
2832

2933
app.route("/version", versionRoute);
3034
app.route("/health", healthRoute);
31-
app.route("/sync", syncRoute);
3235

3336
if (!env.QUIET || process.env.NODE_ENV !== "production") {
3437
console.log(`🚀 Turso Edge Pop server running on port ${env.PORT}`);
3538
console.log(`📦 Version: ${appVersion}`);
3639
console.log(`🌎 Region: ${region}`);
3740
console.log(`💾 Database path: ${env.DB_FILEPATH}`);
3841
console.log(`⏱️ Sync Internal: ${env.TURSO_SYNC_INTERVAL} seconds`);
39-
console.log(`🔄 Redis sync: ${Boolean(redisSub)}`);
42+
console.log(`🔄 Redis sync: ${Boolean(env.REDIS_CONNECTION_STRING)}`);
4043
console.log(`------------------------------------------------------------------
4144
`);
4245
}

src/jobs/interval-sync.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { setIntervalAsync } from "set-interval-async";
2+
import { env } from "../helpers/env";
3+
import { tursoClientSync } from "../helpers/turso-client";
4+
5+
export const intervalSyncJob = async () => {
6+
await tursoClientSync("interval");
7+
8+
setIntervalAsync(async () => {
9+
await tursoClientSync("interval");
10+
}, env.TURSO_SYNC_INTERVAL * 1000);
11+
};

src/jobs/sync.ts renamed to src/jobs/pubsub-sync.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import debounce from "debounce";
22
import { env } from "../helpers/env";
33
import { logger } from "../helpers/logger";
44
import { redisSub } from "../helpers/redis";
5-
import { tursoClient } from "../helpers/turso-client";
5+
import { tursoClientSync } from "../helpers/turso-client";
66

77
export let lastSyncTimestamp = 0;
88

9-
export const syncJob = () => {
9+
export const syncJobPubsub = () => {
1010
if (redisSub) {
1111
// subscribe
1212
redisSub.subscribe(env.REDIS_SYNC_CHANNEL, (err, count) => {
@@ -32,7 +32,7 @@ export const syncJob = () => {
3232
"Received 'sync' message on channel %s. Syncing database.",
3333
channel,
3434
);
35-
await tursoClient.sync();
35+
await tursoClientSync("pubsub");
3636
lastSyncTimestamp = Math.floor(Date.now() / 1000);
3737
}, env.REDIS_SYNC_DEBOUNCE * 1000);
3838
}

0 commit comments

Comments
 (0)