
Karakeep 系统架构解析SQLite 任务队列驱动的 Web 应用与三类后台 Worker【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder导读本文基于 04-architecture.mdv0.32.0 版本文档当前仓库docs/versioned_docs/version-v0.32.0/08-development/04-architecture.md亦含同内容深入解析 KarakeepHoarder 开源项目的新名的整体架构一个以Next.js 构建的 Web 应用Webapp作为前端入口以SQLite 同时承担数据存储与任务队列的角色配合多组后台 Worker 异步消费任务其中三类核心 Worker爬取 Crawling、推理 Inference、索引 Indexing分别完成网页抓取、AI 自动打标与全文检索索引。读完本文你将掌握该项目的核心数据流、三类任务的执行链路、关键环境变量配置以及各模块在仓库中的源码位置为二次开发或自托管调优提供清晰的架构地图。Karakeep 总体架构图Web App 与 Workers 通过 SQLite 持久化数据Workers 借助无头浏览器Headless Browser抓取网页并将解析内容索引到 Meilisearch 供全文检索。一、整体架构三大角色的职责划分架构文档用极简的几行文字勾勒出整个系统的骨架其核心可以拆解为三个角色角色技术栈职责WebappNext.js SQLite面向用户的前端界面与数据持久化层WorkersNode.js 常驻进程同仓库apps/workers从 SQLite 任务队列消费作业并执行Meilisearch独立搜索引擎服务为书签内容建立全文索引支撑快速检索从 docker-compose.yml 可以看出生产部署形态web服务是唯一的用户入口暴露3000端口chrome服务为无头浏览器BROWSER_WEB_URL: http://chrome:9222meilisearch服务提供搜索能力MEILI_ADDR: http://meilisearch:7700。Web 服务同时通过DATA_DIR: /data将 SQLite 数据文件持久化到磁盘卷。也就是说SQLite 数据文件、无头浏览器和 Meilisearch 都以独立服务的形式存在而 Workers 作为web镜像内部的子进程运行——这正是单容器部署Monolith形态的基础。从源码结构看Web 应用本体位于 apps/webNext.js App RouterAPI 路由与 TRPC 层位于 packages/api 与 packages/trpc数据模型Drizzle ORM Schema位于 packages/db/schema.tsWorker 进程则整体位于 apps/workers。二、SQLite 的双重身份数据仓库 任务队列架构文档特别强调基于 SQLite 的任务队列sqlite based job queue这是本项目区别于常规Redis 消息队列方案的最大特色同一个 SQLite 数据库文件既保存业务数据又充当作业队列的持久化存储。这带来的直接好处是部署极简——不需要额外维护 Redis 或专用队列服务单文件数据库天然支持备份与迁移仓库snapshots/目录即存放着 seed 数据快照。任务队列的抽象定义在 packages/shared/queueing.tsQueue接口enqueue(payload, options)入队、stats()查询 pending / running / failed 等队列状态Runner接口通过run(job)执行任务、onComplete(job, result)处理成功、onError(job)处理失败EnqueueOptions支持priority优先级、groupId分组、delayMs延迟执行等调度参数特殊错误QueueRetryAfterError用于限流后稍后重试且不计入重试次数上限见 queueing.ts。队列的实际实现由插件系统提供。getQueueClient()通过PluginManager.getClient(PluginType.Queue)获取队列客户端queueing.ts仓库 packages/plugins 下既有queue-liteque基于 SQLite 的轻量队列也有queue-restate基于 Restate 的分布式队列等实现可选。而任务入队与消费的总装配在 apps/workers/index.tsworkerBuilders注册了crawler、lowPriorityCrawler、embeddings、inference、search、adminMaintenance、video、feed、assetPreprocessing、webhook、ruleEngine、backup共十余种 WorkerWORKERS_ENABLED_WORKERS/WORKERS_DISABLED_WORKERS两个环境变量逗号分隔的 Worker 名单可精确控制启用/禁用哪些 Worker见 config.ts所有 Worker 通过getQueueClient().createRunner()以pollIntervalMs: 1000每秒轮询一次、可配置concurrency与timeoutSecs的方式运行。三、三类核心 Worker 深入解析架构文档列出的三类任务Crawling / OpenAI / Indexing正是数据从收藏到可检索全流程的三个环节。下面结合源码逐一展开。3.1 Crawling Worker无头浏览器抓取网页职责接收爬取任务使用运行在 workers 容器中的无头 Chrome 浏览器获取链接内容并产出正文、截图、PDF、元数据等资产。入口与调度crawlerWorker.ts 中CrawlerWorker.build()通过getQueueClient().createRunnerZCrawlLinkRequest, CrawlerRunResult()注册执行器其核心参数来自配置config.ts环境变量默认值说明BROWSER_WEB_URL/BROWSER_WEBSOCKET_URL无无头浏览器地址HTTP / WebSocket 两种连接方式BROWSER_CONNECT_ONDEMANDfalse是否按需建立浏览器连接BROWSER_COOKIE_PATH无浏览器 Cookie 文件路径用于登录态抓取CRAWLER_NUM_WORKERS1并发爬取 Worker 数CRAWLER_JOB_TIMEOUT_SEC60单个爬取任务超时时间秒CRAWLER_NAVIGATE_TIMEOUT_SEC30页面导航超时秒CRAWLER_STORE_SCREENSHOTtrue是否保存截图CRAWLER_FULL_PAGE_SCREENSHOTfalse是否保存整页长截图CRAWLER_STORE_PDFfalse是否另存 PDFCRAWLER_FULL_PAGE_ARCHIVEfalse是否生成整页归档SingleFile/MonolithCRAWLER_VIDEO_DOWNLOADfalse是否尝试下载页面视频CRAWLER_ENABLE_ADBLOCKERtrue是否启用广告拦截器CRAWLER_ENABLE_AUTOCONSENTtrue是否自动同意 Cookie 弹窗CRAWLER_DOMAIN_RATE_LIMIT_WINDOW_MS/CRAWLER_DOMAIN_RATE_LIMIT_MAX_REQUESTS无按域名限流的窗口与请求上限CRAWLER_HTTP_PROXY/CRAWLER_HTTPS_PROXY/CRAWLER_NO_PROXY无爬取代理配置逗号分隔执行链路runCrawlercrawlerWorker.ts解析请求用zCrawlLinkRequestSchema校验任务数据提取bookmarkId、archiveFullPage、storePdf限流检查checkDomainRateLimit()按目标域名调用限流客户端被限流时抛出QueueRetryAfterError并以 1.01.4 的随机抖动延迟重试避免惊群效应crawlerWorker.ts探测内容类型getContentTypeAndMetadata()预检 URL 的 Content-Type若是 PDF 或受支持的图片类型则走handleAsAssetBookmark()将其作为资产书签asset bookmark处理而非网页抓取浏览器抓取与解析crawlAndParseUrl()执行真正的浏览器渲染、HTML 解析parseSubprocess.ts子进程解析以隔离内存占用、正文提取与元数据写入入队后续任务enqueuePostCrawlJobs()在抓取成功后按需入队推理打标/摘要/Embedding、搜索重建索引、视频下载和crawledWebhook 等下游任务crawlerWorker.ts归档最后执行截图/PDF/整页归档等可能失败的归档逻辑成功与否通过onComplete/onError回写bookmarkLinks.crawlStatus为success/failure。抓取成功后任务会自动级联触发后续处理——这正是架构图中Web App → Workers → 下游数据流的源头。3.2 Inference Worker调用 AI 服务自动打标与摘要职责调用 OpenAI 兼容 API也支持 Ollama 等本地模型见OLLAMA_BASE_URL配置对已抓取内容进行标签推断与摘要生成。入口与调度inferenceWorker.ts 中OpenAiWorker.build()监听OpenAIQueue根据任务类型type: tag | summarize分发到runTagging()与runSummarization()并通过attemptMarkStatus()把taggingStatus/summarizationStatus写回bookmarks表。关键配置config.ts环境变量默认值说明OPENAI_API_KEY无OpenAI或兼容服务API KeyOPENAI_BASE_URL无自定义 API Base URL对接兼容服务OLLAMA_BASE_URL无Ollama 本地模型地址INFERENCE_TEXT_MODELgpt-5.6-luna文本推理模型INFERENCE_IMAGE_MODELgpt-4o-mini图片推理模型INFERENCE_ENABLE_AUTO_TAGGINGtrue是否启用自动打标INFERENCE_ENABLE_AUTO_SUMMARIZATIONfalse是否启用自动摘要INFERENCE_LANGenglish打标语言偏好INFERENCE_NUM_WORKERS1推理并发数INFERENCE_JOB_TIMEOUT_SEC30推理任务超时INFERENCE_CONTEXT_LENGTH2048输入上下文长度INFERENCE_MAX_OUTPUT_TOKENS2048最大输出 Token 数注意inference.isConfigured的计算逻辑!!OPENAI_API_KEY || !!OLLAMA_BASE_URLconfig.ts即只要配置了 OpenAI 系或 Ollama 任一即可启用推理。若未配置任何推理后端runOpenAI会记录没有推理客户端并直接返回inferenceWorker.ts不会阻塞抓取主流程——推理是可选增强而非强依赖。从爬取 Worker 的enqueuePostCrawlJobs()还可以看到推理与 Embedding 的联动若启用了 Embedding 自动索引EMBEDDING_ENABLE_AUTO_INDEXING默认在 OpenAI 默认配置下为true则先入队 Embedding 任务类型embed完成后触发打标runTaggingOnComplete: true否则直接入队tag任务摘要任务summarize始终独立入队crawlerWorker.ts。3.3 Indexing Worker写入 Meilisearch 加速检索职责将书签的结构化字段标题、URL、正文、标签、摘要等组装为搜索文档写入 Meilisearch并处理书签删除时的索引清理。入口与调度searchWorker.ts 中SearchIndexingWorker.build()监听SearchIndexingQueue按任务类型type: index | delete分别执行runIndex()与runDelete()。索引文档结构BookmarkSearchDocumentsearchWorker.ts包含书签id、userId、链接型书签的url/linkTitle/description/ 纯文本正文content/publisher/author/ 发布时间资产型书签的content与metadata文本型书签的text以及公共字段note、summary、title、createdAt、tags。搜索客户端与索引配置Meilisearch 集成位于插件 packages/plugins/search-meilisearch/src/index.tsMeiliSearchProvider.isConfigured()检查MEILI_ADDR环境变量默认由 docker-compose 注入http://meilisearch:7700初始化时自动创建bookmarks索引primaryKey: id并确保filterableAttributes [id, userId]、sortableAttributes [createdAt]符合预期index.ts写入采用BatchingDocumentQueue批量队列MEILI_BATCH_SIZE/MEILI_BATCH_TIMEOUT_MS可调但重试运行runNumber 0时禁用批量、直接写入以提高可靠性searchWorker.ts搜索时通过filterToMeiliSearchFilter()把过滤条件翻译为 Meilisearch 过滤语法/IN [...]排序字段为createdAtindex.ts。搜索索引相关配置config.tsSEARCH_NUM_WORKERS默认 1、SEARCH_JOB_TIMEOUT_SEC默认 30。四、一条书签的完整生命周期从收藏到可检索将上述三个 Worker 串起来一条链接书签从用户点击收藏到出现在搜索结果中完整链路如下入库Web App 通过 API / TRPC 创建书签记录并写入 SQLite数据模型见 packages/db/schema.ts入队爬取创建书签时向LinkCrawlerQueue或低优先级队列LowPriorityCrawlerQueue入队ZCrawlLinkRequest爬取Crawling Worker 每秒轮询取任务经域名限流、内容类型探测后用无头 Chrome 渲染页面并解析正文与元数据产物写入 SQLite正文资产、截图等随后级联入队推理与搜索重建任务enqueuePostCrawlJobscrawlerWorker.ts推理Inference Worker 消费OpenAIQueue调用 AI 模型生成标签必要时先生成 Embedding 再打标与摘要回写bookmarks.taggingStatus/summarizationStatus索引Search Worker 消费SearchIndexingQueue将书签组装为搜索文档批量写入 Meilisearchbookmarks索引检索用户在前端输入关键词Web App 调用 Meilisearch 完成全文检索过滤条件按userId隔离避免跨用户数据泄露。其中第 3、4 步之间通过队列优先级传播enqueueOpts.priority job.prioritycrawlerWorker.ts保证新收藏的高优先级书签优先被处理同时每次运行都会记录埋点指标workerStatsCounter、bookmarkCrawlLatencyHistogram便于观测整条流水线的健康度。五、架构的可扩展性与容错设计虽然架构文档只提到了三类任务但实际仓库中的 Worker 体系已经远远不止这些。从 apps/workers/index.ts 可以看到还包括embeddings向量化、video视频下载、feedRSS 订阅刷新、webhookWebhook 投递、backup定时备份、assetPreprocessing资产预处理、ruleEngine规则引擎、adminMaintenance管理维护等。它们共享同一套队列抽象因此新增一类任务只需实现入队类型 Runner 回调 Worker 构建器这正是插件化队列设计的价值所在。容错方面值得关注的设计点失败重试与状态回写爬取失败且重试耗尽时onError会在一个数据库事务里把crawlStatus置为failure并清理taggingStatus、summarizationStatus、embeddingStatus中残留的pending状态crawlerWorker.ts避免下游任务悬挂限流退避域名限流通过QueueRetryAfterError延迟重试且不消耗重试次数配合 40% 随机抖动防止限流恢复瞬间的请求风暴无搜索降级当MEILI_ADDR未配置时搜索 Worker 记录搜索未配置并直接返回searchWorker.ts系统其余功能不受影响无推理降级未配置任何 AI 后端时推理任务直接跳过抓取与归档正常完成。六、开发与部署相关参考架构总览docs/docs/08-development/04-architecture.mdv0.32.0 版本位于 docs/versioned_docs/version-v0.32.0/08-development/04-architecture.md架构图源文件为 docs/static/img/architecture/arch.pngWorker 进程装配apps/workers/index.ts爬取 Worker 实现apps/workers/workers/crawlerWorker.ts 及 apps/workers/workers/crawler 目录浏览器生命周期、页面抓取、探测、解析、资产持久化等模块推理 Worker 实现apps/workers/workers/inference/inferenceWorker.ts打标与摘要分别位于 apps/workers/workers/inference 下的tagging.ts与summarize.ts搜索索引 Worker 实现apps/workers/workers/searchWorker.tsMeilisearch 插件packages/plugins/search-meilisearch/src/index.ts队列抽象与插件packages/shared/queueing.ts、packages/pluginsqueue-liteque、queue-restate全部环境变量定义packages/shared/config.ts容器编排docker/docker-compose.ymlweb / chrome / meilisearch 三服务本地开发启动start-dev.sh 与 CONTRIBUTING.md。结语Karakeep 的架构用一个 SQLite 文件同时承担了业务存储与任务队列的双重职责配合 Next.js Web 应用与按需启停的多组 Worker在极简部署单容器 Chrome Meilisearch与功能完整性之间取得了很好的平衡。理解爬取 → 推理 → 索引这条主流水线及其容错设计是深入阅读 apps/workers 源码、调优自托管实例如通过CRAWLER_NUM_WORKERS、INFERENCE_NUM_WORKERS、SEARCH_NUM_WORKERS调整并发或为项目贡献新 Worker 类型的最佳起点。【免费下载链接】hoarderA self-hostable bookmark-everything app (links, notes and images) with AI-based automatic tagging and full text search项目地址: https://gitcode.com/GitHub_Trending/ho/hoarder创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考