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秒钟。

  • 从这个thread开始,我已经了解到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,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端点提供。这将降低对请求时间的要求,并释放一个请求线程。

根据应用程序的配置文件,可能值得为此队列使用队列和/或单独的服务。