ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ArchiveBox v0.7.2/v0.8.6 升级 v0.9.0 迁移路径修复指南:Django 数据库迁移全流程解析

ArchiveBox v0.7.2/v0.8.6 升级 v0.9.0 迁移路径修复指南:Django 数据库迁移全流程解析 ArchiveBox v0.7.2/v0.8.6 升级 v0.9.0 迁移路径修复指南Django 数据库迁移全流程解析【免费下载链接】ArchiveBox Open source self-hosted web archiving. Takes URLs/browser history/bookmarks/Pocket/Pinboard/etc., saves HTML, JS, PDFs, media, and more...项目地址: https://gitcode.com/gh_mirrors/ar/ArchiveBox导读本文以仓库内 old/TODO_fix_migration_path.md 为核心主线系统讲解 ArchiveBox 从 v0.7.2 / v0.8.6rc0 升级到 v0.9.0 时数据库迁移路径的设计思路、踩坑清单与验证方法。你会掌握三套旧版本 schema 的差异、SeparateDatabaseAndState双轨迁移的正确用法、如何用最小手工 SQL Django 状态同步避免升级丢数据以及如何在仓库测试基建archivebox/tests/migrations_helpers.py之上复现并验证三类升级场景。一、核心问题v0.7.2 → v0.9.0 升级会丢数据v0.9.0 对核心表core_archiveresult、core_snapshot、core_tag、crawls_crawl做了大规模字段重命名与类型重构。如果迁移实现不到位升级过程中会出现以下典型数据丢失extractor字段数据没有复制到plugin字段output字段数据没有复制到output_str字段时间戳字段added/updated没有被正确转换Tag 主键从 UUID 转换为 INTEGER 时丢失外键关联core_snapshot_tags断裂。这些字段并不是简单的改名v0.9.0 的plugin、output_str是由 DjangoAddField操作以默认值新增的列若在 SQL 阶段直接写新列名后续AddField会用默认值覆盖已复制好的数据导致迁移成功但内容全空。这正是本文要解决的核心矛盾。二、三个版本的表结构差异理解迁移必须先吃透三套 schema。下表整理自 old/TODO_fix_migration_path.md 的 Schema Version Differences 章节并与当前源码archivebox/core/migrations/0023_upgrade_to_0_9_0.py中的字段处理一一对照。v0.7.2迁移 0022 之后表关键字段core_archiveresultid(INTEGER)、uuid、extractor、output、cmd、pwd、cmd_version、start_ts、end_ts、status、snapshot_idcore_snapshotid、url、timestamp、title、added、updated、crawl_idcore_tagid(INTEGER)、name、slugcrawls_crawlv0.7.2 中尚不存在v0.8.6rc0表关键字段core_archiveresultid、abid不是 uuid、extractor、output、created_at、modified_at、retry_at、status…core_snapshotid、url、bookmarked_at、created_at、modified_at、crawl_id、status、retry_at…core_tagid(UUID/CHAR)、name、slug、abid、created_at、modified_at、created_by_idcrawls_crawlid、seed_id、persona(VARCHAR)、max_depth、tags_str、status、retry_at…v0.9.0 目标 schema表关键字段core_archiveresultid(INTEGER)、uuid、plugin替代 extractor、output_str替代 output、hook_name、created_at、modified_at、output_files、output_json、output_size、output_mimetypes、retry_at…core_snapshotid、url、bookmarked_at替代 added、created_at、modified_at替代 updated、crawl_id、parent_snapshot_id、status、retry_at、current_step、depth、fs_version…core_tagid(INTEGER)、name、slug、created_at、modified_at、created_by_idcrawls_crawlid、urls替代 seed_id、persona_id替代 persona、label、notes、output_dir…仓库内 archivebox/tests/migrations_helpers.py 完整定义了SCHEMA_0_4第 26 行、SCHEMA_0_7第 50 行与SCHEMA_0_8第 187 行三套建表脚本是理解迁移前数据库长什么样的第一手资料可用于搭建可重复的迁移测试环境。三、迁移测试方法论三个场景 数据完整性校验3.1 三种必测场景无论迁移代码怎么写都必须跑通以下三个场景摘自原文档 How to Test Migrations场景 1全新安装Fresh Installrm -rf /tmp/test_fresh mkdir -p /tmp/test_fresh DATA_DIR/tmp/test_fresh python -m archivebox init DATA_DIR/tmp/test_fresh python -m archivebox status场景 2v0.7.2 升级rm -rf /tmp/test_v072 mkdir -p /tmp/test_v072 cp /path/to/archivebox-v0.7.2/data/index.sqlite3 /tmp/test_v072/ DATA_DIR/tmp/test_v072 python -m archivebox init DATA_DIR/tmp/test_v072 python -m archivebox status场景 3v0.8.6rc0 升级rm -rf /tmp/test_v086 mkdir -p /tmp/test_v086 cp /path/to/archivebox-v0.8.6rc0/data/index.sqlite3 /tmp/test_v086/ DATA_DIR/tmp/test_v086 python -m archivebox init DATA_DIR/tmp/test_v086 python -m archivebox status原文档中提到的/Users/squash/Local/Code/...是开发者本机路径不具备可移植性仓库的做法是在 archivebox/tests/migrations_helpers.py 中用SCHEMA_0_7/SCHEMA_0_8建表、用seed_0_7_data()/seed_0_8_data()灌入真实感数据含用户、标签、快照、ArchiveResult、Seed/Crawl 关联、API Token 等再通过run_archivebox_migration_cmd()执行init。对应测试用例见 archivebox/tests/test_migrations_fresh.py 与 archivebox/tests/test_migrations_08_to_09.py。3.2 数据完整性校验 SQL每次迁移后用 sqlite3 对比原始库与迁移后库的字段级数据# 检查 ArchiveResult 数据是否保留原库 extractor/output vs 新库 plugin/output_str echo ORIGINAL sqlite3 /path/to/original.db SELECT id, extractor, output, status FROM core_archiveresult LIMIT 5; echo MIGRATED sqlite3 /tmp/test_vXXX/index.sqlite3 SELECT id, plugin, output_str, status FROM core_archiveresult LIMIT 5; # 检查 Snapshot 时间戳语义 echo ORIGINAL SNAPSHOTS sqlite3 /path/to/original.db SELECT id, url, title, added, updated FROM core_snapshot LIMIT 5; echo MIGRATED SNAPSHOTS sqlite3 /tmp/test_vXXX/index.sqlite3 SELECT id, url, title, bookmarked_at, created_at, modified_at FROM core_snapshot LIMIT 5; # 检查 Tag 及快照-标签关联数量 echo ORIGINAL TAGS sqlite3 /path/to/original.db SELECT * FROM core_tag; echo MIGRATED TAGS sqlite3 /tmp/test_vXXX/index.sqlite3 SELECT * FROM core_tag; sqlite3 /tmp/test_vXXX/index.sqlite3 SELECT COUNT(*) FROM core_snapshot_tags;关键校验点行数一致所有 URL、标题、时间戳原样保留所有extractor值复制到plugin所有output值复制到output_str所有标签关联完整v0.8.6 的 Tag ID 需从 UUID 转为 INTEGER 且关联表同步重写。仓库测试对上述校验做了自动化test_migration_preserves_snapshot_count、test_migration_preserves_tags、test_migration_preserves_archiveresults同时断言每条 ArchiveResult 都关联到新machine_process记录、test_tag_associations_preserved_after_migration、test_timestamps_preserved_after_migration等均可在 archivebox/tests/test_migrations_08_to_09.py 中找到。四、迁移哲学最小手工 SQL原文档的 Migration Philosophy 提出了一个关键原则SQL 阶段只做表重建保数据不做字段重命名字段映射交给 Django 的AddFieldRunPython数据复制最后用SeparateDatabaseAndState让 Django 状态与真实库同步。五步流程如下Python探测现有 schema 版本def get_table_columns(table_name): cursor connection.cursor() cursor.execute(fPRAGMA table_info({table_name})) return {row[1] for row in cursor.fetchall()} cols get_table_columns(core_archiveresult) has_extractor extractor in cols has_plugin plugin in colsSQL迁移期间重建表结构CREATE TABLE core_archiveresult_new (...); INSERT INTO core_archiveresult_new SELECT ... FROM core_archiveresult; DROP TABLE core_archiveresult; ALTER TABLE core_archiveresult_new RENAME TO core_archiveresult;Python新旧字段间复制数据if extractor in cols and plugin in cols: cursor.execute(UPDATE core_archiveresult SET plugin COALESCE(extractor, ))SQL删除旧列/旧表RemoveField交给 DjangoDjango注册最终状态migrations.SeparateDatabaseAndState( database_operations[...], # 你的 SQL/Python 迁移 state_operations[...] # 告诉 Django 最终 schema 长什么样 )这套五步法在仓库中已经完整落地下面结合源码逐文件解析。五、关键迁移文件深度解析5.1 core/migrations/0023_upgrade_to_0_9_0.pySQL 重建三张核心表该迁移的职责是用旧字段名重建表、逐行搬运数据、带行数断言地替换旧表。关键实现archivebox/core/migrations/0023_upgrade_to_0_9_0.py防丢数据硬约束assert_rebuild_row_count()第 22-32 行在DROP TABLE之前比较源表与目标表行数不一致直接raise RuntimeError拒绝用残缺数据替换旧表。状态归一化normalize_status()第 47-58 行把success/succeded统一映射为succeeded其余未知状态回落为failednormalize_cmd()把命令行统一序列化为 JSON 数组。ArchiveResultPART 1新建core_archiveresult_new时只保留旧字段名extractor、output第 111-112 行并按来源分三条分支复制v0.7.2有uuid、v0.8.6rc0有abid、两者皆无生成新 UUID。UUID 使用 archivebox/uuid_compat.py 提供的uuid7()生成。SnapshotPART 2core_snapshot_new直接采用 v0.9.0 的新时间戳字段bookmarked_at、created_at、modified_at、downloaded_at第 288-290 行。v0.7.2 源的added/updated在 SQL 中完成语义映射bookmarked_at优先用timestamp的 unixepoch 转换、回落addedupdated被重命名为downloaded_at第 343-364 行。TagPART 3先通过PRAGMA table_info判断id列类型第 472-477 行。若为 CHAR/UUIDv0.8.6rc0建立uuid_to_int_map映射、以顺序整数重写core_tag.id并重建core_snapshot_tags关联表第 489-531 行若为 INTEGER 则原样搬运并保留审计字段。Postgres 分支SQLite 特有的PRAGMA/sqlite_master/ 表重建在 PostgreSQL 上不可用迁移开头vendor ! sqlite直接返回第 68-69 行最后统一由_pg_sync_schema通过 archivebox/misc/db.py 的rebuild_models_from_migration_state()按迁移状态重同步空表。5.2 core/migrations/0025AddField RunPython 数据复制0025 是数据不丢失的第二半程archivebox/core/migrations/0025_alter_archiveresult_options_alter_snapshot_options_and_more.py通过常规AddField新增plugin、output_str、hook_name、output_files、output_json、output_size、output_mimetypes、config、retry_at等新列第 107-167 行关键AddField之后紧接RunPython(copy_old_fields_to_new)第 270-273 行执行extractor → plugin、output → output_str的COALESCE复制第 26-32 行并用start_ts/end_ts回填缺失的created_at/modified_at第 37-45 行复制完成后才RemoveField删除旧列extractor、output第 275-282 行Snapshot 的config、current_step、depth、notes、parent_snapshot、status等新字段因 0023 已建好真实列这里用SeparateDatabaseAndState的state-onlyAddField声明第 175-240 行避免 SQLite 以 0023 之前的状态重建表、用默认值覆盖已迁移行末尾同样以_pg_sync_schema收尾第 365 行。5.3 crawls/migrations/0002_upgrade_from_0_8_6.pyCrawl 表升级v0.8.6 的crawls_crawl通过seed_id外键引用独立的seeds_seed表v0.9.0 将其扁平化为urls文本字段archivebox/crawls/migrations/0002_upgrade_from_0_8_6.py先探测列名确认是 v0.8.6 schema有seed_id且无urls第 85-86 行才执行升级天然支持条件化跳过用LEFT JOIN seeds_seed把seed.uri灌入新表的urls列第 130-137 行对 UUID 类外键统一执行REPLACE(id, -, )去连字符归一化第 31-62 行包括crawls_crawlschedule.template_id、persona_id、schedule_id以及core_snapshot.crawl_id新建表后重建索引第 143-147 行。六、生成迁移 vs 应用迁移两个目录两种命令原文档强调一个极易犯错的点makemigrations 与应用迁移必须在不同目录执行。生成迁移创建新迁移——永远在 archivebox/ 包目录下执行cd archivebox/ ./manage.py makemigrations ./manage.py makemigrations --check # 验证没有未反映到迁移的状态变化archivebox/manage.py 第 10 行明确白名单了makemigrations、migrate、startapp、squashmigrations、generate_stubs、test六个开发命令开发者跑其他命令时会收到提示改用archivebox init/archivebox server等 CLI。这从工具层面强制了不要从数据目录直接跑 Django 命令的纪律。应用迁移测试迁移——永远在数据目录内用archivebox init# 错误示范 cd /some/data/dir ../path/to/archivebox/manage.py migrate # 正确做法 DATA_DIR/some/data/dir python -m archivebox init原因archivebox init会建立数据目录结构、在正确的DATA_DIR上下文中执行迁移、创建必要文件并校验安装。七、十大常见坑Gotchas1. ❌ 不要在 0023 的 SQL 里创建新字段0023 重建表必须沿用旧字段名extractor、output若提前建plugin、output_str0025 的AddField会用默认值覆盖已复制数据。仓库 0023 第 111-112 行正是旧字段名写法。2. ❌ 不要在 0023 里复制数据到新字段字段复制应放在 0025 的AddField之后的RunPython中否则同样会被默认值覆盖。仓库copy_old_fields_to_new严格遵循此顺序。3. ❌ 不要用空表判断全新安装全新安装会跑 0001-0022 建出空的老 schema 表0023 必须照常重建表结构。正确做法是探测列名而非行数。0023 先查sqlite_master确认表存在行数为 0 时跳过复制循环但仍执行表重建与断言archivebox/core/migrations/0023_upgrade_to_0_9_0.py 第 76-93 行。4. ❌ 不要从数据目录生成迁移makemigrations 必须在 archivebox/ 包目录执行理由见上一节。5. ❌ 不要用 WHERE 子句跳过不存在的列-- 错误即使 WHERE 为假SQLite 仍会解析 uuid 列报 no such column INSERT INTO new_table SELECT uuid FROM old_table WHERE EXISTS (SELECT 1 FROM pragma_table_info(old_table) WHERE nameuuid);正确做法是在 Python 侧探测列名后选择对应 SQLif uuid in get_table_columns(old_table): cursor.execute(INSERT INTO new_table SELECT uuid FROM old_table) else: cursor.execute(INSERT INTO new_table SELECT abid as uuid FROM old_table)6. ❌ 不要混用 UUID 与 INTEGER 的 Tag IDv0.8.6rc0 的Tag.id是 UUIDv0.9.0 需要 INTEGER。转换必须三步走建旧 UUID → 新 INTEGER 映射 → 重写core_tag→ 重写core_snapshot_tags。0023 PART 3 已实现并带copied_snapshot_tags ! len(snapshot_tags)的防丢断言。7. ❌ 不要忘记 SeparateDatabaseAndState手工改了真实库必须用state_operations告诉 Django 最终状态否则makemigrations --check永远报未反映的变更migrations.SeparateDatabaseAndState( database_operations[ migrations.RunPython(my_sql_function), ], state_operations[ migrations.RemoveField(archiveresult, extractor), migrations.RemoveField(archiveresult, output), ], )0023、0025 都大量使用该模式。8. ✅ 要打印调试信息print(fMigrating ArchiveResult from v0.7.2 schema...) print(fDEBUG: has_uuid{has_uuid}, has_abid{has_abid}, row_count{row_count})0023 中保留了类似输出如Rebuilding core tables from 0.8.x abid schema、copying 44 ArchiveResults...便于判断走了哪条迁移分支。注意正式发布前应清理调试 print。9. ✅ 要测全部三个场景全新安装、v0.7.2 升级文档记录为 12 snapshots / 44 archiveresults / 2 tags、v0.8.6rc0 升级14 snapshots / 多个 UUID Tag。仓库中 archivebox/tests/test_migrations_fresh.py、archivebox/tests/test_migrations_07_to_09.py、archivebox/tests/test_migrations_08_to_09.py 已覆盖。10. ✅ 要验证没有未反映的迁移cd archivebox/ ./manage.py makemigrations --check # 期望输出No changes detected八、字段映射参考表下表继承自原文档 Reference 章节并补充了当前实现细节旧字段v0.7.2 / v0.8.6新字段v0.9.0说明extractorplugin改名0025 中COALESCE复制outputoutput_str改名0025 中COALESCE复制addedbookmarked_at改名同时用于推导created_atupdatedmodified_at改名0023 中updated同时映射为downloaded_atabiduuid仅 v0.8.6字段改名UUID 生成见 archivebox/uuid_compat.pyTag.id (UUID)Tag.id (INTEGER)仅 v0.8.6类型转换并重写关联表seed_idurlsCrawl 表仅 v0.8.6LEFT JOIN seeds_seed取值persona(VARCHAR)persona_id(UUID FK)Crawl 表仅 v0.8.6status如successstatussucceeded枚举归一化见normalize_status()无cmd→machine_process.cmd0027 将 ArchiveResult 的命令元数据迁入 Process 记录九、当前状态与修复清单原文档记录截至 2025-01-01 的状态三个场景迁移均能跑通但存在数据丢失。对照当前仓库源码原文档列出的修复项大多已落地0023 的 CREATE TABLE 已改用旧字段名extractor、output、added、updated语义并新增assert_rebuild_row_count防丢断言0025 已在所有AddField之后加入RunPython(copy_old_fields_to_new)复制extractor→plugin、output→output_strSnapshot 时间戳转换、Tag UUID→INTEGER 转换、core_snapshot_tags重写均已在 0023 PART 2/PART 3 实现crawls/0002 保持基于 schema 探测的条件升级策略并补充了 UUID 去连字符归一化。因此将本文视为迁移设计蓝图 当前实现对照来阅读表格、五步法、十大坑依然是你评估任何新 schema 变更迁移质量的通用清单。若你基于旧版数据目录做升级实验仍建议按第三节的校验 SQL 逐项核对。十、测试检查清单全新安装创建正确 schema全新安装有 0 snapshots、0 archiveresultsv0.7.2 迁移保留全部 snapshots仓库测试见 archivebox/tests/test_migrations_07_to_09.pyv0.7.2 迁移保留全部 archiveresults 与 tagsv0.7.2 迁移完成extractor→plugin、output→output_str抽查前 5 行v0.7.2 迁移完成added→bookmarked_at、updated→modified_at对比时间戳v0.8.6 迁移保留全部 snapshots 与 crawl 关联含无 crawl 的快照被 0024 分配默认 crawlv0.8.6 迁移将 Tag ID 从 UUID 转为 INTEGER 且保留core_snapshot_tags关系v0.8.6 迁移将abid转为uuid字段每条 ArchiveResult 迁移后关联一条machine_process记录见test_migration_creates_process_records./manage.py makemigrations --check无未反映变更所有迁移无报错archivebox status显示正确的 snapshot 统计结语ArchiveBox v0.9.0 的迁移路径是SQL 保数据、Django 管状态、测试守底线三者配合的典型案例0023 用最小手工 SQL 完成表重建与数据搬运0025 在AddField之后用RunPython完成新旧字段映射SeparateDatabaseAndState保证 Django 状态与真实库不脱节而 archivebox/tests 下按 schema 版本构造的 fixture 与断言则让升级不丢数据从口号变成可重复验证的工程事实。理解这条路径不仅能安全完成 ArchiveBox 的旧版升级也能为任何 Django 项目的大版本字段重构提供可复用的方法论。【免费下载链接】ArchiveBox Open source self-hosted web archiving. Takes URLs/browser history/bookmarks/Pocket/Pinboard/etc., saves HTML, JS, PDFs, media, and more...项目地址: https://gitcode.com/gh_mirrors/ar/ArchiveBox创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表