美妙的一天,从在群里面被人艾特开始...
celery
定时任务修改了执行时间,页面展示的时间生效,但实际执行的时间不生效,还是使用按照原来的时间运行
with transaction.atomic():
# 更新PeriodicTask的字段
if crontab_str := fields_kv.get('crontab', ''):
logger.info(f'update field: {crontab_str=}')
celery_model_operation.update_celery_crontab(
celery_id=obj.celery_task.id,
crontab_str=crontab_str,
)
if (is_enabled := fields_kv.get('is_enabled', None)) is not None:
logger.info(f'update field: {is_enabled=}')
celery_model_operation.update_celery_status(
id=obj.celery_task.id,
is_enabled=is_enabled,
)
if marker_name := fields_kv.get('marker_name', None):
logger.info(f'update field: {marker_name=}')
celery_task_obj = celery_model_operation.get_celery_obj_by_id(obj.celery_task.id)
celery_task_obj.name = f'{obj.project_id}:{marker_name}'
kwargs: dict = json.loads(obj.celery_task.kwargs)
kwargs['marker_name'] = marker_name
celery_task_obj.kwargs = json.dumps(kwargs)
celery_task_obj.save()
# 更新页面展示表的字段
return super().partial_update(id, user_email, **fields_kv)
def get_celery_obj_by_id(id: int) -> PeriodicTask:
return PeriodicTask.objects.get(id=id)
def update_celery_status(id: int, is_enabled: bool) -> None:
celery_task_obj = get_celery_obj_by_id(id)
celery_task_obj.enabled = is_enabled
celery_task_obj.save()
def update_celery_crontab(celery_id: int, crontab_str: str) -> None:
celery_task_obj = get_celery_obj_by_id(celery_id)
crontab_obj = get_or_create_crontab_obj(crontab_str=crontab_str)
celery_task_obj.crontab = crontab_obj
celery_task_obj.save()
在页面上,任务的开关有个快捷修改入口,直接放在列表
测试一下,发现页面展示表和 celery 表字段都被正常修改
内心 os: 这会是一个突破口吗?
检查前端传参:
a
,还是老问题,页面展示表字段被修改,celery
字段没被修改a
的问题?b
,跟字段a
一样,看来并不是字段a
的问题,真是令人头大这时不禁问,难道是生产环境和本地,测试环境的数据差异?
那试一下呗,正好本地和测试都有那批数据
本地和测试都再来一遍上面的检查步骤,问题还是没有复现
难道是生产环境个别特殊数据才会这样?
换个数据试一下,问题还是必现!
那看来并不是特殊数据的问题?
这似乎也看不出来什么问题啊,怎么办?
看来加日志也是很需要讲究的
但是这时候已经快下班了,明天再搞吧
真的能放下不管,明天再搞?
作为一名有强迫症的程序员,那是肯定放不下的
在深圳回来广州的高铁上,还是忍不住继续去思考
虽然高铁上,iPhone 的信号很差,可是依然是无法阻止我探索的欲望
于是,查到了这篇文章Learn the subtle differences between Save and Update in Django
恍然大悟,我代码save()
方法是没有指定更新字段的,后面更新的记录,会覆盖掉前面更新的字段。
不对,不对,不对。为啥在本地和测试环境并没有这个问题,在生产环境却有。
update_fields
被这个bug
困扰了一个晚上,睡眠并不是很好
一早醒来,迫不及待的去验证save()
方法增加update_fields
参数的方案
哇塞,加上update_fields
之后,还真的可以!页面显示表和celery
表的数据都完美更新了
那就直接修改完,提交代码,叫leader
合并,搞定!
这显然是不行,即使结果是对的,但依然没有解释清楚,为啥本地和测试环境没问题,但生产环境就有问题!
既然本地,测试,生产环境代码都是一样,那有啥是不一样的。
没错,那就是数据库配置了。生产环境数据库为了高可用,是主从模式,而测试和本地都是单机模式。
说是这么说,那怎么验证呢?
毫无疑问,还是得加日志。
那加在哪里,这个很关键
突然想起了群里面ayo
同学的一句话,在此非常感谢ayo
同学
加了68
行的日志
本地日志
本地68,71,59
行可以看出来
更新前crontab_id=395
,新crontab_id
是399
在更新status
时,获取到crontab_id
值也是399
,符合预期
生产日志
生产68,71,59
行可以看出来
更新前crontab_id=399
,新crontab_id
是1107
在更新status
时,但获取到crontab_id
却是旧值399
,不符合预期
至此,就可以解释清楚,为啥本地和测试环境没问题,但生产环境就有问题
因为本地和测试环境更新crontab
是写入的是主库
在更新status
时,读取的也是主库。始终都是在一个数据库上操作
生产环境更新crontab
,写入的是主库
更新status
时,读取却是走了从库。
数据库主从同步是有延迟的,肯定没有代码执行的快,所以从库的数据就还是旧数据
在没有指定update_fields
时,更新的是所有字段,最终导致,crontab_id
就被旧值覆盖了。
最终的效果就是页面展示表
数据是正常更新的,但celery
表看起来没有被更新一样,实际是被旧值覆盖
update_fields
指定只更新需要的字段Django save()
方法一定要小心,可能出现旧数据覆盖问题,可考虑使用update
方法代替原文
我在【TesterHome 系列征文活动 | 有意思的 bug】https://testerhome.com/topics/33905 等你,一起 day day up!