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:
@ -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,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@ -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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
22
database/seeders/Concerns/ReportsMemory.php
Normal file
22
database/seeders/Concerns/ReportsMemory.php
Normal 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");
|
||||
}
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user