Add monitoring service and database schema
- Implement monitoring service with heartbeat, lease tasks, resume tasks, and task result handling. - Create monitoring time utilities for business date calculations. - Add unit tests for date window resolution and business day handling. - Define database schema for monitoring-related tables including quotas, daily reports, and task management. - Establish migration scripts for creating and dropping monitoring tables.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMonitoringReceivedAlertThreshold = 15 * time.Minute
|
||||
defaultMonitoringReceivedInspectLimit = 100
|
||||
)
|
||||
|
||||
type monitoringStaleReceivedTask struct {
|
||||
ID int64
|
||||
TenantID int64
|
||||
BrandID int64
|
||||
QuestionID int64
|
||||
AIPlatformID string
|
||||
CollectorType string
|
||||
BusinessDate time.Time
|
||||
CallbackReceivedAt time.Time
|
||||
}
|
||||
|
||||
func markMonitoringStaleReceivedTasks(ctx context.Context, tx pgx.Tx, now time.Time, threshold time.Duration, limit int) ([]monitoringStaleReceivedTask, error) {
|
||||
if limit <= 0 {
|
||||
limit = defaultMonitoringReceivedInspectLimit
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, `
|
||||
WITH candidates AS (
|
||||
SELECT
|
||||
id,
|
||||
tenant_id,
|
||||
brand_id,
|
||||
question_id,
|
||||
ai_platform_id,
|
||||
collector_type,
|
||||
business_date,
|
||||
callback_received_at
|
||||
FROM monitoring_collect_tasks
|
||||
WHERE collector_type = $1
|
||||
AND status = 'received'
|
||||
AND callback_received_at IS NOT NULL
|
||||
AND callback_received_at < $2
|
||||
AND (
|
||||
request_payload_json IS NULL
|
||||
OR jsonb_typeof(request_payload_json) <> 'object'
|
||||
OR COALESCE(NULLIF(request_payload_json ->> 'received_alerted_at', ''), '') = ''
|
||||
)
|
||||
ORDER BY callback_received_at ASC, id ASC
|
||||
FOR UPDATE SKIP LOCKED
|
||||
LIMIT $3
|
||||
)
|
||||
UPDATE monitoring_collect_tasks t
|
||||
SET request_payload_json = (
|
||||
CASE
|
||||
WHEN t.request_payload_json IS NULL OR jsonb_typeof(t.request_payload_json) <> 'object'
|
||||
THEN '{}'::jsonb
|
||||
ELSE t.request_payload_json
|
||||
END
|
||||
) || jsonb_build_object(
|
||||
'received_alerted_at', $4::text,
|
||||
'received_alert_count', (
|
||||
CASE
|
||||
WHEN COALESCE(t.request_payload_json ->> 'received_alert_count', '') ~ '^[0-9]+$'
|
||||
THEN (t.request_payload_json ->> 'received_alert_count')::int
|
||||
ELSE 0
|
||||
END
|
||||
) + 1
|
||||
),
|
||||
updated_at = NOW()
|
||||
FROM candidates c
|
||||
WHERE t.id = c.id
|
||||
RETURNING
|
||||
c.id,
|
||||
c.tenant_id,
|
||||
c.brand_id,
|
||||
c.question_id,
|
||||
c.ai_platform_id,
|
||||
c.collector_type,
|
||||
c.business_date,
|
||||
c.callback_received_at
|
||||
`, monitoringCollectorType, now.Add(-threshold), limit, now.UTC().Format(time.RFC3339))
|
||||
if err != nil {
|
||||
return nil, monitoringInternalError(50041, "received_watchdog_query_failed", "failed to inspect stale received monitoring tasks", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make([]monitoringStaleReceivedTask, 0)
|
||||
for rows.Next() {
|
||||
var item monitoringStaleReceivedTask
|
||||
if scanErr := rows.Scan(
|
||||
&item.ID,
|
||||
&item.TenantID,
|
||||
&item.BrandID,
|
||||
&item.QuestionID,
|
||||
&item.AIPlatformID,
|
||||
&item.CollectorType,
|
||||
&item.BusinessDate,
|
||||
&item.CallbackReceivedAt,
|
||||
); scanErr != nil {
|
||||
return nil, monitoringInternalError(50041, "received_watchdog_scan_failed", "failed to parse stale received monitoring tasks", scanErr)
|
||||
}
|
||||
result = append(result, item)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, monitoringInternalError(50041, "received_watchdog_scan_failed", "failed to iterate stale received monitoring tasks", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
Reference in New Issue
Block a user