如何在高负载Django应用中避免数据丢失?
想象一下,有一个比较复杂的Django应用,它包含前端和后端两个部分。有些用户在前端修改数据,而一些脚本则定期在后端修改同样的数据。
举个例子:
instance = SomeModel.objects.get(...)
# (long-running part where various fields are changed, takes from 3 to 20 seconds)
instance.field = 123
instance.another_field = 'abc'
instance.save()
如果在某个部分正在修改一些字段的时候,其他人(或者其他程序)也在改变这个实例的数据,那么之前的修改就会丢失,因为最后保存的实例会覆盖之前的修改。换句话说,如果代码中的某个部分获取了数据,等了一段时间后再保存数据,那么只有最后一个保存的数据会被保留,之前的修改都会被覆盖。
这个应用的负载很高,我们使用的是Postgres数据库,数据库的负载也很大,我希望避免任何会导致数据库活动显著增加或占用过多内存的情况。
还有一个问题是,我们有很多信号(signals)绑定在一起,甚至还重写了save()方法,所以我希望避免任何可能会破坏这些信号或者与自定义的save()或update()方法不兼容的情况。
在这种情况下,你有什么建议吗?有没有特别的应用可以解决这个问题?事务?还是其他什么方法?
谢谢!
1 个回答
2
为了防止数据在读取和写入之间发生变化,正确的方法是使用 select_for_update
。这样可以确保数据在你读取和写入的过程中不会被其他地方修改。不过,这样做会锁住这一行数据,导致其他更新操作变得慢,所以可能会让你的应用变得很卡。
一种解决方案是先读取数据,然后进行你的长时间运行的任务。在你准备把数据保存回去之前,先开启一个事务,再次读取数据,这次使用 select_for_update
,确认原来的数据没有变化。如果数据还是一样的,就可以保存。如果数据发生了变化,就放弃这次操作,重新运行那个长时间的任务。这样你就能把锁住数据的时间缩短到最小。
大概是这样的:
success = False
while not success:
instance1 = SomeModel.objects.get(...)
# (long-running part)
with django.db.transaction.atomic():
instance2 = SomeModel.objects.select_for_update().get(...)
# (compare relevant data from instance1 vs instance2)
if unchanged:
# (make the changes on instance2)
instance2.field = 123
instance2.another_field = 'abc'
instance2.save()
success = True
这种方法是否可行,取决于你的长时间任务具体是什么。而且用户仍然可能会覆盖你在这里保存的数据。