mcpField
A
axiom-core-data-diag

axiom-core-data-diag

github.com/charleswiltgen开发1.2k169 次安装今天更新
代码仓库

关于 axiom-core-data-diag

面向使用 Core Data 的开发者的诊断与迁移指南,涵盖线程约束、性能问题和数据模型变更。它根据故障类型引导排查,并区分轻量迁移、自定义迁移及异步数据访问方案。

主要功能

  1. 01识别模型版本不匹配
  2. 02选择轻量或自定义迁移
  3. 03处理上下文线程约束
  4. 04使用异步方式访问数据
  5. 05预取关系并设置批量大小
  6. 06在 SwiftData 与 Core Data 间权衡

适用场景

  1. 01发布后因模型变更发生崩溃
  2. 02后台任务触发线程访问错误
  3. 03关系查询出现 N+1 性能问题
  4. 04需要将旧数据转换为新模型

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-core-data-diag

安装后在 Claude Code 中输入/axiom-core-data-diag确认已加载。

详细介绍

核心数据诊断与迁移

概述

核心数据问题会表现为生产环境崩溃(由架构不匹配导致)、莫名其妙的并发错误、负载下性能下降,以及不安全迁移造成的数据损坏。核心原则 85% 的核心数据问题源于对线程约束、架构迁移要求和关系查询模式的误解,而非核心数据本身的缺陷。

危险信号 — 怀疑是核心数据问题

如果看到以下任意情况,请怀疑是对核心数据的误解,而非框架故障:

  • 生产环境启动时崩溃:架构变更后出现 “Unresolvable fault”
  • 线程约束错误:“Accessing NSManagedObject on a different thread”
  • 添加 User→Posts 关系后,应用突然变慢
  • SwiftData 应用需要复杂功能;正在考虑同时使用核心数据
  • 架构迁移在模拟器中正常运行,但在生产环境崩溃
  • ❌ 禁止“核心数据坏了,我们需要换一种数据库”
    • 核心数据已在生产应用中处理数万亿条记录
    • 架构不匹配和线程错误始终是开发者代码的问题,而非框架问题
    • 不要为问题找借口——请进行诊断

关键区别 模拟器会在每次重新构建时删除数据库,从而掩盖架构不匹配问题。真实设备会保留持久化数据库,并在架构不匹配时立即崩溃。必须:发布前在真实设备上使用真实数据测试迁移。

必须执行的首要步骤

务必先运行以下步骤(再更改代码之前):

// 1. Identify the crash/issue type
// Screenshot the crash message and note:
//   - "Unresolvable fault" = schema mismatch
//   - "different thread" = thread-confinement
//   - Slow performance = N+1 queries or fetch size issues
//   - Data corruption = unsafe migration
// Record: "Crash type: [exact message]"

// 2. Check if it's schema mismatch
// Compare these:
let coordinator = persistentStoreCoordinator
let model = coordinator.managedObjectModel
let store = coordinator.persistentStores.first

// Get actual store schema version:
do {
    let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
        ofType: NSSQLiteStoreType,
        at: storeURL,
        options: nil
    )
    print("Store version identifier: \(metadata[NSStoreModelVersionIdentifiersKey] ?? "unknown")")

    // Get app's current model version:
    print("App model version: \(model.versionIdentifiers)")

    // If different = schema mismatch
} catch {
    print("Schema check error: \(error)")
}
// Record: "Store version vs. app model: match or mismatch?"

// 3. Check thread-confinement for concurrency errors
// For any NSManagedObject access:
print("Main thread? \(Thread.isMainThread)")
print("Context concurrency type: \(context.concurrencyType.rawValue)")
print("Accessing from: \(Thread.current)")
// Record: "Thread mismatch? Yes/no"

// 4. Profile relationship access for N+1 problems
// In Xcode, run with arguments:
// -com.apple.CoreData.SQLDebug 1
// Check Console for SQL queries:
//   SELECT * FROM USERS;  (1 query)
//   SELECT * FROM POSTS WHERE user_id = 1;  (1 query per user = N+1!)
// Record: "N+1 found? Yes/no, how many extra queries"

// 5. Check SwiftData vs. Core Data confusion
if #available(iOS 17.0, *) {
    // If using SwiftData @Model + Core Data simultaneously:
    // Error: "Store is locked" or "EXC_BAD_ACCESS"
    // = trying to access same database from both layers
    print("Using both SwiftData and Core Data on same store?")
}
// Record: "Mixing SwiftData + Core Data? Yes/no"
这些结果说明什么
  • 架构不匹配 → 继续查看模式 1(轻量级迁移决策)
  • 线程约束错误 → 继续查看模式 2(async/await 并发)
  • N+1 查询 → 继续查看模式 3(关系预取)
  • SwiftData + 核心数据冲突 → 继续查看模式 4(桥接)
  • 迁移后变慢 → 继续查看模式 5(测试安全性)
必须进行的判断

在更改任何代码之前,先确定以下情况中的一种:

  1. 如果崩溃信息是 “Unresolvable fault”,且存储版本与模型版本不同 → 架构不匹配(并非用户错误)
  2. 如果崩溃信息提到 “different thread”,且你正在使用 DispatchQueue → 线程约束(并非线程安全设计)
  3. 如果访问关系时性能下降 → N+1 查询(检查 SQL 日志)
  4. 如果 SwiftData 和核心数据代码同时存在 → 数据层冲突(架构问题)
  5. 如果迁移测试通过但生产环境失败 → 真实数据中的边缘情况(测试缺口)
如果诊断结果相互矛盾或不清楚
  • 停止。暂时不要继续查看模式
  • 在每次访问 NSManagedObject 时添加 print 语句(检查线程)
  • 添加 -com.apple.CoreData.SQLDebug 1 并统计 SQL 查询次数
  • 建立基准:实际发生了什么,与原先的假设有何不同

决策树

Core Data problem suspected?
├─ Crash: "Unresolvable fault"?
│  └─ YES → Schema mismatch (store ≠ app model)
│     ├─ Add new required field? → Pattern 1a (lightweight migration)
│     ├─ Remove field, rename, or change type? → Pattern 1b (heavy migration)
│     └─ Don't know how to fix? → Pattern 1c (testing safety)
│
├─ Crash: "different thread"?
│  └─ YES → Thread-confinement violated
│     ├─ Using DispatchQueue for background work? → Pattern 2a (async context)
│     ├─ Mixing Core Data with async/await? → Pattern 2b (structured concurrency)
│     └─ SwiftUI @FetchRequest causing issues? → Pattern 2c (@FetchRequest safety)
│
├─ Performance: App became slow?
│  └─ YES → Likely N+1 queries
│     ├─ Accessing user.posts in loop? → Pattern 3a (prefetching)
│     ├─ Large result set? → Pattern 3b (batch sizing)
│     └─ Just added relationships? → Pattern 3c (relationship tuning)
│
├─ Using both SwiftData and Core Data?
│  └─ YES → Data layer conflict
│     ├─ Need Core Data features SwiftData lacks? → Pattern 4a (drop to Core Data)
│     ├─ Already committed to SwiftData? → Pattern 4b (stay in SwiftData)
│     └─ Unsure which to use? → Pattern 4c (decision framework)
│
└─ Migration works locally but crashes in production?
   └─ YES → Testing gap
      ├─ Didn't test with real data? → Pattern 5a (production testing)
      ├─ Schema change affects large dataset? → Pattern 5b (migration safety)
      └─ Need verification before shipping? → Pattern 5c (pre-deployment checklist)

常见模式

模式选择规则(必须遵守)
一次应用一种模式,从诊断开始
  1. 始终从必须执行的首要步骤开始 — 确定实际问题
  2. 运行决策树 — 缩小范围,找到具体模式
  3. 应用一种模式 — 不要组合多种模式
  4. 在真实设备上测试 — 模拟器会掩盖问题
  5. 通过迁移测试验证 — 部署前完成
禁止事项
  • ❌ 未经诊断就更改代码
  • ❌ 跳过真实设备测试
  • ❌ 将模拟器运行成功作为迁移安全的证明
  • ❌ 混用多种迁移模式
  • ❌ 未进行部署前验证就部署迁移

模式 1a:轻量级迁移(简单架构变更)

原则 如果操作正确,核心数据可以在不丢失数据的情况下自动迁移简单架构(增量式变更)。

✅ 安全的轻量级迁移
  • 添加新的可选字段:@NSManaged var nickname: String?
  • 添加新的必需字段并提供默认值:创建属性时设置默认值
  • 重命名实体或属性:使用映射模型进行自动映射
  • 删除未使用的字段:直接从模型中删除(数据仍保留在磁盘上,但会被忽略)
❌ 错误做法(会导致生产环境崩溃)
// BAD: Adding required field without migration
@NSManaged var userID: String  // Required, no default

// BAD: Assuming simulator = production
// Works in simulator (deletes DB), crashes on real device

// BAD: Modifying field type
@NSManaged var createdAt: Date  // Was String, now Date
// Core Data can't automatically convert
✅ 正确(安全的轻量级迁移)
// 1. In Xcode: Editor → Add Model Version
// Creates new .xcdatamodel version file

// 2. In new version, add required field WITH default:
@NSManaged var userID: String = UUID().uuidString

// 3. Mark as current model version:
// File Inspector → Versioned Core Data Model
// Check "Current Model Version"

// 4. Test:
// Simulate old version: delete app, copy old database, run with new code
// Real app loads → migration succeeded

// 5. Deploy when confident
适用情况
  • 添加可选字段(始终安全)
  • 添加带默认值的必需字段
  • 删除字段
  • 使用映射模型重命名实体/属性
不适用情况(不要尝试轻量级迁移)
  • 更改字段类型(String → Int)
  • 将可选字段改为必需字段(数据中存在 null,无法转换)
  • 复杂的关系更改
  • 需要自定义数据转换

耗时 轻量级迁移设置需 5-10 分钟


模式 1b:重型迁移(复杂架构更改)

原则 轻量级迁移无法满足需求时,使用 NSEntityMigrationPolicy 实现自定义转换逻辑。

适用情况
  • 更改字段类型(String → Date)
  • 将可选字段改为必需字段(需要填充现有的 null 值)
  • 复杂的关系重构
  • 自定义数据转换(例如,将“firstName lastName”拆分为单独的字段)
示例:将 String 日期转换为 Date 对象
// 1. Create mapping model
// File → New → Mapping Model
// Source: old version, Destination: new version

// 2. Create custom migration policy
class DateMigrationPolicy: NSEntityMigrationPolicy {
    override func createDestinationInstances(
        forSource sInstance: NSManagedObject,
        in mapping: NSEntityMapping,
        manager: NSMigrationManager
    ) throws {
        let destination = NSEntityDescription.insertNewObject(
            forEntityName: mapping.destinationEntityName ?? "",
            into: manager.destinationContext
        )

        for key in sInstance.entity.attributesByName.keys {
            destination.setValue(sInstance.value(forKey: key), forKey: key)
        }

        // Custom transformation: String → Date
        if let dateString = sInstance.value(forKey: "createdAt") as? String,
           let date = ISO8601DateFormatter().date(from: dateString) {
            destination.setValue(date, forKey: "createdAt")
        } else {
            destination.setValue(Date(), forKey: "createdAt")
        }

        manager.associate(source: sInstance, withDestinationInstance: destination, for: mapping)
    }
}

// 3. In mapping model Inspector:
// Set Custom Policy Class: DateMigrationPolicy

// 4. Test extensively with real data before shipping
关键安全规则
  • 测试迁移前,始终备份数据库
  • 在生产数据的副本上测试迁移
  • 迁移后验证数据完整性(抽样检查)
  • 如果迁移失败,制定回滚计划

耗时 每次迁移需 30-60 分钟,另加测试时间


模式 2a:用于后台获取的异步上下文

原则 Core Data 对象受线程限制。应在后台线程获取对象,再转换为轻量表示形式供主线程使用。

❌ 错误(会因线程限制而崩溃)
DispatchQueue.global().async {
    let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
    let results = try! context.fetch(request)

    DispatchQueue.main.async {
        self.objects = results  // ❌ CRASH: objects faulted on background thread
    }
}
✅ 正确(使用私有队列上下文执行后台工作)
// Create background context
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
backgroundContext.parent = viewContext

// Fetch on background thread
backgroundContext.perform {
    do {
        let results = try backgroundContext.fetch(userRequest)

        // Convert to lightweight representation BEFORE main thread
        let userIDs = results.map { $0.id }  // Just the IDs, not full objects

        DispatchQueue.main.async {
            // On main thread, fetch full objects from main context
            let mainResults = try self.viewContext.fetch(request)
            self.objects = mainResults
        }
    } catch {
        print("Fetch error: \(error)")
    }
}
原因
  • 后台上下文在后台线程获取数据(安全)
  • 将重量级对象转换为轻量值(可安全传递到主线程)
  • 主上下文在主线程获取数据(安全)
  • 不会有受线程限制的对象跨线程边界传递

耗时 重构需 10 分钟


模式 2b:结构化并发(使用 Core Data 的 async/await)

原则 使用 NSPersistentContainer 或 NSManagedObjectContext 的异步方法,以兼容 Swift 并发。

✅ 正确(iOS 13+ 异步 API)
// iOS 13+: Use async perform
let users = try await viewContext.perform {
    try viewContext.fetch(userRequest)
}
// Executes fetch on correct thread, returns to caller

// iOS 17+: Use Swift Concurrency async/await directly
let users = try await container.mainContext.fetch(userRequest)

// For background work:
let backgroundUsers = try await backgroundContext.perform {
    try backgroundContext.fetch(userRequest)
}
// Fetch happens on background queue, thread-safe
❌ 错误(混用 Swift 并发与 DispatchQueue)
async {
    DispatchQueue.global().async {
        try context.fetch(request)  // ❌ Wrong thread!
    }
}

耗时 从 DispatchQueue 转换为 async/await 需 5 分钟


模式 3a:关系预取(避免 N+1)

原则 告知 Core Data 提前获取关系,而不是在访问时延迟加载。

❌ 错误(N+1 查询模式)
let users = try context.fetch(userRequest)

for user in users {
    let posts = user.posts  // ❌ Triggers fetch for EACH user!
    // 1 fetch for users + N fetches for relationships = N+1 total
}
✅ 正确(预取关系)
var request = NSFetchRequest<User>(entityName: "User")

// Tell Core Data to fetch relationships eagerly
request.relationshipKeyPathsForPrefetching = ["posts", "comments"]
// Now relationships are fetched in a single query per relationship

let users = try context.fetch(request)

for user in users {
    let posts = user.posts  // ✅ INSTANT: Already fetched
    // Total: 1 fetch for users + 1 fetch for all posts = 2 queries
}
其他优化模式
// Batch size: fetch in chunks for large result sets
request.fetchBatchSize = 100

// Faulting behavior: convert faults to lightweight snapshots
request.returnsObjectsAsFaults = false  // Keep objects in memory
// Use carefully—can cause memory pressure with large results

// Distinct: remove duplicates from relationship fetches
request.returnsDistinctResults = true

耗时 添加预取设置需 2-5 分钟


模式 3b:获取批次大小

原则 对于大型结果集,分批获取以管理内存。

示例:滚动浏览 100,000 个用户
var request = NSFetchRequest<User>(entityName: "User")
request.fetchBatchSize = 100  // Fetch 100 at a time

// Set sort descriptor for stable pagination
request.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]

let results = try context.fetch(request)
// Memory footprint: ~100 users at a time, not all 100,000

for user in results {
    // Accessing user 0-99: in memory
    // Accessing user 100: batch refetch (user 100-199)
    // Auto-pagination, minimal memory usage
}

耗时 调整批次大小需 3 分钟


模式 4a:SwiftData 不具备的 Core Data 功能

场景 你选择了 SwiftData,但需要它不具备的功能。

SwiftData 不具备
  • 复杂迁移(仅支持自动迁移)
  • 自定义验证(保存前)
  • 关系删除规则(级联、拒绝、置空)
  • 直接 SQL 查询
  • 高级预取
  • 故障对象控制
何时从 SwiftData 切换到 Core Data
  • 需要自定义迁移
  • 需要验证逻辑
  • 需要复杂的关系规则
  • 需要使用原始 SQL 提升性能
  • 需要容错模式
✅ 正确(必要时采用混合方案)
// Keep SwiftData for simple entities
@Model final class Note {
    var id: String
    var title: String
}

// Drop to Core Data for complex operations
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
backgroundContext.parent = container.viewContext

// Fetch with Core Data, convert to SwiftData models
let results = try backgroundContext.perform {
    try backgroundContext.fetch(coreDataRequest)
}

重要 不要同时通过 SwiftData 和 Core Data 访问同一个实体。只能选择其中一种,不能两者兼用。

耗时 创建桥接层需 30-60 分钟


模式 4b:继续使用 SwiftData(新项目推荐)

场景 你正在使用 SwiftData,并在考虑是否需要 Core Data。

对于现代应用,SwiftData 提供了 Core Data 80% 的功能
  • 类型安全的模型(@Model)
  • 响应式查询(@Query)
  • CloudKit 同步(内置)
  • 自动迁移(适用于简单更改)
  • 完善的 async/await 集成
SwiftData 足够的情况
  • 简单的架构(用户、笔记、待办事项)
  • 关系复杂度最低
  • 需要 CloudKit 同步
  • 可以接受 iOS 17+ 的要求
  • 无需维护旧版 Core Data 代码
决策:如果以下 3 项或更多回答为“是”,则继续使用 SwiftData
  • ✅ 仅支持 iOS 17+(无需支持 iOS 16)
  • ✅ 关系简单(一对多,而非多对多)
  • ✅ 标准迁移(添加字段、删除字段)
  • ✅ CloudKit 同步有益
  • ✅ 类型安全很重要
决策:以下情况改用 Core Data
  • ❌ 需要支持 iOS 16(SwiftData 仅支持 iOS 17+)
  • ❌ 关系规则复杂(级联规则、约束)
  • ❌ 需要自定义迁移
  • ❌ 需要原始 SQL 以提升性能
  • ❌ 已有 Core Data 代码库

耗时 0 分钟(仅做决策)


模式 5a:迁移前的安全生产环境测试

原则 未经真实数据测试,绝不部署迁移。

部署前必做检查清单
// Step 1: Export production database
// From running app in simulator or real device:
// ~/Library/Developer/CoreData/[AppName]/
// Copy entire [AppName].sqlite database

// Step 2: Create migration test
@Test func testProductionDataMigration() throws {
    // Copy production database to test location
    let testDB = tempDirectory.appendingPathComponent("test.sqlite")
    try FileManager.default.copyItem(from: prodDatabase, to: testDB)

    // Attempt migration
    var config = ModelConfiguration(url: testDB, isStoredInMemory: false)
    let container = try ModelContainer(for: User.self, configurations: [config])

    // Verify data integrity
    let context = container.mainContext
    let allUsers = try context.fetch(FetchDescriptor<User>())

    // Spot checks: verify specific records migrated correctly
    guard let user1 = allUsers.first(where: { $0.id == "test-id-1" }) else {
        throw MigrationError.missingUser
    }

    // Check derived data is correct
    XCTAssertEqual(user1.name, "Expected Name")
    XCTAssertNotNil(user1.createdAt)

    // Check relationships
    XCTAssertEqual(user1.posts.count, expectedPostCount)
}

// Step 3: Run test against real production data
// Pass ✓ before shipping
安全规则
  • ❌ 绝不要在模拟器中测试迁移(模拟器会删除数据库)
  • ✅ 务必使用真实生产数据的副本进行测试
  • ✅ 务必进行抽查(检查特定记录)
  • ✅ 务必检查关系是否正确加载
  • ✅ 务必记录回滚计划

耗时 创建迁移测试需 15-30 分钟


模式 5c:部署前验证检查清单
发布任何 Core Data 更改前必须检查
  • 是否创建了新的 .xcdatamodel 版本?(而非仅编辑现有版本)
  • 新版本是否包含所需的映射模型?
  • 是否使用真实生产数据测试了迁移?(不是模拟器)
  • 是否验证了 5 条或更多特定记录均已正确迁移?
  • 是否检查了关系是否已加载?
  • 是否在真实设备(支持范围内最旧的设备)上测试?
  • 应用是否能正常启动且不崩溃?(全新安装)
  • 应用能否使用旧数据启动?(迁移路径)
  • 是否记录了回滚计划?(以防生产环境发生故障)
如果任何一项回答为“否”
  • ❌ 请勿发布
  • 返回并修复问题,然后重新测试
  • 一个“否”就意味着存在数据丢失风险

耗时 检查清单需 5 分钟


快速参考表

问题检查修复
“Unresolvable fault”崩溃存储版本与模型版本是否匹配?创建 .xcdatamodel 版本和映射模型
“Different thread”崩溃是否在主线程上执行提取?使用私有队列上下文执行后台工作
应用变慢是否预先提取了关系?添加 relationshipKeyPathsForPrefetching
N+1 查询性能问题检查 -com.apple.CoreData.SQLDebug 1 日志添加预先提取,或转换为轻量级表示形式
SwiftData 需要 Core Data 功能是否需要自定义迁移?使用 Core Data NSEntityMigrationPolicy
不确定选 SwiftData 还是 Core Data是否需要支持 iOS 16?iOS 16 使用 Core Data,iOS 17+ 使用 SwiftData
迁移测试成功,生产环境失败是否使用真实数据进行了测试?使用生产数据库副本创建迁移测试

卡住超过 30 分钟时

如果你已花费超过 30 分钟,Core Data 问题仍未解决:

停下。你可能
  1. 跳过了必做诊断(最常见)
  2. 误判了实际问题
  3. 针对当前症状应用了错误的模式
  4. 尚未在真实设备/真实数据上测试
  5. 遇到了需要自定义 NSEntityMigrationPolicy 的边缘情况
声称“技能不起作用”前必须完成的检查清单
  • 我已运行所有必做的第一步诊断
  • 我已确定问题类型(架构、并发、性能、桥接、测试)
  • 我已检查 Core Data SQL 调试日志(-com.apple.CoreData.SQLDebug 1)
  • 我已使用真实数据在真实设备上测试(不是模拟器)
  • 我已应用决策树中第一个匹配的模式
  • 如果架构有更改,我已创建迁移测试
  • 我已验证至少 3 条特定记录均已正确迁移
  • 我已记录回滚计划
如果所有选项都已勾选,但问题仍未解决
  • 需要自定义的 NSEntityMigrationPolicy(基本模式未涵盖)
  • 耗时:复杂迁移需要 60-90 分钟
  • 询问:“实际需要进行什么数据转换?”并实现自定义策略
耗时说明
  • 模式 1(轻量级迁移):5-10 分钟
  • 模式 1(使用自定义策略的重量级迁移):60-90 分钟
  • 模式 2(并发):5-10 分钟
  • 模式 3(预取):2-5 分钟
  • 模式 4(桥接):30-60 分钟
  • 模式 5(测试):15-30 分钟

常见错误

❌ 只在模拟器中测试迁移

  • 模拟器在重新构建时会删除数据库,从而掩盖架构不匹配问题
  • 修复方法:务必在真机上测试,或使用生产数据库副本测试

❌ 认为默认值可以防止数据丢失

  • 默认值仅适用于新记录,不适用于现有数据
  • 修复方法:对现有数据使用 NSEntityMigrationPolicy

❌ 未转换就跨线程访问 Core Data 对象

  • 对象受线程限制,不能跨越线程边界
  • 修复方法:传递到其他线程前,先转换为轻量级表示形式

❌ 没有意识到访问关系 = 执行数据库查询

  • user.posts 会为每个用户触发一次提取(N+1)
  • 修复方法:使用 relationshipKeyPathsForPrefetching,或先提取 ID

❌ 在同一存储中混用 SwiftData 和 Core Data

  • 两个层无法同时访问同一个数据库
  • 修复方法:选择其中一个层,或采用使用不同实体的混合方案

❌ 未经部署前测试就部署迁移

  • 生产数据中的边缘情况会导致崩溃
  • 修复方法:必须使用真实生产数据进行迁移测试

❌ 自我安慰:“我直接删除数据就行”

  • ❌ 禁止:用户不会接受自己的数据丢失
  • 用户会卸载应用并留下差评
  • 修复方法:投入资源进行安全的迁移测试

生产危机压力:坚持安全迁移模式

问题

在生产危机压力下,你可能会遇到以下要求:

  • “用户一直崩溃——直接删除数据库,从头开始”
  • “迁移耗时太长——跳过测试,直接发布”
  • “我们等不了 2 天来做好迁移——先凑合着实现”
  • “架构不匹配?直接强制创建新存储”

这些听起来像是务实的危机应对措施。**但它们会导致数据丢失,并永久损害用户信任。**你的职责是依据数据安全原则和客户影响进行抗辩,而不是屈服于压力。

危险信号 — 会导致数据丢失的 PM/经理要求

如果在生产危机期间听到以下任何一项,请立即停止并参考此技能:

  • ❌ “删除持久化存储,从头开始”——用户的所有数据都会永久丢失
  • ❌ “未经测试就强制进行轻量级迁移”——生产环境中数据损坏风险很高
  • ❌ “跳过迁移,创建新存储”——放弃现有用户数据
  • ❌ “发布后再修复数据问题”——丢失或损坏的数据无法恢复
  • ❌ “直接发布,支持工单我们之后再处理”——数据丢失会导致用户永久流失
  • ❌ “在模拟器上测试就够了”——模拟器在重新构建时会删除数据库,从而掩盖架构不匹配问题
如何专业地提出异议
第 1 步:量化对客户的影响
"I want to resolve this crash ASAP, but let me show you what deleting the store means:

Current situation:
- 10,000 active users with data
- Average 50 items per user (500,000 total records)
- Users have 1 week to 2 years of accumulated data

If we delete the store:
- 10,000 users lose ALL their data on next app launch
- Uninstall rate: 60-80% (industry standard after data loss)
- App Store reviews: Expect 1-star reviews citing data loss
- Recovery: Impossible - data is gone permanently

Safe alternative:
- Test migration on real device with production data copy (2-4 hours)
- Deploy migration that preserves user data
- Uninstall rate: <5% (standard update churn)"
第 2 步:展示风险

向 PM/经理说明会发生什么:

  1. 从设备备份中复制生产数据库
  2. 执行提议的“快速修复”(删除存储)
  3. 展示:所有用户数据永久丢失
  4. 展示替代方案:保留数据的安全迁移
  5. 时间对比:30 分钟的临时方案 vs. 2-4 小时的安全迁移
参考
  • “用户不会原谅数据丢失”(App Store 评论模式)
  • 在真机上测试迁移可避免 95% 的生产环境崩溃
  • 架构不匹配导致的崩溃会影响 100% 的现有用户
第 3 步:提出折中方案
"I can get us through this crisis while protecting user data:

#### Fast track (4 hours total)
1. Copy production database from TestFlight user (30 min)
2. Write and test migration on real device copy (2 hours)
3. Submit build with tested migration (30 min)
4. Monitor first 100 updates for crashes (1 hour)

#### Fallback if migration fails
- Have "delete store" build ready as Plan B
- Only deploy if migration shows 100% failure rate
- Communicate data loss to users proactively

This approach:
- Tries safe path first (protects user data)
- Has emergency fallback (if migration impossible)
- Honest timeline (4 hours vs. "just delete it" 30 min)"
第 4 步:记录决策

如果你的意见被否决(PM 坚持删除存储):

Slack message to PM + team:

"Production crisis: Schema mismatch causing crashes for existing users.

PM decision: Delete persistent store to resolve immediately.

Impact assessment:
- 10,000 users lose ALL data permanently on next app launch
- Expected uninstall rate: 60-80% based on data loss patterns
- App Store review damage: High risk of 1-star reviews
- Customer support: Expect high volume of data loss complaints
- Recovery: Impossible - deleted data cannot be recovered

Alternative proposed (4-hour safe migration) was declined due to urgency.

I'm flagging this decision proactively so we can:
1. Prepare support team for data loss complaints
2. Draft App Store response to expected negative reviews
3. Consider user communication about data loss before launch"
为什么有效
  • 你没有质疑他们在压力下做出的判断
  • 你量化了对用户的影响(业务后果)
  • 你提供了诚实的时间表和解决方案
  • 你提供了备用选项(不会阻碍进度)
  • 你记录了决策(上线后可保护你)
真实案例:生产环境崩溃(50 万活跃用户)
场景
  • 更新后,生产应用对 100% 的用户崩溃
  • 错误:“用于打开存储区的模型与用于创建存储区的模型不兼容”
  • CTO 说:“删除数据库,并在 2 小时内发布热修复”
  • 50 万活跃用户,每人平均有 6 个月的数据
该怎么做
// ❌ WRONG - Deletes all user data (CTO's request)
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
let storeURL = /* persistent store URL */
try? FileManager.default.removeItem(at: storeURL) // 500K users lose data
try! coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                    configurationName: nil,
                                    at: storeURL,
                                    options: nil)

// ✅ CORRECT - Safe lightweight migration (4-hour timeline)
let options = [
    NSMigratePersistentStoresAutomaticallyOption: true,
    NSInferMappingModelAutomaticallyOption: true
]

do {
    try coordinator.addPersistentStore(ofType: NSSQLiteStoreType,
                                       configurationName: nil,
                                       at: storeURL,
                                       options: options)
    // Migration succeeded - user data preserved
} catch {
    // Migration failed — NOW consider deleting with user communication
    print("Migration error: \(error)")
}
在会议中展示
  1. 导致崩溃的架构版本不匹配
  2. 轻量级迁移可以自动修复
  3. 在生产数据库副本上测试(2 小时)
  4. 时间对比:2 小时(安全)与立即处理(数据丢失)

时间估算 总计 4 小时(2 小时迁移测试,2 小时构建和部署)

结果
  • 诚实的时间表有助于管理预期
  • 安全迁移保留了 50 万用户的数据
  • 卸载率:3%(正常的更新流失率)
  • App Store 评论:没有数据丢失投诉
如果迁移确实无法实现,可采取的替代方案
  • 记录迁移失败的原因
  • 主动告知用户数据丢失
  • 在下一个版本中提供导出功能
何时接受数据丢失(即使你不同意)

有时数据丢失是唯一的选择。满足以下条件时接受:

  • 迁移确实无法实现(已在生产数据副本上尝试)
  • PM/CTO 了解预计会有 60-80% 的卸载率
  • 团队承诺就数据丢失问题与用户沟通
  • 你已记录迁移失败的技术原因
在 Slack 中记录
"Production crisis: Migration failed on production data copy after 4-hour testing.

Technical details:
- Attempted lightweight migration: Failed with [error]
- Attempted heavy migration with mapping model: Failed with [error]
- Root cause: [specific schema incompatibility]

Data loss decision:
- No safe migration path exists
- PM approved delete persistent store approach
- Expected impact: 60-80% uninstall rate (500K → 100-200K users)

Mitigation plan:
- Add data export feature before next schema change
- Communicate data loss to users via in-app message
- Prepare support team for complaints
- Monitor uninstall rates post-launch"

这样可以保护你,并表明你已先尝试所有安全方案。


实际影响

之前 每个 Core Data 问题需要 3-8 小时调试

  • 生产环境崩溃(架构不匹配)
  • 性能逐渐下降(N+1 查询)
  • 后台操作中的线程错误
  • 不安全的迁移导致数据损坏
  • 客户信任受损

之后 通过系统化诊断,只需 30 分钟到 2 小时

  • 借助诊断手段识别问题类型(5 分钟)
  • 应用正确的模式(5-10 分钟)
  • 在真实设备上测试(视情况而定)
  • 满怀信心地部署

关键见解 Core Data 对每种常见问题都有成熟的模式。问题在于,开发者不知道哪种模式适用于自己遇到的症状。


最后更新:2025-11-30
状态:已通过压力场景的 TDD 测试
框架:Core Data(Foundation 框架)
补充:SwiftData 技能(了解其与 Core Data 的关系)

评论

支持 Markdown