我在 Flutter-web 上。
我有一个 Firestore 集合
schools
,每所学校都有一个 pupils
集合。 我有大约 5000 所学校和 500000 名学生。
我想拜访每一位学生并写道
await visitSchoolPupils(
onSchoolPupil: (s, school, pupil) async {
//print("School ${school.schoolName}, pupil ${pupil.userName}");
return true;
});
哪里
Future<void> visitSchoolPupils({required Future<bool> Function(DocumentSnapshot s, School school, Pupil pupil) onSchoolPupil}) async {
int start = DateTime.now().millisecondsSinceEpoch;
int pupilCtr = 0;
await visitSchools(
onSchool: (s, school) async {
print(">>>DEBUG onSchool ${school.schoolId}");
try {
//pick all pupils
QuerySnapshot pupilSnapshot = await s.reference.collection(keyPupils).get();
List<Future> pupilTasks = [];
for (DocumentSnapshot doc in pupilSnapshot.docs) {
JsonMap pupilData = (doc.data()) as JsonMap;
Pupil pupil = Pupil.fromJson(pupilData);
pupilTasks.add(onSchoolPupil(doc, school, pupil));
// Execute in batches
if (pupilTasks.length >= 5) {
await Future.wait(pupilTasks);
pupilTasks.clear();
}
}
await Future.wait(pupilTasks);
pupilTasks.clear();
print("<<<DEBUG onSchool ${school.schoolId}");
return true;
} catch (e, trace) {
print("pupil tasks1 $e $trace");
return false;
}
});
print("DEBUG visitSchoolPupils: READY took ${DateTime.now().millisecondsSinceEpoch - start} ms for $pupilCtr pupils");
}
和
Future<void> visitSchools({required Future<bool> Function(DocumentSnapshot s, School school) onSchool}) async {
int start = DateTime.now().millisecondsSinceEpoch;
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection(keySchools).get();
int ctr=0;
int nr = snapshot.docs.length;
print("DEBUG visitSchools: START $nr schools");
//for all schools
List<Future> tasks = [];
for (DocumentSnapshot s in snapshot.docs) {
JsonMap schoolData = (s.data()) as JsonMap;
School school = School.fromJson(schoolData);
ctr++;
if(ctr%100==0)print("DEBUG[${DateTime.now().millisecondsSinceEpoch - start}] visitSchools[$ctr] ${school.schoolId}");
tasks.add(onSchool(s, school));
// Execute in batches of 10 or on final iteration
if (tasks.length >= 5) {
await Future.wait(tasks);
tasks.clear();
}
}
await Future.wait(tasks);
tasks.clear();
print("DEBUG visitSchools: READY took ${DateTime.now().millisecondsSinceEpoch - start} ms");
}
请注意,我在列表中添加了 5 个 future,并使用 Future.wait 一起等待它们(以加快速度)
此代码有时会挂起。 我注意到 5 个
onSchool
已经开始,但似乎没有一个完成。
当我仅向任务和瞳孔任务添加一个 Future 时,代码运行良好。
问题似乎是随机的,有时我会遇到 1000 所学校,有时会遇到 4000 所学校。是否存在我忽略的“线程安全”问题?我正在积累的记忆? 当某些 future 已经完成时,Future.wait 会出现问题吗?
关于如何调试此问题/改进此代码有什么建议吗?
每次您在前端应用程序中从 Firestore 加载数百或数千个文档时,您都做错了。
我建议将每个文档视为一个单独的文件。我怀疑您是否曾经从服务器加载数百个单独的文件,因此您也不应该加载那么多文档。
相反,请考虑您的应用程序实际需要从这些文档向用户显示哪些信息,然后仅加载该数据。
例如,您可能会显示学校名称列表。不要加载所有学校文档来显示名称,而是创建一个仅包含学校名称的集合 - 每个文档包含多个学校名称,并具有某种哈希机制。
接下来,考虑您是否真的立即需要数百或数千个学校名称。如果您在列表中显示它们,则一次可能无法容纳超过数十个(在移动设备上)或数百个(桌面设备),因此这就是您应该加载的全部内容。您可以使用
count()
查询确定有多少个学校名称(以确定列表的长度),然后使用基于游标的分页仅获取前N个名称并按需加载其余名称.
如果结合前两种方法,您可以按照学校名称文档中所需的任何排序顺序存储名称 - 然后仅加载其中的一两个。
如果您不熟悉 NoSQL 数据建模,我建议您阅读 NoSQL 数据建模技术 并观看 了解 Cloud Firestore。现在花几个小时在这些上,将为您节省更多时间。
需要考虑的其他一些(非数据建模)更改:
通过像您一样调用
get()
,您将强制 SDK 在返回任何内容之前从服务器获取文档(或至少检查那里)。考虑使用snapshot()
监听器从本地缓存中急切地获取文档,同时还从服务器接收更新。