Help us learn about your current experience with the documentation. Take the survey.

文件导出项目迁移故障排除

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated

如果您在使用文件导出迁移项目时遇到问题,请参考以下可能的解决方案。

故障排除命令

使用 JID 查找导入状态和更多日志信息,使用 Rails 控制台

Project.find_by_full_path('group/project').import_state.slice(:jid, :status, :last_error)
> {"jid"=>"414dec93f941a593ea1a6894", "status"=>"finished", "last_error"=>nil}
# 日志
grep JID /var/log/gitlab/sidekiq/current
grep "Import/Export error" /var/log/gitlab/sidekiq/current
grep "Import/Export backtrace" /var/log/gitlab/sidekiq/current
tail /var/log/gitlab/gitlab-rails/importer.log

项目因不匹配而导入失败

如果导出项目和项目导入之间的实例运行器启用状态不匹配,项目将导入失败。 请查看问题 276930,并执行以下任一操作:

  • 确保源项目和目标项目都启用了实例运行器。
  • 在导入项目时,禁用父组的实例运行器。

导入项目中缺少用户

如果用户没有随导入项目一起导入,请查看保留用户贡献的要求。

用户缺失的一个常见原因是用户没有配置公开邮箱设置。 要解决此问题,请要求用户使用 GitLab UI 配置此设置。

如果用户太多,手动配置不可行, 您可以使用 Rails 控制台 将所有用户配置文件设置为使用公开邮箱地址:

User.where("public_email IS NULL OR public_email = '' ").find_each do |u|
  next if u.bot?

  puts "将 #{u.username} 当前为空的公开邮箱设置为 #{u.email}…"
  u.public_email = u.email
  u.save!
end

大型仓库的导入变通方法

最大导入大小限制 可能导致导入失败。如果无法更改导入限制,您可以 尝试此处列出的变通方法之一。

变通方法选项 1

可以使用以下本地工作流程临时 减少仓库大小,以便再次尝试导入:

  1. 从导出文件创建一个临时工作目录:

    EXPORT=<filename-without-extension>
    
    mkdir "$EXPORT"
    tar -xf "$EXPORT".tar.gz --directory="$EXPORT"/
    cd "$EXPORT"/
    git clone project.bundle
    
    # 防止稍后重新创建可导入文件时产生干扰
    mv project.bundle ../"$EXPORT"-original.bundle
    mv ../"$EXPORT".tar.gz ../"$EXPORT"-original.tar.gz
    
    git switch --create smaller-tmp-main
  2. 为减少仓库大小,在此 smaller-tmp-main 分支上工作: 识别并删除大文件交互式变基和修复 以减少提交次数。

    # 减少 .git/objects/pack/ 文件大小
    cd project
    git reflog expire --expire=now --all
    git gc --prune=now --aggressive
    
    # 准备重新创建可导入文件
    git bundle create ../project.bundle <default-branch-name>
    cd ..
    mv project/ ../"$EXPORT"-project
    cd ..
    
    # 重新创建可导入文件
    tar -czf "$EXPORT"-smaller.tar.gz --directory="$EXPORT"/ .
  3. 将这个新的、较小的文件导入 GitLab。

  4. 在原始仓库的完整克隆中, 使用 git remote set-url origin <new-url> && git push --force --all 来完成导入。

  5. 更新导入仓库的 分支保护规则 和 其默认分支,并 删除临时的 smaller-tmp-main 分支, 以及本地的临时数据。

变通方法选项 2

此变通方法不适用于 LFS 对象。

与其尝试一次性推送所有更改,此变通方法:

  • 将项目导入与 Git 仓库导入分开
  • 逐步将仓库推送到 GitLab
  1. 迁移仓库的本地克隆。在后续步骤中,您将在项目导出之外推送此克隆。

  2. 下载导出文件并删除 project.bundle(包含 Git 仓库):

    tar -czvf new_export.tar.gz --exclude='project.bundle' @old_export.tar.gz
  3. 导入没有 Git 仓库的导出文件。它会要求您确认导入没有仓库。

  4. 将此 bash 脚本保存为文件,并在添加适当的 origin 后运行它。

    #!/bin/sh
    
    # 假设:
    # - GitLab 位置是 "origin"
    # - 默认分支是 "main"
    # - 这将尝试以 500 MB 的块大小推送(将总大小除以 500 MB)。
    #   如果仍然超时,减小此大小以推送更小的块。
    
    git gc
    SIZE=$(git count-objects -v 2> /dev/null | grep size-pack | awk '{print $2}')
    
    # 保持保守,尝试一次推送 2 GB
    # (假设每个提交大小相同 - 这是错误的)
    BATCHES=$(($SIZE / 500000))
    TOTAL_COMMITS=$(git rev-list --count HEAD)
    if (( BATCHES > TOTAL_COMMITS )); then
        BATCHES=$TOTAL_COMMITS
    fi
    
    INCREMENTS=$(( ($TOTAL_COMMITS / $BATCHES) - 1 ))
    
    for (( BATCH=BATCHES; BATCH>=1; BATCH-- ))
    do
      COMMIT_NUM=$(( $BATCH - $INCREMENTS ))
      COMMIT_SHA=$(git log -n $COMMIT_NUM --format=format:%H | tail -1)
      git push -u origin ${COMMIT_SHA}:refs/heads/main
    done
    git push -u origin main
    git push -u origin --all
    git push -u origin --tags

Sidekiq 进程无法导出项目

偶尔 Sidekiq 进程可能无法导出项目,例如 如果在执行过程中被终止。

GitLab.com 用户应联系支持团队解决此问题。

GitLab 自托管管理员可以使用 Rails 控制台绕过 Sidekiq 进程并 手动触发项目导出:

project = Project.find(1)
current_user = User.find_by(username: 'my-user-name')
RequestStore.begin!
ActiveRecord::Base.logger = Logger.new(STDOUT)
params = {}

::Projects::ImportExport::ExportService.new(project, current_user, params).execute(nil)

这会使导出文件可通过 UI 访问,但不会触发用户邮件。 要手动触发项目导出并发送邮件:

project = Project.find(1)
current_user = User.find_by(username: 'my-user-name')
RequestStore.begin!
ActiveRecord::Base.logger = Logger.new(STDOUT)
params = {}

ProjectExportWorker.new.perform(current_user.id, project.id)

手动执行导出步骤

您通常通过Web 界面API导出项目。 使用这些方法导出有时可能会失败,且没有提供足够的信息进行故障排除。 在这些情况下,打开 Rails 控制台会话并循环遍历 所有定义的导出器。 单独执行每一行,而不是一次性粘贴整个代码块,这样您可以看到每个命令返回的任何错误。

# 用户需要具有导出权限
u = User.find_by_username('someuser')
p = Project.find_by_full_path('some/project')
e = Projects::ImportExport::ExportService.new(p,u)

e.send(:version_saver).send(:save)
e.send(:repo_saver).send(:save)
e.send(:avatar_saver).send(:save)
e.send(:project_tree_saver).send(:save)
e.send(:uploads_saver).send(:save)
e.send(:wiki_repo_saver).send(:save)
e.send(:lfs_saver).send(:save)
e.send(:snippets_repo_saver).send(:save)
e.send(:design_repo_saver).send(:save)
## 继续使用 `e.send(:exporter_name).send(:save)` 遍历导出器列表

# 以下行应显示类似 /var/opt/gitlab/gitlab-rails/shared/tmp/gitlab_exports/@hashed/49/94/4994.... 的导出路径
s = Gitlab::ImportExport::Saver.new(exportable: p, shared: p.import_export_shared, user: u)

# 在 GitLab 17.0 之前,不支持 `user` 参数。如果遇到上述错误或不确定是否要提供 `user`
# 参数,请使用以下检查:
Gitlab::ImportExport::Saver.instance_method(:initialize).parameters.include?([:keyreq, :user])
# 如果上述检查返回 false,请省略 user 参数:
s = Gitlab::ImportExport::Saver.new(exportable: p, shared: p.import_export_shared)

# 尝试上传:
s.send(:compress_and_save)
s.send(:save_upload)

项目成功上传后,导出的项目位于 /var/opt/gitlab/gitlab-rails/uploads/-/system/import_export_upload/export_file/ 中的 .tar.gz 文件中。

使用组访问令牌时通过 REST API 导入失败

组访问令牌 不适用于项目或组导入操作。当组访问令牌发起导入时, 导入失败并显示以下消息:

Error adding importer user to Project members.
Validation failed: User project bots cannot be added to other groups / projects

要使用导入 REST API, 请传递常规用户帐户凭据,如个人访问令牌

错误:PG::QueryCanceled: ERROR: canceling statement due to statement timeout

某些迁移可能会因以下错误而超时:PG::QueryCanceled: ERROR: canceling statement due to statement timeout。 避免此问题的一种方法是减少迁移批处理大小。这使迁移不太可能超时, 但会使迁移变慢。

要减少批处理大小,您必须启用一个功能标志。有关更多信息,请参阅 问题 456948

错误:command exited with error code 15 and Unable to save [FILTERED] into [FILTERED]

当您使用文件导出迁移项目时,可能会在日志中收到以下错误:

command exited with error code 15 and Unable to save [FILTERED] into [FILTERED]

当 Sidekiq 收到 SIGTERM 时,此错误会在导出或导入期间发生,通常在执行 tar 命令时。

在 GitLab.com 和 GitLab Dedicated 等 Kubernetes 环境中,操作系统会因内存或磁盘不足、 代码部署或实例升级而触发 SIGTERM 信号。 管理员应调查 Kubernetes 终止实例的原因以确定根本原因。

在非 Kubernetes 环境中,如果在执行 tar 命令时实例被终止,可能会发生此错误。 但是,此错误不是由于磁盘不足引起的,因此内存不足是最可能的原因。

如果您收到此错误:

  • 当您导出文件时,GitLab 会重试导出,直到达到最大重试次数并将导出标记为失败。
  • 当您导入文件时,您必须自己重试导入。GitLab 不会自动重试导入。

性能问题故障排除

阅读以下导入/导出相关的当前性能问题。

OOM 错误

内存不足(OOM)错误通常由Sidekiq 内存杀手引起:

SIDEKIQ_MEMORY_KILLER_MAX_RSS = 2000000
SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS = 3000000
SIDEKIQ_MEMORY_KILLER_GRACE_TIME = 900

导入状态为 started,以及以下 Sidekiq 日志表示存在内存问题:

WARN: Work still in progress <struct with JID>

超时错误

Gitlab::Import::StuckProjectImportJobsWorker 将进程标记为失败时会发生超时错误:

module Gitlab
  module Import
    class StuckProjectImportJobsWorker
      include Gitlab::Import::StuckImportJob
      # ...
    end
  end
end

module Gitlab
  module Import
    module StuckImportJob
      # ...
      IMPORT_JOBS_EXPIRATION = 15.hours.to_i
      # ...
      def perform
        stuck_imports_without_jid_count = mark_imports_without_jid_as_failed!
        stuck_imports_with_jid_count = mark_imports_with_jid_as_failed!

        track_metrics(stuck_imports_with_jid_count, stuck_imports_without_jid_count)
      end
      # ...
    end
  end
end
Marked stuck import jobs as failed. JIDs: xyz
  +-----------+    +-----------------------------------+
  |Export Job |--->| 对所有项目模型调用 ActiveRecord   |
  +-----------+    | `as_json` 和 `to_json`            |
                   +-----------------------------------+

  +-----------+    +-----------------------------------+
  |Import Job |--->| 将所有 JSON 加载到内存中,然后    |
  +-----------+    | 分批插入数据库                    |
                   +-----------------------------------+

问题和解决方案

慢速 JSON 从数据库加载/转储模型:

  • 拆分工作器 |
  • 批量导出
  • 优化 SQL
  • 远离 ActiveRecord 回调(困难)

高内存使用(另请参阅一些分析):

  • DB 提交最佳实践,使用更少内存
  • Netflix Fast JSON API 可能有所帮助
  • 批量读写磁盘和任何 SQL