@@ -173,3 +173,184 @@ interface DebugContext {
173173 metadata? : Record <string , unknown >;
174174}
175175```
176+
177+ ## Health Checks
178+
179+ Verify the health of all pools and connections for monitoring and load balancer integration:
180+
181+ ``` typescript
182+ const health = await tenants .healthCheck ();
183+
184+ if (health .healthy ) {
185+ console .log (' All systems operational' );
186+ } else {
187+ console .log (` ${health .unhealthyPools } pools are unhealthy ` );
188+ }
189+ ```
190+
191+ ### Express Endpoint
192+
193+ ``` typescript
194+ app .get (' /health' , async (req , res ) => {
195+ const health = await tenants .healthCheck ();
196+ res .status (health .healthy ? 200 : 503 ).json (health );
197+ });
198+ ```
199+
200+ ### Options
201+
202+ ``` typescript
203+ await tenants .healthCheck ({
204+ ping: true , // Execute SELECT 1 to verify connection (default: true)
205+ pingTimeoutMs: 5000 , // Timeout for ping query (default: 5000)
206+ includeShared: true , // Check shared database (default: true)
207+ tenantIds: [' t1' , ' t2' ], // Check specific tenants only (optional)
208+ });
209+ ```
210+
211+ ### HealthCheckResult
212+
213+ ``` typescript
214+ interface HealthCheckResult {
215+ healthy: boolean ; // Overall health status
216+ pools: PoolHealth []; // Per-tenant pool health
217+ sharedDb: PoolHealthStatus ; // 'ok' | 'degraded' | 'unhealthy'
218+ sharedDbResponseTimeMs? : number ;
219+ totalPools: number ;
220+ degradedPools: number ;
221+ unhealthyPools: number ;
222+ timestamp: string ;
223+ durationMs: number ;
224+ }
225+
226+ interface PoolHealth {
227+ tenantId: string ;
228+ schemaName: string ;
229+ status: ' ok' | ' degraded' | ' unhealthy' ;
230+ totalConnections: number ;
231+ idleConnections: number ;
232+ waitingRequests: number ;
233+ responseTimeMs? : number ;
234+ error? : string ;
235+ }
236+ ```
237+
238+ ### Health Status
239+
240+ | Status | Condition |
241+ | --------| -----------|
242+ | ` ok ` | Pool responding normally |
243+ | ` degraded ` | Requests waiting in queue or slow response |
244+ | ` unhealthy ` | Ping failed or timed out |
245+
246+ ## Metrics
247+
248+ Collect metrics on demand with zero overhead. Data is returned in a format-agnostic structure that you can format for any monitoring system:
249+
250+ ``` typescript
251+ const metrics = tenants .getMetrics ();
252+
253+ console .log (` Active pools: ${metrics .pools .total }/${metrics .pools .maxPools } ` );
254+ ```
255+
256+ ### MetricsResult
257+
258+ ``` typescript
259+ interface MetricsResult {
260+ pools: {
261+ total: number ;
262+ maxPools: number ;
263+ tenants: TenantPoolMetrics [];
264+ };
265+ shared: {
266+ initialized: boolean ;
267+ connections: ConnectionMetrics | null ;
268+ };
269+ timestamp: string ;
270+ }
271+
272+ interface TenantPoolMetrics {
273+ tenantId: string ;
274+ schemaName: string ;
275+ connections: ConnectionMetrics ;
276+ lastAccessedAt: string ;
277+ }
278+
279+ interface ConnectionMetrics {
280+ total: number ;
281+ idle: number ;
282+ waiting: number ;
283+ }
284+ ```
285+
286+ ### Prometheus Integration
287+
288+ ``` typescript
289+ import { Gauge , register } from ' prom-client' ;
290+
291+ const poolGauge = new Gauge ({
292+ name: ' drizzle_pool_count' ,
293+ help: ' Number of active tenant pools' ,
294+ });
295+
296+ const connectionsGauge = new Gauge ({
297+ name: ' drizzle_connections' ,
298+ help: ' Connection metrics by tenant' ,
299+ labelNames: [' tenant' , ' state' ],
300+ });
301+
302+ app .get (' /metrics' , async (req , res ) => {
303+ const metrics = tenants .getMetrics ();
304+
305+ poolGauge .set (metrics .pools .total );
306+
307+ for (const pool of metrics .pools .tenants ) {
308+ connectionsGauge .labels (pool .tenantId , ' total' ).set (pool .connections .total );
309+ connectionsGauge .labels (pool .tenantId , ' idle' ).set (pool .connections .idle );
310+ connectionsGauge .labels (pool .tenantId , ' waiting' ).set (pool .connections .waiting );
311+ }
312+
313+ res .set (' Content-Type' , ' text/plain' );
314+ res .send (await register .metrics ());
315+ });
316+ ```
317+
318+ ### Datadog / Custom APM
319+
320+ ``` typescript
321+ import { StatsD } from ' hot-shots' ;
322+
323+ const statsd = new StatsD ();
324+
325+ setInterval (() => {
326+ const metrics = tenants .getMetrics ();
327+
328+ statsd .gauge (' drizzle.pools.total' , metrics .pools .total );
329+
330+ for (const pool of metrics .pools .tenants ) {
331+ statsd .gauge (' drizzle.connections.idle' , pool .connections .idle , { tenant: pool .tenantId });
332+ statsd .gauge (' drizzle.connections.waiting' , pool .connections .waiting , { tenant: pool .tenantId });
333+ }
334+ }, 10000 );
335+ ```
336+
337+ ### Combined Health + Metrics Endpoint
338+
339+ ``` typescript
340+ app .get (' /status' , async (req , res ) => {
341+ const [health, metrics] = await Promise .all ([
342+ tenants .healthCheck (),
343+ Promise .resolve (tenants .getMetrics ()),
344+ ]);
345+
346+ res .json ({
347+ status: health .healthy ? ' healthy' : ' unhealthy' ,
348+ pools: {
349+ total: metrics .pools .total ,
350+ max: metrics .pools .maxPools ,
351+ unhealthy: health .unhealthyPools ,
352+ degraded: health .degradedPools ,
353+ },
354+ timestamp: metrics .timestamp ,
355+ });
356+ });
0 commit comments