我正在开发一个由Django驱动的API,该API可以调用另一个外部API。它修改并增强了从外部API接收的结果,从而在PostgreSQL数据库中为它们创建标准的Django模型实例。在以后使用相同参数调用我们的API时,不再调用外部API,而是返回以前存储的本地结果。
让我们说,对我们的API的调用是对端点/stuff/thingies
和/stuff/thingamabobs
的调用。每个端点都是一个单独的Django视图,每个端点返回stuff
的不同方面。 Stuff
是我们从外部API检索和存储的数据,thingies
和thingamabobs
是我们对该外部数据的两个不同的增强“视图”。
在任何给定情况下都无法保证首先调用哪个端点,因此两个端点都从mixin类继承,该类检查结果是否已经存在,调用外部API并在需要时存储结果,然后再实际返回增强结果。
外部API调用既昂贵又费时(这就是为什么我们首先将结果存储在本地的原因),这就是问题所在:两个端点都可能以足够快的顺序被两个端点调用而决定结果还不存在,都进行了长时间运行的外部API调用,并且都开始写入数据库,从而导致数据库条目重复。
(对于单个端点的两次快速调用也存在相同的问题,但是由于我们系统的性质,我们主要看到的是从一个客户端到两个端点的快速调用,所以我在描述一下我们的设置只是供参考。)
以漂亮的ASCII图形形式需要解决此问题的解决方案:
API CALL 1 API CALL 2
+ /thingies called with param X.
| Set some global flag atomically,
| saying that you're processing
T | param X right now and anybody
I | wanting access to param X data
M | should wait until the flag is
E | cleared. +
| |
| |
| v
| call external API for the data
| +
| | /thingamabobs called with param X.
| | See that the global flag is set and
| | keep checking for its clearance.
| | (5-10 seconds) +
| | |
| | |
| | |
| v |
| external API results received |
| + |
| | |
| v |
| results written to DB |
| + |
| | |
| v |
| global flag cleared |
| + v
| | oh hey, the flag cleared. Notice
| v that the data for param X is now
| /thingies returns its data in the database, do the thing.
| to the client +
| |
| v
| /thingamabobs returns its data
v to the client
该解决方案不能仅仅依靠在某些数据库字段上设置唯一索引,而在捕获的/thingamabobs
上调用IntegrityError
失败,因为它试图写入现在重复的结果,因为)我不想再进行一次冗长的外部API调用,并且b)我希望/thingamabobs
调用保持有效并等待/thingies
调用完成,然后可以访问本地存储的参数X个结果。
我们在许多Gunicorn工人之上运行Django,因此进程全局标记也不会削减它。
我当时在想也许会有一个额外的“全局标志” Django模型。在上面的示例中,/thingies
将在执行其他任何操作之前在相应的DB表中为参数X写入一个条目,并在完成后删除该条目。 /thingamabobs
会看到该条目并一直检查直到删除为止,然后为/thingies
在其处理期间创建的参数X获取存储的数据。但是,我对Django的事务处理,Django对PostgreSQL所使用的隔离级别(或其在此处应使用的隔离级别)或PostgreSQL的锁定原语(如果需要)没有足够的了解,因此我不确定我是否可以构建一个正确的解决方案。
总而言之,如果可能的话,我宁愿使用本机Django / PostgreSQL解决方案。