mirror of
https://github.com/rajnandan1/kener.git
synced 2026-06-22 20:00:44 +00:00
fix(database): clamp pool bounds, guard redis probe, harden error page
Address review feedback on #744: - clamp DATABASE_POOL_MAX to >= 1 and DATABASE_POOL_MIN to <= max so bad env values can not produce a pool that fails every acquire - healthcheck redis probe checks client status before PING so commands are not queued indefinitely while redis is down (maxRetriesPerRequest is null) - probe() clears its timeout timer once the check settles - error.html shows only the status code, not the error message - docs: correct SQLite default to kener.sqlite.db to match knexfile Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+6
-2
@@ -25,9 +25,13 @@ const keepAliveEnabled = process.env.DATABASE_KEEPALIVE !== "false";
|
||||
// ones that go stale and wedge the app until a manual restart
|
||||
// - 15s acquire/create timeouts: fail fast instead of hanging requests for
|
||||
// knex's default 60s during a database outage
|
||||
// Tarn requires max >= 1 and min <= max; clamp so a bad env value can not
|
||||
// produce a pool that fails every acquire
|
||||
const poolMax = Math.max(1, intFromEnv("DATABASE_POOL_MAX", 10));
|
||||
const poolMin = Math.min(intFromEnv("DATABASE_POOL_MIN", 0), poolMax);
|
||||
const pool = {
|
||||
min: intFromEnv("DATABASE_POOL_MIN", 0),
|
||||
max: intFromEnv("DATABASE_POOL_MAX", 10),
|
||||
min: poolMin,
|
||||
max: poolMax,
|
||||
idleTimeoutMillis: intFromEnv("DATABASE_IDLE_TIMEOUT_MS", 30000),
|
||||
createTimeoutMillis: intFromEnv("DATABASE_CREATE_TIMEOUT_MS", 15000),
|
||||
};
|
||||
|
||||
+17
-2
@@ -24,14 +24,19 @@ async function start() {
|
||||
// Caps a health probe at 2s so a wedged dependency can not hang the
|
||||
// endpoint. A probe is healthy unless it throws, times out, or resolves false.
|
||||
const probe = async (check: () => Promise<unknown>): Promise<boolean> => {
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
try {
|
||||
const result = await Promise.race([
|
||||
check(),
|
||||
new Promise((_, reject) => setTimeout(() => reject(new Error("health probe timeout")), 2000)),
|
||||
new Promise((_, reject) => {
|
||||
timer = setTimeout(() => reject(new Error("health probe timeout")), 2000);
|
||||
}),
|
||||
]);
|
||||
return result !== false;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,7 +44,17 @@ async function start() {
|
||||
// not bounce the app while a dependency is down (a restart can not fix a
|
||||
// dead database); pass ?strict=1 to get 503 when any component is down.
|
||||
app.get(base + "/healthcheck", async (req: any, res: any) => {
|
||||
const [dbOk, redisOk] = await Promise.all([probe(() => dbInstance.ping()), probe(() => redisConnection().ping())]);
|
||||
const [dbOk, redisOk] = await Promise.all([
|
||||
probe(() => dbInstance.ping()),
|
||||
// Guard on status before PING: the shared ioredis client has
|
||||
// maxRetriesPerRequest null, so commands sent while disconnected would
|
||||
// queue forever and accumulate across healthcheck polls
|
||||
probe(async () => {
|
||||
const redis = redisConnection();
|
||||
if (redis.status !== "ready") return false;
|
||||
return await redis.ping();
|
||||
}),
|
||||
]);
|
||||
const healthy = dbOk && redisOk;
|
||||
const strict = req.query.strict === "1";
|
||||
res.status(strict && !healthy ? 503 : 200).json({
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@
|
||||
<h1>This status page is temporarily unavailable</h1>
|
||||
<p>We are having trouble serving this page right now. It usually resolves on its own.</p>
|
||||
<p>This page will retry automatically in 30 seconds.</p>
|
||||
<div class="code">%sveltekit.status% · %sveltekit.error.message%</div>
|
||||
<div class="code">%sveltekit.status%</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -238,15 +238,15 @@ SMTP_SECURE=1
|
||||
|
||||
### Database Configuration {#database-configuration}
|
||||
|
||||
| Variable | Description | Default |
|
||||
| :---------------------------- | :----------------------------------------------------------- | :----------------------------- |
|
||||
| `DATABASE_URL` | Full database connection string | `sqlite://./database/kener.db` |
|
||||
| `DATABASE_POOL_MIN` | Minimum pool connections (PostgreSQL/MySQL) | `0` |
|
||||
| `DATABASE_POOL_MAX` | Maximum pool connections (PostgreSQL/MySQL) | `10` |
|
||||
| `DATABASE_ACQUIRE_TIMEOUT_MS` | Wait for a free connection before failing (PostgreSQL/MySQL) | `15000` |
|
||||
| `DATABASE_CREATE_TIMEOUT_MS` | Wait for a new connection before failing (PostgreSQL/MySQL) | `15000` |
|
||||
| `DATABASE_IDLE_TIMEOUT_MS` | Idle time before a connection is closed (PostgreSQL/MySQL) | `30000` |
|
||||
| `DATABASE_KEEPALIVE` | TCP keepalive on connections (PostgreSQL/MySQL) | `true` |
|
||||
| Variable | Description | Default |
|
||||
| :---------------------------- | :----------------------------------------------------------- | :------------------------------------ |
|
||||
| `DATABASE_URL` | Full database connection string | `sqlite://./database/kener.sqlite.db` |
|
||||
| `DATABASE_POOL_MIN` | Minimum pool connections (PostgreSQL/MySQL) | `0` |
|
||||
| `DATABASE_POOL_MAX` | Maximum pool connections (PostgreSQL/MySQL) | `10` |
|
||||
| `DATABASE_ACQUIRE_TIMEOUT_MS` | Wait for a free connection before failing (PostgreSQL/MySQL) | `15000` |
|
||||
| `DATABASE_CREATE_TIMEOUT_MS` | Wait for a new connection before failing (PostgreSQL/MySQL) | `15000` |
|
||||
| `DATABASE_IDLE_TIMEOUT_MS` | Idle time before a connection is closed (PostgreSQL/MySQL) | `30000` |
|
||||
| `DATABASE_KEEPALIVE` | TCP keepalive on connections (PostgreSQL/MySQL) | `true` |
|
||||
|
||||
**Supported Databases**:
|
||||
|
||||
@@ -258,7 +258,7 @@ SMTP_SECURE=1
|
||||
|
||||
```bash
|
||||
# SQLite (default)
|
||||
DATABASE_URL=sqlite://./database/kener.db
|
||||
DATABASE_URL=sqlite://./database/kener.sqlite.db
|
||||
|
||||
# PostgreSQL
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/kener
|
||||
@@ -484,7 +484,7 @@ Create a `.env` file in the project root:
|
||||
```bash
|
||||
# .env
|
||||
KENER_SECRET_KEY=dev-secret-key
|
||||
DATABASE_URL=sqlite://./database/kener.db
|
||||
DATABASE_URL=sqlite://./database/kener.sqlite.db
|
||||
|
||||
# Custom variables
|
||||
API_KEY=test-key-123
|
||||
|
||||
Reference in New Issue
Block a user