Django:加速数百个用户上传

Django: speed up hundreds of users upload

问题

我目前正在开发一个应用程序并将其部署到 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() 的原因是我必须跟踪 skipped/not 添加到数据库的用户数量。 (尽管这可能不是最佳做法。)

一些发现

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

我假设 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 查询,执行它并 return 结果。

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 的内部以查看是否存在一些惰性计算函数(如您提到的哈希)。

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


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

可能值得使用队列 and/or 一项单独的批量上传服务,具体取决于您的应用程序的配置文件。