perf: eager-load relationships in UpdateSearchJob and cache permissions in DiscussionSeeder

- Add relationships() to Seeder base class; DiscussionSeeder implements it to
  return tags/recipientUsers/recipientGroups for eager loading
- UpdateSearchJob calls loadMissing(relationships()) before the map loop,
  eliminating N×R lazy-load queries per batch
- Remove refresh:true from bulk call — synchronous ES refresh was the primary
  throughput bottleneck during indexing
- Cache viewForum permissions in DiscussionSeeder per job instance, replacing
  N×2 Permission queries per document with a single cached query
- Increase seeder batch size from 1000 to 2500
This commit is contained in:
Bart van Bragt 2026-04-13 22:18:14 +02:00
parent 439c74f0a3
commit 2fc66b9bf7
4 changed files with 50 additions and 23 deletions

View File

@ -355,13 +355,13 @@ HELP;
$seeded = null;
while ($continueAt !== null) {
$rangeFrom = max(1, $continueAt - 1000);
$rangeFrom = max(1, $continueAt - 2500);
$rangeTo = $continueAt;
if ($seedMissing) {
$response = (new Builder($client))
->index($targetIndex)
->size(1000)
->size(2500)
->addQuery(
(new BoolQuery())
->add((new RangeQuery('rawId'))->gte($rangeFrom)->lte($rangeTo))

View File

@ -25,6 +25,8 @@ class UpdateSearchJob extends Job
return;
}
$this->models->loadMissing($this->seeder->relationships());
// Preparing body for storing.
$body = $this->models->map(function (Model $model) {
$document = $this->seeder->toDocument($model);
@ -38,9 +40,8 @@ class UpdateSearchJob extends Job
->flatten(1);
$response = $client->bulk([
'index' => $this->index,
'body' => $body->toArray(),
'refresh' => true,
'index' => $this->index,
'body' => $body->toArray(),
]);
if (Arr::get($response, 'errors') !== true) {

View File

@ -45,6 +45,12 @@ class DiscussionSeeder extends Seeder
}
public function query(): Builder
{
return Discussion::query()
->whereNull('hidden_at');
}
public function relationships(): array
{
$includes = [];
@ -57,9 +63,7 @@ class DiscussionSeeder extends Seeder
$includes[] = 'recipientGroups';
}
return Discussion::query()
->whereNull('hidden_at')
->with($includes);
return $includes;
}
public static function savingOn(Dispatcher $events, callable $callable)
@ -151,37 +155,53 @@ class DiscussionSeeder extends Seeder
return $document;
}
/**
* All viewForum permissions keyed by permission string, loaded once per seeder instance.
* Avoids N×2 Permission queries inside the per-document map loop.
*/
private ?Collection $cachedPermissions = null;
private ?Collection $cachedGlobalPermission = null;
private function allPermissions(): Collection
{
if ($this->cachedPermissions === null) {
$this->cachedPermissions = Permission::query()
->where(function ($q) {
$q->where('permission', 'viewForum')
->orWhere('permission', 'like', 'tag%.viewForum');
})
->get();
$this->cachedGlobalPermission = $this->cachedPermissions
->where('permission', 'viewForum')
->pluck('group_id');
}
return $this->cachedPermissions;
}
protected function groupsForDiscussion(Discussion $discussion): array
{
$allPerms = $this->allPermissions();
$permissions = collect();
$globalPermission = Permission::query()
->where('permission', 'viewForum')
->pluck('group_id');
if ($this->extensionEnabled('flarum-tags')) {
/** @var Collection $tags */
$tags = $discussion->tags;
$tagPermissions = Permission::query()
->whereIn(
'permission',
$tags->pluck('id')->map(fn (int $id) => "tag$id.viewForum")
)->get();
$permissions = $tags->map(function (Tag $tag) use ($tagPermissions) {
$permissions = $tagPermissions->where('permission', "tag$tag->id.viewForum");
$permissions = $tags->map(function (Tag $tag) use ($allPerms) {
$tagPerms = $allPerms->where('permission', "tag$tag->id.viewForum");
if ($tag->is_restricted) {
$permissions = $permissions->add(['group_id' => Group::ADMINISTRATOR_ID]);
$tagPerms = $tagPerms->add(['group_id' => Group::ADMINISTRATOR_ID]);
}
return $permissions->pluck('group_id');
return $tagPerms->pluck('group_id');
})->flatten();
}
if (!$discussion->is_private && $permissions->isEmpty()) {
$permissions = $globalPermission;
$permissions = $this->cachedGlobalPermission;
}
return $permissions->toArray();

View File

@ -45,6 +45,12 @@ abstract class Seeder
abstract public function toDocument(Model $model): Document;
/** Relationships to eager-load on the collection before calling toDocument(). */
public function relationships(): array
{
return [];
}
protected function extensionEnabled(string $extension): bool
{
/** @var ExtensionManager $manager */