Django:加速数百名用户上传



问题

我目前正在开发一个应用程序,并将其部署在Heroku上。一个重要的功能是,管理员应该能够通过上传.csv文件来一次上传数百个用户。问题是,由于预算限制,使用Heroku的免费计划花费的时间太长,并且请求时间已用完。我需要一个更好的解决方案来处理这个问题。

我的尝试

我现在的做法是使用create_user()将新用户注册到数据库中(代码附在下面(。

def register_user(upload_file, file_type):
if file_type == 'csv':
reader = csv.reader(StringIO(upload_file), delimiter=',')
for i, row in enumerate(reader):
if i == 0:
continue
else:
username = row[0]
password = row[1]
if username.isdigit():
is_staff = False
else:
is_staff = True
try:
User.objects.create_user(username=username, password=password, is_staff=is_staff)
except:
continue

我不使用bulk_create()的原因是,我必须跟踪有多少用户被跳过/没有添加到数据库中。(尽管这可能不是最佳做法。(

一些发现

  • 我的尝试在localhost中完全正常。大约有300名用户,大约需要10秒。

  • 从这个线程中,我已经了解到create_user()由于制作密码的过程而花费了太长时间。所以我决定使用Argon2哈希器,而不是默认的哈希器。业绩已经大幅提升,但仍然不够。

  • bulk_create()确实加快了进程,但在localhost中几乎没有什么作用。

有什么方法可以防止请求超时吗?

我假设create_user在内部调用QuerySet.create。此函数在内部使用对象构造函数创建实例,然后使用对象的save方法将其持久化。

也就是说,这些片段很可能是等价的

User.objects.create_user(username=username, password=password, is_staff=is_staff)
u = User(username=username, password=password, is_staff=is_staff)
u.save()

你可以测量每次通话所花费的时间。我敢打赌,保存占用了最大的部分,因为它必须建立(或缓存(到数据库的连接,创建SQL查询,执行它并返回结果。

Django的ORM提供了一个方便的函数,可以将所有这些查询批量处理为一个:bulk_create。如果数据库在不同的主机上运行,切换到该功能将产生显著影响。

有了这个,你的代码看起来会像下面这样。我也有权清理你的代码。

def register_users(upload_file, file_type):
if file_type != 'csv':
# TODO raise error here
return
reader = csv.DictReader(upload_file)
users = []
for row in reader:
is_staff = not row['username'].isdigit()
users.append(User(username=row['username'], password=row['password'], is_staff=is_staff))
try:
User.objects.bulk_create(users)
except:
# TODO: Catch specific expected exceptions and log them
continue

如果这种优化不能在一秒钟内得到查询,我会调试bulk_create的内部,看看是否有一些延迟求值的函数(比如你提到的散列(。

请注意,我没有测试以上任何一项。


除了这个小的改进之外,这类过程通常最好在后台异步完成。您可以直接返回请求者,并可选地将上传状态作为单独的API端点提供。这将降低对请求定时的要求,并将释放一个请求线程。

根据应用程序的配置文件,使用队列和/或单独的服务进行批量上传可能是值得的。

最新更新