如何在高负载Django应用中避免数据丢失?

0 投票
1 回答
508 浏览
提问于 2025-04-20 10:26

想象一下,有一个比较复杂的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

这种方法是否可行,取决于你的长时间任务具体是什么。而且用户仍然可能会覆盖你在这里保存的数据。

撰写回答