此前处理过几次edx数据迁移方面的工作,包括

  1. 更换服务器,整个平台的迁移(迁移前后edx版本相同release-2014-09-17
  2. 部署birch版的edX,将既有数据(来自release-2014-09-17)迁移到birch中
  3. 将平台数据迁移给docker中的edx使用

1,3本质上是数据导出与导入问题,2涉及到新旧版edx数据结构的变化,下边分别说明。

#几点建议

  • 一定记得备份!!
  • 弄清你关心的数据是什么,最坏的情况下,需要手动迁移,明确自己关心的数据,将减少大量工作,以下几类数据可能是你在意的(如有遗漏,欢迎补充):
    • 用户注册表(mysql edxapp库中auth_user表)
    • 用户信息表(mysql edxapp库中auth_userprofile)
    • 用户/课程关系表(mysql edxapp库中student_courseenrollment表)
    • 学生答题记录(mysql edxapp库中courseware_studentmodule)(可能会比较大)
    • 论坛里的帖子和评论(mongo/cs_comments_service_development/contents)
    • 论坛里的用户(mongo/cs_comments_service_development/users)
    • 用户订阅帖子的关系(mongo/cs_comments_service_development/subscriptions)
    • 课程内容,这个当然也在mongo中,不过直接使用studio提供的export和import就好。
  • 如果是整个系统的迁移,除了考虑数据库中的数据之外,还应该考虑静态文件,静态文件基本分布在/edx/var/edxapp/staticfiles

#just do it 分三部分来说:

  • 同版本的edx迁移时的数据迁移(本质是数据导出和导入)
  • edx升级后的数据迁移
  • 将数据迁移给docker中的edx使用(版本相同)

##同版本的edx迁移时的数据迁移(本质是数据导出和导入) 之所以要在同版本中迁移数据,是因为服务器发生了变更,需要迁移整个平台。尝试过打包整个系统的做法(tar备份还原),也试过使用Remastersys做整个系统的镜像,两种方法造成的结果都是数据库罢工。基于dd指令的方法还没用过 :)。以上其实是个运维的问题,我对此并不很熟悉。

所以最终采用的是重新在一个新的平台上部署,之后将数据迁移过去。这样做确实挺烦的,edx的每次国内安装过程都很蛋疼(你懂的),所以才一直想使用docker来跑edx,这样迁移什么的都酸爽极了,更不用说开发。

###mysql

1
2
3
4
5
6
7
8
:::text
#导出,mysqldump -u 用户名 数据库名 > 导出的文件名 ,ansible安装mysql时,没设密码,所以不需要-p
mysqldump -u root edxapp > /path/to/edxapp.sql

#使用scp将导出文件复制到目标服务器上

#在目标服务器上导入
mysql -u root edxapp < /path/to/edxapp.sql

一般来说主要数据都在edxapp里,如果你要备份所有数据的话,使用这条mysqldump -u root --all-databases> alldb.sql

对应的还原mysql -u root -p < alldb.sql

###mongo

1
2
3
4
5
6
7
8
:::text
#导出mongo中所有数据
mongodump -h 127.0.0.1 -o /path/to/mongodb/

#将其用tar打包,scp复制到目标服务器上,解压

#在目标服务器导入数据
mongodump -h 127.0.0.1 -o /path/to/mongodb/

##edx升级后的数据迁移 首先让人想到的是update,但据我所知很少有人能一次性update成功的,这归结于edx的依赖错综复杂。

update究竟干了啥,look here

如果使用update,中途遇到的问题,只好手动处理了

如果是从aspen升级到birch,参考这里

###手动迁移 我当时面对的场景是,在一台新的服务器上部署birch,然后要将release-2014-09-17版上的数据迁移过去。
当时的做法比较简单,因为我们只对文章开头提到几类数据感兴趣,所以对比了这些数据表结构基本没变化后,我直接导出旧有数据,覆盖新的,至于mongo部分也是如此,推荐使用diff做比较。手动的方案虽然繁琐,但足够灵活。
至于定制的代码,都限制在独立的django application里,所以定制代码的迁移也并不麻烦。复制整个application目录便是,之后syncdb/migrate一下。

####mysql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
:::text
#导出数据表:mysqldump -u 用户名 -p 数据库名 表名> 导出的文件名
mysqldump -u root edxapp auth_user> /path/to/auth_user.sql
mysqldump -u root edxapp auth_userprofile> /path/to/auth_userprofile.sql
mysqldump -u root edxapp student_courseenrollment> /path/to/student_courseenrollment.sql

#导入数据表,先备份/删除同名表 
#备份可用上边的方法
mysql -u root
use edxapp
drop table <表名>;
source /path/to/auth_user.sql
source /path/to/auth_userprofile.sql
source /path/to/student_courseenrollment.sql

####mongo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
:::text
#在旧版服务器导出所需的collections。参数含义: -d database_name -c collection_name
mongoexport -d cs_comments_service_development -c contents -o /path/to/contents.dat
mongoexport -d cs_comments_service_development -c users -o /path/to/users.dat
mongoexport -d cs_comments_service_development -c  subscriptions -o /path/to/subscriptions.dat
#modulestore
mongoexport -d edxapp -c  modulestore -o /path/to/modulestore.dat


#使用scp将以上dat文件复制到目标服务器

#在目标服务器上先删除同名collectons,删除前记得备份
mongo
use cs_comments_service_development
db.contents.drop()
db.users.drop()
db.subscriptions.drop()
#导入所需的collections
mongoimport -d cs_comments_service_development -c contents  /path/to/contents.dat
mongoimport -d cs_comments_service_development -c users path/to/users.dat
mongoimport -d cs_comments_service_development -c subscriptions path/to/subscriptions.dat

###自动迁移 后来想到更好的方案
当初在release-2014-09-17上做二次开发时有考虑到之后要升级的问题,所以二次开发的代码除了限制在一个application里之外,还都在wwj_master分支上。
首先在旧版服务器上工作:

  • 拉取所需的远程新版分支到本地: git checkout -b new_branch origin/new_branch
  • 将wwj_master分支衍合(rebase)到新版分支上:git rebase new_branch wwj_master

关于衍合的知识可以看这里
rebase完之后,同步数据库,执行syncdb和migrate。这里有相关命令
如此一来,就完成了edx-platform源码的升级,定制的内容也都在,同时旧版的数据库也和新版一致了。
此后直接将旧版上整套edx-platform源码和数据库备份文件,移到新版服务器中进行替换就行。
很像1中同版本的迁移时的数据迁移的情况了。

##将数据迁移给docker中的edx使用(edx版本相同) 这里其实和同版本的edx迁移时的数据迁移(本质是数据导出和导入)没啥区别,需要注意的是,记得将数据库文件目录挂载出来,使用-v参数,这样数据的安全性得到保证,数据不会因为docker进程的挂掉而丢失。
顺便提一下,把静态文件目录/edx/var/edxapp/staticfiles也挂载出去.
总之,使用docker的核心观念之一是容器无状态。把你认为需要状态的数据都挂载出去

#附录 我在升级release-2014-09-17named-release/birch时,对比过mysql中所有表结构的差异。

##用到的相关指令

1
2
3
4
5
6
:::text
#在旧版服务器上
mysqldump -u root  -d edxapp > /path/to/old_edx.sql
#在新版服务器上
mysqldump -u root  -d edxapp > /path/to/new_edx.sql
vimdiff /path/to/old_edx.sql /path/to/new_edx.sql 

2016.04.07更新

如果从birch迁移到dogwood,那么数据表有以下差异

###mongodb cs_comments_service_development数据库中contents表新版比老版多一个key:context,value:string

cat contents.dat | jq -c ‘. + { “context”: “course” }’ > contents_ok.dat

-c:不改变单行模式

另一个问题是course_id发生了变化你可以使用vi逐个替代

:%s,edX/DemoX/Demo_Course,course-v1:edX+DemoX+Demo_Course,g

课程多的话使用,vi显然效率太低,也可以使用sed和jq来做流式编辑

(思路:1.使用正则首先需要定位,course_id之后,然后分组捕获元素,使用环顾,或者逐行装换为json)

我对python的正则熟悉一些还是用python好了,往format_course_id.py写入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# encoding: utf-8
#使用流

import sys
import re
import json

for line in sys.stdin:
  #print line
  json_line = json.loads(line)
  old_course_id = json_line.get("course_id","")
  if old_course_id and "/" in old_course_id:
    old_course_id_items = old_course_id.split("/")
    #print old_course_id_items
    new_course_id = "course-v1:{}+{}+{}".format(old_course_id_items[0],old_course_id_items[1],old_course_id_items[2])
    json_line["course_id"] = new_course_id
    sys.stdout.write(json.dumps(json_line))
  else:
    sys.stdout.write(line)

cat contents.dat | python format_course_id.py >/tmp/contents.dat

###mysql auth_userprofile表dogwood比brich多了两个字段:bio和profile_image_uploaded_at;

迁移方法为先

  • SET FOREIGN_KEY_CHECKS=0; //取出依赖检查
  • DROP TABLE auth_user;
  • DROP TABLE auth_userprofile; //删除旧表
  • 导入新表
  • 修改表结构
1
2
ALTER TABLE auth_userprofile ADD bio varchar(3000) NULL
ALTER TABLE auth_userprofile ADD profile_image_uploaded_at  DATETIME  NULL 

最后SET FOREIGN_KEY_CHECKS=1