3
0
mirror of https://github.com/snipe/snipe-it.git synced 2026-08-18 03:06:23 +00:00

Added chunking and memory tracking in seeders

This commit is contained in:
snipe
2026-07-16 15:04:21 +01:00
parent 76949ed1a1
commit ce5a7568c8
7 changed files with 158 additions and 34 deletions

View File

@ -40,11 +40,15 @@ class ActionlogFactory extends Factory
$target = User::inRandomOrder()->first();
$asset = Asset::inRandomOrder()->RTD()->first();
// Fall back to the asset's default (rtd) location when the target
// user has no location set. Mirrors the fallback in
// App\Console\Commands\SyncAssetLocations so we don't seed assets
// with a null location_id.
$asset->update(
[
'assigned_to' => $target->id,
'assigned_type' => User::class,
'location_id' => $target->location_id,
'location_id' => $target->location_id ?? $asset->rtd_location_id,
]
);

View File

@ -60,6 +60,23 @@ class AssetFactory extends Factory
$asset->asset_eol_date = $this->faker->boolean(5)
? CarbonImmutable::parse($asset->purchase_date)->addMonths(rand(0, 20))->format('Y-m-d')
: CarbonImmutable::parse($asset->purchase_date)->addMonths($asset->model?->eol ?? rand(12, 60))->format('Y-m-d');
// Set location_id to match the asset's current allocation. Mirrors
// the four cases in App\Console\Commands\SyncAssetLocations so a
// fresh db:seed doesn't need to run that command as post-processing
// (which allocated ~130 MB scanning every asset). Any state that
// pre-sets location_id (e.g., a caller passes it explicitly) wins.
if ($asset->location_id !== null) {
return;
}
$asset->location_id = match (true) {
$asset->assigned_to === null => $asset->rtd_location_id,
$asset->assigned_type === User::class => User::find($asset->assigned_to)?->location_id ?? $asset->rtd_location_id,
$asset->assigned_type === Location::class => $asset->assigned_to,
$asset->assigned_type === Asset::class => Asset::find($asset->assigned_to)?->location_id ?? $asset->rtd_location_id,
default => $asset->rtd_location_id,
};
});
}

View File

@ -6,10 +6,13 @@ use App\Models\Actionlog;
use App\Models\Asset;
use App\Models\Location;
use App\Models\User;
use Database\Seeders\Concerns\ReportsMemory;
use Illuminate\Database\Seeder;
class ActionlogSeeder extends Seeder
{
use ReportsMemory;
public function run()
{
Actionlog::truncate();
@ -24,19 +27,30 @@ class ActionlogSeeder extends Seeder
$admin = User::where('permissions->superuser', '1')->first() ?? User::factory()->firstAdmin()->create();
$this->reportMemory('ActionlogSeeder start');
memory_reset_peak_usage();
Actionlog::factory()
->count(300)
->assetCheckoutToUser()
->create(['created_by' => $admin->id]);
gc_collect_cycles();
$this->reportMemory('ActionlogSeeder after 300 assetCheckoutToUser');
memory_reset_peak_usage();
Actionlog::factory()
->count(100)
->assetCheckoutToLocation()
->create(['created_by' => $admin->id]);
gc_collect_cycles();
$this->reportMemory('ActionlogSeeder after 100 assetCheckoutToLocation');
memory_reset_peak_usage();
Actionlog::factory()
->count(20)
->licenseCheckoutToUser()
->create(['created_by' => $admin->id]);
gc_collect_cycles();
$this->reportMemory('ActionlogSeeder after 20 licenseCheckoutToUser');
}
}

View File

@ -6,6 +6,7 @@ use App\Models\Asset;
use App\Models\Location;
use App\Models\Supplier;
use App\Models\User;
use Database\Seeders\Concerns\ReportsMemory;
use Illuminate\Database\Eloquent\Factories\Sequence;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
@ -14,6 +15,8 @@ use Illuminate\Support\Facades\Storage;
class AssetSeeder extends Seeder
{
use ReportsMemory;
private $admin;
private $locationIds;
@ -31,7 +34,19 @@ class AssetSeeder extends Seeder
$this->locationIds = Location::all()->pluck('id');
$this->supplierIds = Supplier::all()->pluck('id');
Asset::factory()->count(2000)->laptopMbp()->state(new Sequence($this->getState()))->create();
$this->reportMemory('AssetSeeder start');
// Chunk the big laptopMbp batch so we don't hold 2000 Asset models
// (plus 2000 Actionlog observer side-effects) in memory at once, which
// was pushing the demo servers into swap during full re-seeds.
memory_reset_peak_usage();
for ($i = 0; $i < 10; $i++) {
Asset::factory()->count(200)->laptopMbp()->state(new Sequence($this->getState()))->create();
gc_collect_cycles();
}
$this->reportMemory('AssetSeeder after laptopMbp chunked batch (2000 total)');
memory_reset_peak_usage();
Asset::factory()->count(50)->laptopMbpPending()->state(new Sequence($this->getState()))->create();
Asset::factory()->count(50)->laptopMbpArchived()->state(new Sequence($this->getState()))->create();
Asset::factory()->count(50)->laptopAir()->state(new Sequence($this->getState()))->create();
@ -63,6 +78,8 @@ class AssetSeeder extends Seeder
}
DB::table('checkout_requests')->truncate();
$this->reportMemory('AssetSeeder end (all factory batches complete)');
}
private function ensureLocationsSeeded()

View File

@ -0,0 +1,22 @@
<?php
namespace Database\Seeders\Concerns;
/**
* Adds a small [mem] logger to seeders so peak/current memory is visible in
* db:seed output. Kept in place as a durable regression signal: if someone
* later adds a giant factory batch that pushes demo servers into swap, the
* jump shows up in the console immediately.
*
* Safe to use in any Seeder subclass. Silently no-ops when $this->command is
* null (e.g., when the seeder is invoked outside of an Artisan command).
*/
trait ReportsMemory
{
protected function reportMemory(string $label): void
{
$peakMb = number_format(memory_get_peak_usage(true) / 1024 / 1024, 1);
$currentMb = number_format(memory_get_usage(true) / 1024 / 1024, 1);
$this->command?->info("[mem] {$label}: peak {$peakMb} MB, current {$currentMb} MB");
}
}

View File

@ -3,14 +3,15 @@
namespace Database\Seeders;
use App\Models\Setting;
use Database\Seeders\Concerns\ReportsMemory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class DatabaseSeeder extends Seeder
{
use ReportsMemory;
/**
* Run the database seeds.
*
@ -21,6 +22,8 @@ class DatabaseSeeder extends Seeder
Model::unguard();
DB::statement('SET FOREIGN_KEY_CHECKS=0');
$this->reportMemory('DatabaseSeeder start');
// Only create default settings if they do not exist in the db.
if (! Setting::first()) {
// factory(Setting::class)->create();
@ -28,33 +31,56 @@ class DatabaseSeeder extends Seeder
}
$this->call(CompanySeeder::class);
$this->reportMemory('after CompanySeeder');
$this->call(CategorySeeder::class);
$this->reportMemory('after CategorySeeder');
$this->call(LocationSeeder::class);
$this->reportMemory('after LocationSeeder');
$this->call(DepartmentSeeder::class);
$this->reportMemory('after DepartmentSeeder');
$this->call(UserSeeder::class);
$this->reportMemory('after UserSeeder');
$this->call(DepreciationSeeder::class);
$this->reportMemory('after DepreciationSeeder (1st)');
$this->call(ManufacturerSeeder::class);
$this->reportMemory('after ManufacturerSeeder');
$this->call(SupplierSeeder::class);
$this->reportMemory('after SupplierSeeder');
$this->call(AssetModelSeeder::class);
$this->reportMemory('after AssetModelSeeder');
$this->call(DepreciationSeeder::class);
$this->reportMemory('after DepreciationSeeder (2nd)');
$this->call(StatuslabelSeeder::class);
$this->reportMemory('after StatuslabelSeeder');
$this->call(AccessorySeeder::class);
$this->reportMemory('after AccessorySeeder');
$this->call(CustomFieldSeeder::class);
$this->reportMemory('after CustomFieldSeeder');
$this->call(AssetSeeder::class);
$this->reportMemory('after AssetSeeder');
$this->call(LicenseSeeder::class);
$this->reportMemory('after LicenseSeeder');
$this->call(ComponentSeeder::class);
$this->reportMemory('after ComponentSeeder');
$this->call(ConsumableSeeder::class);
$this->reportMemory('after ConsumableSeeder');
$this->call(ActionlogSeeder::class);
$this->reportMemory('after ActionlogSeeder');
$this->call(MaintenanceSeeder::class);
$this->reportMemory('after MaintenanceSeeder');
Artisan::call('snipeit:sync-asset-locations', ['--output' => 'all']);
$output = Artisan::output();
Log::info($output);
// snipeit:sync-asset-locations used to run here to backfill location_id
// on seeded assets. AssetFactory::configure() now sets location_id at
// make-time based on the assignment state, so post-seed sync is
// redundant. The command remains available as a manual maintenance
// tool for production databases that need drift correction.
Model::reguard();
DB::statement('SET FOREIGN_KEY_CHECKS=1');
DB::table('imports')->truncate();
DB::table('requested_assets')->truncate();
$this->reportMemory('DatabaseSeeder end');
}
}

View File

@ -5,6 +5,7 @@ namespace Database\Seeders;
use App\Models\Company;
use App\Models\Department;
use App\Models\User;
use Database\Seeders\Concerns\ReportsMemory;
use Illuminate\Database\Eloquent\Factories\Sequence;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Log;
@ -12,6 +13,8 @@ use Illuminate\Support\Facades\Storage;
class UserSeeder extends Seeder
{
use ReportsMemory;
/**
* Run the database seeds.
*
@ -71,37 +74,57 @@ class UserSeeder extends Seeder
// ~30% (600) no company
// ~50% (1000) one company
// ~20% (400) two or three companies
//
// Chunked so we don't hold 1000-2000 User models in memory at once
// (each ->each() / foreach that follows a big ->create() otherwise
// materializes the whole collection). Reduced demo-server memory
// pressure that was pushing seeding into swap.
$chunk = 200;
User::factory()->count(600)->viewAssets()
->withoutCompany()
->state(new Sequence(fn ($sequence) => [
'department_id' => $departmentIds->random(),
]))
->create();
$departmentState = fn () => new Sequence(fn ($sequence) => [
'department_id' => $departmentIds->random(),
]);
User::factory()->count(1000)->viewAssets()
->withoutCompany()
->state(new Sequence(fn ($sequence) => [
'department_id' => $departmentIds->random(),
]))
->create()
->each(function (User $user) use ($companyIds) {
$user->companies()->sync([$companyIds->random()]);
$user->syncLegacyCompanyIdMirror();
});
$this->reportMemory('UserSeeder start of regular-user batches');
$multiCompanyUsers = User::factory()->count(400)->viewAssets()
->withoutCompany()
->state(new Sequence(fn ($sequence) => [
'department_id' => $departmentIds->random(),
]))
->create();
foreach ($multiCompanyUsers as $user) {
$ids = $companyIds->random(min(rand(2, 3), $companyIds->count()))->toArray();
$user->companies()->sync($ids);
$user->syncLegacyCompanyIdMirror();
memory_reset_peak_usage();
for ($i = 0; $i < 600 / $chunk; $i++) {
User::factory()->count($chunk)->viewAssets()
->withoutCompany()
->state($departmentState())
->create();
gc_collect_cycles();
}
$this->reportMemory('UserSeeder after 600 no-company users (chunked)');
memory_reset_peak_usage();
for ($i = 0; $i < 1000 / $chunk; $i++) {
User::factory()->count($chunk)->viewAssets()
->withoutCompany()
->state($departmentState())
->create()
->each(function (User $user) use ($companyIds) {
$user->companies()->sync([$companyIds->random()]);
$user->syncLegacyCompanyIdMirror();
});
gc_collect_cycles();
}
$this->reportMemory('UserSeeder after 1000 one-company users (chunked)');
memory_reset_peak_usage();
for ($i = 0; $i < 400 / $chunk; $i++) {
User::factory()->count($chunk)->viewAssets()
->withoutCompany()
->state($departmentState())
->create()
->each(function (User $user) use ($companyIds) {
$ids = $companyIds->random(min(rand(2, 3), $companyIds->count()))->toArray();
$user->companies()->sync($ids);
$user->syncLegacyCompanyIdMirror();
});
gc_collect_cycles();
}
$this->reportMemory('UserSeeder after 400 multi-company users (chunked)');
$src = public_path('/img/demo/avatars/');
$dst = 'avatars'.'/';
@ -138,5 +161,6 @@ class UserSeeder extends Seeder
$file_number++;
}
$this->reportMemory('UserSeeder end (all users + avatars complete)');
}
}