perf: lazy load heavy dependencies and db drivers

Move database drivers and heavy server-side modules to be loaded
lazily via require() and dynamic import(). This reduces the initial
memory footprint and improves cold start performance for serverless
functions by avoiding loading unused dependencies on every request.
This commit is contained in:
Richard R 2026-02-16 16:10:03 -07:00
parent 9931c7d0d8
commit fc24b31cc3
2 changed files with 29 additions and 16 deletions

View file

@ -1,14 +1,16 @@
import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';
import { drizzle as drizzleSqlite } from 'drizzle-orm/better-sqlite3';
import { sql } from 'drizzle-orm'; import { sql } from 'drizzle-orm';
import { Pool } from 'pg';
import Database from 'better-sqlite3';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import * as schema from './schema'; import * as schema from './schema';
import * as authSchemaSqlite from './schema_auth_sqlite'; import * as authSchemaSqlite from './schema_auth_sqlite';
import * as authSchemaPostgres from './schema_auth_postgres'; import * as authSchemaPostgres from './schema_auth_postgres';
// Database driver modules are loaded lazily via require() inside getDrizzleDB()
// to avoid loading the unused driver (~15-20 MB each) in every serverless function.
// require() is used instead of dynamic import() because getDrizzleDB() must remain
// synchronous for the SQLite code path.
const UNCLAIMED_USER_ID = 'unclaimed'; const UNCLAIMED_USER_ID = 'unclaimed';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -62,12 +64,20 @@ function getDrizzleDB() {
dbIsPostgres = !!process.env.POSTGRES_URL; dbIsPostgres = !!process.env.POSTGRES_URL;
if (dbIsPostgres) { if (dbIsPostgres) {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { drizzle: drizzlePg } = require('drizzle-orm/node-postgres');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { Pool } = require('pg');
const pool = new Pool({ const pool = new Pool({
connectionString: process.env.POSTGRES_URL, connectionString: process.env.POSTGRES_URL,
}); });
dbInstance = drizzlePg(pool, { schema: { ...schema, ...authSchemaPostgres } }); dbInstance = drizzlePg(pool, { schema: { ...schema, ...authSchemaPostgres } });
} else { } else {
// Fallback to SQLite // Fallback to SQLite
// eslint-disable-next-line @typescript-eslint/no-require-imports
const { drizzle: drizzleSqlite } = require('drizzle-orm/better-sqlite3');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const Database = require('better-sqlite3');
const dbPath = path.join(process.cwd(), 'docstore', 'sqlite3.db'); const dbPath = path.join(process.cwd(), 'docstore', 'sqlite3.db');
const dir = path.dirname(dbPath); const dir = path.dirname(dbPath);
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {

View file

@ -5,17 +5,13 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server'; import type { NextRequest } from 'next/server';
import { db } from "@/db"; import { db } from "@/db";
import { rateLimiter } from "@/lib/server/rate-limiter";
import { isAuthEnabled, isAnonymousAuthSessionsEnabled } from "@/lib/server/auth-config"; import { isAuthEnabled, isAnonymousAuthSessionsEnabled } from "@/lib/server/auth-config";
import { deleteUserStorageData } from "@/lib/server/user-data-cleanup";
import * as authSchemaSqlite from "@/db/schema_auth_sqlite"; import * as authSchemaSqlite from "@/db/schema_auth_sqlite";
import * as authSchemaPostgres from "@/db/schema_auth_postgres"; import * as authSchemaPostgres from "@/db/schema_auth_postgres";
import {
transferUserAudiobooks, // Heavy modules (S3 SDK, blobstore, rate-limiter, claim-data) are loaded
transferUserDocuments, // lazily via dynamic import() inside the beforeDelete / onLinkAccount
transferUserPreferences, // callbacks to avoid inflating every serverless function that touches auth.
transferUserProgress,
} from "@/lib/server/claim-data";
// ... // ...
@ -80,6 +76,7 @@ const createAuth = () => betterAuth({
enabled: true, enabled: true,
beforeDelete: async (user) => { beforeDelete: async (user) => {
try { try {
const { deleteUserStorageData } = await import('@/lib/server/user-data-cleanup');
await deleteUserStorageData(user.id, null); await deleteUserStorageData(user.id, null);
} catch (error) { } catch (error) {
console.error('[auth] Failed to clean up user storage before deletion:', error); console.error('[auth] Failed to clean up user storage before deletion:', error);
@ -123,6 +120,12 @@ const createAuth = () => betterAuth({
newUserEmail: newUser.user.email, newUserEmail: newUser.user.email,
}); });
// Lazy-load heavy modules only when account linking actually happens
const [{ rateLimiter }, claimData] = await Promise.all([
import('@/lib/server/rate-limiter'),
import('@/lib/server/claim-data'),
]);
// Transfer rate limiting data (TTS char counts) from anonymous user to authenticated user // Transfer rate limiting data (TTS char counts) from anonymous user to authenticated user
try { try {
await rateLimiter.transferAnonymousUsage(anonymousUser.user.id, newUser.user.id); await rateLimiter.transferAnonymousUsage(anonymousUser.user.id, newUser.user.id);
@ -134,7 +137,7 @@ const createAuth = () => betterAuth({
// Transfer audiobooks from anonymous user to new authenticated user // Transfer audiobooks from anonymous user to new authenticated user
try { try {
const transferred = await transferUserAudiobooks(anonymousUser.user.id, newUser.user.id); const transferred = await claimData.transferUserAudiobooks(anonymousUser.user.id, newUser.user.id);
if (transferred > 0) { if (transferred > 0) {
console.log(`Successfully transferred ${transferred} audiobook(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); console.log(`Successfully transferred ${transferred} audiobook(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`);
} }
@ -145,7 +148,7 @@ const createAuth = () => betterAuth({
// Transfer documents from anonymous user to new authenticated user // Transfer documents from anonymous user to new authenticated user
try { try {
const transferred = await transferUserDocuments(anonymousUser.user.id, newUser.user.id); const transferred = await claimData.transferUserDocuments(anonymousUser.user.id, newUser.user.id);
if (transferred > 0) { if (transferred > 0) {
console.log(`Successfully transferred ${transferred} document(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); console.log(`Successfully transferred ${transferred} document(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`);
} }
@ -156,7 +159,7 @@ const createAuth = () => betterAuth({
// Transfer preferences from anonymous user to new authenticated user // Transfer preferences from anonymous user to new authenticated user
try { try {
const transferred = await transferUserPreferences(anonymousUser.user.id, newUser.user.id); const transferred = await claimData.transferUserPreferences(anonymousUser.user.id, newUser.user.id);
if (transferred > 0) { if (transferred > 0) {
console.log(`Successfully transferred preferences from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); console.log(`Successfully transferred preferences from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`);
} }
@ -167,7 +170,7 @@ const createAuth = () => betterAuth({
// Transfer reading progress from anonymous user to new authenticated user // Transfer reading progress from anonymous user to new authenticated user
try { try {
const transferred = await transferUserProgress(anonymousUser.user.id, newUser.user.id); const transferred = await claimData.transferUserProgress(anonymousUser.user.id, newUser.user.id);
if (transferred > 0) { if (transferred > 0) {
console.log(`Successfully transferred ${transferred} progress row(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); console.log(`Successfully transferred ${transferred} progress row(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`);
} }