ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

TimescaleDB 后台作业调度器(Background Worker Jobs)原理与源码解析

TimescaleDB 后台作业调度器(Background Worker Jobs)原理与源码解析 时序数据库数据库关系型数据库【免费下载链接】timescaledbA time-series database for high-performance real-time analytics packaged as a Postgres extension项目地址https://gitcode.com/gh_mirrors/ti/timescaledb点击查看免费下载TimescaleDB 依靠大量后台任务维持系统运转压缩策略、连续聚合刷新、数据保留retention、统计上报等都需要在后台按计划反复执行。src/bgw模块为这些任务实现了一套轻量级、内建于 PostgreSQL 后台进程体系中的作业调度器。本文以 src/bgw/README.md 为核心骨架结合 scheduler.c、job.c、job_stat.c 等源码与 sql/pre_install/tables.sql 中的目录表定义完整解析其调度语义、崩溃检测机制、状态机与主循环实现。读完本文你将能说清 TimescaleDB 后台作业从何时运行、失败如何退避、崩溃如何被感知到如何被回收的完整链路并理解为何每个数据库需要独立的调度器。模块定位为什么需要一套自己的调度器TimescaleDB 需要运行多个后台作业background jobs例如自动化压缩、策略执行等。由于这些任务形态各异、生命周期长TimescaleDB 在扩展内部实现了一个简单的调度器scheduler使得插入到后台作业表中的作业可以按照计划被调度执行。该模块最显著的设计决策是实例instance中的每个数据库都运行各自独立的调度器。原因在于不同数据库可能安装了不同版本的 TimescaleDB 扩展不同版本可能要求不同的调度逻辑。从源码看调度器主函数ts_bgw_scheduler_mainscheduler.c在启动时通过MyDatabaseId与具体数据库绑定ts_bgw_start_worker 在注册后台 worker 时也将MyDatabaseId作为bgw_main_arg传入worker 随后通过BackgroundWorkerInitializeConnectionByOid连接指定数据库job.c。这意味着每个数据库的作业表、作业统计表都是独立维护、独立调度的。核心数据模型三张目录表调度器的持久化状态全部落在 TimescaleDB 内部目录表中定义于 sql/pre_install/tables.sql。作业表_timescaledb_catalog.bgw_jobCREATE TABLE _timescaledb_catalog.bgw_job ( id integer NOT NULL DEFAULT nextval(_timescaledb_catalog.bgw_job_id_seq), application_name name NOT NULL, schedule_interval interval NOT NULL, max_runtime interval NOT NULL, max_retries integer NOT NULL, retry_period interval NOT NULL, proc_schema name NOT NULL, proc_name name NOT NULL, owner regrole NOT NULL DEFAULT pg_catalog.quote_ident(current_role)::regrole, scheduled bool NOT NULL DEFAULT TRUE, fixed_schedule bool not null default true, initial_start timestamptz, hypertable_id integer, config jsonb, check_schema name, check_name name, timezone text, CONSTRAINT bgw_job_pkey PRIMARY KEY (id), CONSTRAINT bgw_job_hypertable_id_fkey FOREIGN KEY (hypertable_id) REFERENCES _timescaledb_catalog.hypertable (id) ON DELETE CASCADE );各列语义如下列类型含义idinteger作业 ID来自bgw_job_id_seq序列从 1000 开始见 tables.sqlapplication_namename作业在pg_stat_activity中显示的应用名实际拼接为name [job_id]见 job.cschedule_intervalinterval作业成功完成后调度器等待多久再启动下一次max_runtimeinterval作业超时阈值超过将被强制终止max_retriesinteger连续失败达到该次数后作业将被取消调度retry_periodinterval失败后指数退避的基准间隔proc_schema/proc_namename作业要调用的函数或过程的 schema 与名称scheduledbool是否参与调度fixed_schedulebool是否为固定时隙调度见下文成功后的下一次启动initial_starttimestamptz固定调度的起始锚点hypertable_idinteger关联的 hypertable可选configjsonb传给作业函数的配置check_schema/check_namename配置校验函数可选timezonetext固定调度计算时使用的时区统计表_timescaledb_internal.bgw_job_statCREATE TABLE _timescaledb_internal.bgw_job_stat ( job_id integer NOT NULL, last_start timestamptz NOT NULL DEFAULT NOW(), last_finish timestamptz NOT NULL, next_start timestamptz NOT NULL, last_successful_finish timestamptz NOT NULL, last_run_success bool NOT NULL, total_runs bigint NOT NULL, total_duration interval NOT NULL, total_duration_failures interval NOT NULL, total_successes bigint NOT NULL, total_failures bigint NOT NULL, total_crashes bigint NOT NULL, consecutive_failures int NOT NULL, consecutive_crashes int NOT NULL, flags int NOT NULL DEFAULT 0, CONSTRAINT bgw_job_stat_pkey PRIMARY KEY (job_id), CONSTRAINT bgw_job_stat_job_id_fkey FOREIGN KEY (job_id) REFERENCES _timescaledb_catalog.bgw_job (id) ON DELETE CASCADE );该表聚合记录一个作业的统计信息最近一次运行的开始/结束时间、是否成功以及连续失败次数与连续崩溃次数。其中next_start尤为关键——调度器重启后正是依据它来决定某个作业何时再次运行README「Design」一节。另外还有一张 bgw_job_stat_history 表以追加方式记录每次执行的 PID、开始/结束时间、是否成功与错误数据jsonb供运维排查。调度器对作业表的读取方式调度器启动时通过ts_bgw_job_get_scheduledjob.c扫描bgw_job表使用bgw_job_filter_scheduled过滤出scheduled true的作业并按主键索引BGW_JOB_PKEY_IDX保证按 job ID 排序。值得注意的优化是调度器读取时不物化config、check_schema、check_name字段源码注释明确说明原因避免 detoast、简化内存管理因为这些字段只有真正执行作业的 worker 才需要。调度周期与失败退避README「Schedules」一节定义了调度器的核心时间语义这里结合 job_stat.c 的实现将其精确化。成功schedule_interval决定下一次启动schedule_interval定义的是作业成功结束后调度器等待该间隔再启动它。也就是说调度是完成驱动而非绝对时钟驱动的。具体计算在calculate_next_start_on_successjob_stat.c中并区分两种模式固定时隙调度fixed_schedule true默认下一次启动 time_bucket(schedule_interval, finish_time, origin initial_start, timezone)之后的第一个时隙即下一次运行严格对齐到由initial_start锚定的固定边界上ts_get_next_scheduled_execution_slotjob_stat.c。若schedule_interval含月份分量源码退化为按月计算因为time_bucket无法对含月分量且带 origin 的区间取桶并依赖timezone做时区化取桶。漂移调度fixed_schedule false下一次启动 last_finish schedule_interval简单直接calculate_next_start_on_success_driftingjob_stat.c。另外若schedule_interval含月份分量则不能再混入日或时间分量ts_bgw_job_validate_schedule_intervaljob.c会在创建/修改作业时直接报错因为内部取桶逻辑无法处理这类混合区间。失败retry_period指数退避如果作业失败调度器使用retry_period以**指数退避exponential backoff**决定何时重跑。calculate_next_start_on_failurejob_stat.c的实现细节如下基础退避值retry_period × consecutive_failures注意consecutive_failures已包含本次失败即第 n 次连续失败乘数就是 n。乘数上限超过MAX_FAILURES_MULTIPLIER 20时封顶防止计算出超范围时间戳job_stat.c。退避上限ceilingMAX_INTERVALS_BACKOFF(5) × schedule_interval即退避最多拖到 5 个调度周期。抖动jitter乘上1.0 jitter其中 jitter 落在约[-12.5%, 12.5%]区间calculate_jitter_percentjob_stat.c用于避免多个失败作业同时惊群重试。对于固定时隙作业若退避算出的时间超过了下一个固定时隙则回落到该时隙保证作业不脱离固定节奏。整个计算被包在内部子事务中一旦溢出等异常发生会回滚并退化为now retry_periodjob_stat.c。崩溃额外的最小等待窗口calculate_next_start_on_crashjob_stat.c在失败退避的基础上强制保证MIN_WAIT_AFTER_CRASH_MS 5 分钟job_stat.c的最小等待即使按退避公式该立刻重跑也会被推迟到崩溃后至少 5 分钟。README 明确解释了这个设计的用意——给运维人员留出足够时间在作业再次崩溃前将其禁用。连续失败触发自动停用除了退避ts_bgw_job_check_max_retriesjob.c实现了熔断当max_retries 0且consecutive_failures max_retries时作业会被置为scheduled false停止调度并在日志中提示可通过alter_job(id, scheduled TRUE)重新启用。崩溃检测保守计数协议README「Design」一节用大段篇幅解释了一个 PostgreSQL 生态下的棘手问题这也是本模块最精妙的设计之一一次崩溃crash会导致 PostgreSQL所有进程立即退出因此一旦任何进程崩溃就无法再向数据库写入任何东西。所以必须能够从崩溃发生之前的一次提交中推断出崩溃确实发生过。实现方式是 job_stat.c 中的先标记、后撤销协议作业启动前调度器在独立事务中调用ts_bgw_job_stat_mark_startjob_stat.c将last_start置为当前时间、last_finish置为-infinity、next_start置为-infinitytotal_runs并把last_run_success false、total_crashes、consecutive_crashes。注释明确说明这些变更会在任何 end mark 中被撤销因此只有作业从未被标记结束即崩溃时崩溃计数才会保持递增。作业正常结束ts_bgw_job_stat_mark_endjob_stat.c写入last_finish然后撤销启动标记——total_crashes--、consecutive_crashes 0再按成功/失败分别累加total_successes/total_failures与consecutive_failures并计算下一次next_start。由此崩溃被证明的路径是作业启动标记已提交、但结束标记从未出现于是consecutive_crashes 0的状态会保留下来调度器据此采用崩溃退避。这也解释了 README 中所说的崩溃次数是对实际崩溃次数的过度估计overestimate——源码注释列举了三种会触发误报但无法避免的情形① 作业自身崩溃② 作业运行时其他进程崩溃殃及所有进程③ 调度器收到 SIGTERM 时连带给运行中作业发 SIGTERM而收到信号后不允许再写库结束标记无法落盘job_stat.c。过度估计是有意为之的保守策略确保绝不漏掉任何一次真实崩溃。调度器重启后ts_bgw_job_stat_next_startjob_stat.c决定每个作业的首次运行时间若consecutive_crashes 0则按崩溃退避计算并顺带通过ts_bgw_job_stat_mark_crash_reported记录崩溃已报告标志flags中的LAST_CRASH_REPORTED位若next_start仍为-infinitymark_start 写入的哨兵值可能因主备切换被继承则清洗为当前时间避免作业被永久卡死。调度器状态机README「Scheduler State Machine」一节给出了调度器为每个作业维护的状态机。需要指出的是文档中的STARTING状态在源码 scheduler.c 中实际命名为JOB_STATE_STARTED指作业已交给 worker 运行、尚未检测到其结束其余状态一一对应--------- -------- --- |SCHEDULED------- |DISABLED| | -------- -------- | | | | | v | ------- |-----STARTING| | ------- | | | | | v | ---------- |-----TERMINATING| | -----------各状态的语义与合法迁移均由scheduled_bgw_job_transition_state_to中的Assert约束见 scheduler.c状态语义允许的迁移JOB_STATE_SCHEDULED初始状态。作业未运行等待到点启动也可能是运行结束回到此状态等待下一轮任意状态可迁入包括自身JOB_STATE_STARTED文档中的 STARTING调度器已启动该作业的 worker作业正在运行或已结束但调度器尚未感知只能从SCHEDULED迁入JOB_STATE_TERMINATING调度器已显式下发终止信号如超时但尚未确认 worker 停止只能从STARTED迁入JOB_STATE_DISABLED终止态当前实现下无返回路径作业不再参与调度只能从STARTED或TERMINATING迁入迁入SCHEDULED时调度器会做完整的worker_state_cleanup释放 worker handle、归还保留的 worker 名额、必要时补记 job 结束状态并依据统计表重新计算next_startscheduler.c。主循环调度器进程是如何运转的调度器本身也是一个持续运行的 PostgreSQL background worker应用名为TimescaleDB Background Worker Scheduler定义于 job.h。其主循环ts_bgw_scheduler_processscheduler.c大致如下启动检查若处于恢复restore或二进制升级binary upgrade状态直接退出不调度ts_guc_restoring || IsBinaryUpgradescheduler.c。加载作业表启动时在一个事务中调用ts_update_scheduled_jobs_list从数据库读出全部待调度作业。注意 README「Limitations」指出的第一个限制——作业列表只在调度器启动时读取且本模块当前实现为虽然调度器通过AcceptInvalidationMessages处理缓存失效消息ts_bgw_job_cache_invalidate_callback置位jobs_list_needs_update见 scheduler.c可以刷新列表但 README 面向的是第一版实现的语义描述ts_update_scheduled_jobs_listscheduler.c会按 job ID 合并新旧列表将新作业初始化为SCHEDULED对已删除的作业执行terminate_and_cleanup_job。循环体每轮迭代start_scheduled_jobsscheduler.c将作业按next_start升序排序cmp_next_start到点next_start now或next_start -infinity即启动。计算下一次唤醒时间取最早需要启动的作业时间与最早需要强杀的作业超时时间的较小值然后ts_timer_wait睡到该时刻scheduler.c。处理 SIGHUP重载配置、处理缓存失效消息、调用check_for_stopped_and_timed_out_jobs巡检已启动/终止中的作业scheduler.c。每轮结束重置scratch_mctx临时内存上下文。退出路径等待所有已启动/终止中作业退出、清理所有 worker然后proc_exit。作业的启动流程start_scheduled_jobs对到点作业调用scheduled_ts_bgw_job_startscheduler.c其关键顺序注释强调必须在遇到任何错误之前先标记作业启动这样错误才能被登记事务内校验作业仍存在 →ts_bgw_worker_reserve预留 worker 名额失败则保持SCHEDULED并告警→mark_job_as_started即上述崩溃计数协议的 mark_start→ 若max_runtime 0计算timeout_at。通过ts_bgw_job_startjob.c构造BgwParamsjob_id、history 信息、user_oid、入口函数名ts_bgw_job_entrypoint最终由ts_bgw_start_worker调用RegisterDynamicBackgroundWorker注册 workerscheduler.c。注册失败时走on_failure_to_start_job恢复原next_start保持优先级并标记JOB_FAILURE_TO_START结束。WaitForBackgroundWorkerStartup等待 worker 真正启动。真正的作业执行发生在 worker 进程内入口是ts_bgw_job_entrypointjob.c建立数据库连接后作业函数通过ts_bgw_job_executejob.c执行——telemetry 作业走独立代码路径其余交给job_execute执行前会将max_parallel_workers_per_gather、max_parallel_workers、max_parallel_maintenance_workers归零以禁用并行执行作业负责自行提交/回滚自身事务结束时由作业进程mark_end若作业因信号无法标记调度器在worker_state_cleanup中代为补记JOB_FAILURE_IN_EXECUTION。超时与终止调度器通过earliest_job_timeout追踪各运行中作业的timeout_at 启动时间 max_runtime见 job.c。在check_for_stopped_and_timed_out_jobs中若作业仍处于STARTED且当前时间已到超时点则将其迁移到TERMINATING并记录 WARNING。terminate_and_cleanup_jobscheduler.c提供了分级终止策略先尝试用pg_cancel_backendSIGINT优雅取消轮询等待 3 秒每 100ms 检查一次仍未退出才调用TerminateBackgroundWorkerSIGTERM并等待其完全关闭。信号处理与内存管理调度器注册了两类信号处理scheduler.cSIGTERM使用die而非默认的bgworker_die因为后者不尊重临界区SIGHUP触发配置重载。内存方面调度器维护两个内存上下文scheduler.cscheduler_mctx存放长生命周期对象如作业列表、worker handlescratch_mctx存放每轮迭代的临时数据并在循环末尾整体重置——这是避免长期运行进程内存泄漏的关键手段。已知限制README「Limitations」一节明确列出了这一版实现的两个限制它们在 scheduler.c 文件头的注释中也有对应说明作业列表的加载时机待运行作业列表在调度器首次启动时从数据库读取ts_update_scheduled_jobs_list。虽然通过缓存失效机制可以在作业表变更时刷新jobs_list_needs_update但作为第一版实现README 强调其原始设计并不主动跟踪作业表的每次变化。没有优先级调度器不区分作业优先级所有作业按next_start时间先后启动start_scheduled_jobs只按时间排序scheduler.c先到先服务。小结src/bgw模块以每个数据库一个独立调度器进程 目录表持久化 保守崩溃计数为三大支柱用约两千行 C 代码支撑起了 TimescaleDB 全部后台自动化能力。理解schedule_interval的完成驱动语义、retry_period的指数退避与 5 分钟崩溃冷却窗、mark_start/mark_end 的崩溃推断协议以及 SCHEDULED→STARTED→TERMINATING 的状态机是读懂 TimescaleDB 后台任务体系乃至排查作业为什么不跑/为什么反复崩溃类问题的前提。感兴趣的读者可以继续深入 job_stat.c、job.c、scheduler.c 的完整实现以及 job_stat_history.c 与 launcher_interface.c负责 worker 名额预留等配套模块。赞分享时序数据库数据库关系型数据库【免费下载链接】timescaledbA time-series database for high-performance real-time analytics packaged as a Postgres extension项目地址https://gitcode.com/gh_mirrors/ti/timescaledb点击查看免费下载相关推荐openvr_fsr兼容性完全手册哪些SteamVR游戏完美支持哪些需要特殊配置openvr_fsr兼容性完全手册哪些SteamVR游戏完美支持哪些需要特殊配置 openvr_fsr是一款为SteamVR游戏提供AMD Fidelity游戏开发图形学LlamaIndex Data Connector 使用模式指南从 download_loader 到数据加载实战LlamaIndex Data Connector 使用模式指南从 download_loader 到数据加载实战 本文围绕 LlamaIndex 文档加载后端网络数据建模区块链产品经理之道黑马程序员教你如何设计区块链应用区块链产品经理之道黑马程序员教你如何设计区块链应用 在当今数字经济时代 区块链产品经理 正成为技术领域最炙手可热的职位之一。黑马程序员推出的120天全栈区块可观测性性能剖析后端运维观测上一篇mxbai-embed-large-v1-gguf量化原理Q4_K_M为何成为推荐格式下一篇Craft Agents 文档工具冒烟测试指南基于 uv 的 CLI 工具验证体系解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表