问题描述
问题
我目前正在开发一个应用程序并将其部署在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端点提供。这将降低对请求时间的要求,并释放一个请求线程。
根据应用程序的配置文件,可能值得为此队列使用队列和/或单独的服务。