Export users 500k+ lines, seed, supervisord setup
This commit is contained in:
+15
-2
@@ -30,7 +30,7 @@ docker compose up
|
||||
alias fhr-mysql='docker exec -it mysql_fhr /bin/bash'
|
||||
alias fhr-nginx='docker exec -it nginx_fhr /bin/bash'
|
||||
alias fhr-redis='docker exec -it redis_fhr /bin/sh'
|
||||
alias fhr-php='docker exec -it php8_fhr /bin/bash'
|
||||
alias fhr-php='docker exec -u www-data:www-data -it php8_fhr /bin/bash'
|
||||
```
|
||||
|
||||
### Написать сортировку для массива числовых данных от 200 тысяч элементов
|
||||
@@ -74,4 +74,17 @@ mysqldump -u root -psecret test_fhr clubs players > clubs_and_players_dump.sql
|
||||
После такой выгрузки, дамп появится в папке MySQL-files
|
||||
Тестовый дамп лежит в корне проекта **clubs_and_players_dump.sql**
|
||||
|
||||
**Время: 1 час 25 минут.**
|
||||
**Время: 1 час 25 минут.**
|
||||
|
||||
### Выгрузка БД пользователей, более 500к+ строк
|
||||
|
||||
Сидируем БД
|
||||
```bash
|
||||
# Долгий сид достаточно, минут 15-20 возможно
|
||||
php artisan db:seed --class=UserSeeder
|
||||
```
|
||||
|
||||
Дальше открываем главную страницу http://test-fhr.ldev/
|
||||
Хост **test-fhr.ldev** нужно будет прописать в hosts в зависимости от вашей ОС.
|
||||
|
||||
**Время: 2 часа 30 минут.**
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Jobs\UsersExport;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class UserExportController extends Controller
|
||||
{
|
||||
public function status()
|
||||
{
|
||||
$status = Cache::remember('export_status', now()->addMinutes(2), function () {
|
||||
return 'standby';
|
||||
});
|
||||
|
||||
$lastExportFile = Cache::get('last_export_file');
|
||||
|
||||
return [
|
||||
'export_status' => $status,
|
||||
'last_export_file' => $lastExportFile
|
||||
];
|
||||
}
|
||||
|
||||
public function counter()
|
||||
{
|
||||
$currentCount = Cache::remember('users_count', now()->addMinutes(2), function () {
|
||||
return User::count();
|
||||
});
|
||||
|
||||
return [
|
||||
'count' => $currentCount,
|
||||
];
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
$status = cache('export_status');
|
||||
if ($status === 'progress') {
|
||||
return [
|
||||
'export_status' => $status,
|
||||
];
|
||||
}
|
||||
|
||||
Cache::forever('export_status', 'progress');
|
||||
|
||||
UsersExport::dispatch();
|
||||
|
||||
return [
|
||||
'export_status' => 'started',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Throwable;
|
||||
|
||||
class UsersExport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$exportPath = public_path('exports');
|
||||
|
||||
if (!File::isDirectory($exportPath)) {
|
||||
File::makeDirectory($exportPath, 0755, true);
|
||||
}
|
||||
|
||||
$oldFiles = File::glob($exportPath . '/users_export_*.csv');
|
||||
foreach ($oldFiles as $file) {
|
||||
File::delete($file);
|
||||
}
|
||||
|
||||
$filename = 'users_export_' . now()->format('Y-m-d_H-i-s') . '.csv';
|
||||
$filePath = $exportPath . '/' . $filename;
|
||||
|
||||
$handle = fopen($filePath, 'w');
|
||||
|
||||
fputcsv($handle, ['Имя', 'Фамилия', 'Телефон', 'E-mail']);
|
||||
|
||||
foreach (User::select('name', 'surname', 'phone_number', 'email')->lazy() as $user) {
|
||||
fputcsv($handle, [
|
||||
$user->name,
|
||||
$user->surname,
|
||||
$user->phone_number,
|
||||
$user->email,
|
||||
]);
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
Cache::forever('export_status', 'standby');
|
||||
Cache::forever('last_export_file', 'exports/' . $filename);
|
||||
|
||||
} catch (Throwable $e) {
|
||||
Cache::forever('export_status', 'failed');
|
||||
report($e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ class UserFactory extends Factory
|
||||
{
|
||||
return [
|
||||
'name' => fake()->name(),
|
||||
'surname' => fake()->lastName(),
|
||||
'phone_number' => fake()->phoneNumber(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('surname')->after('name');
|
||||
$table->string('phone_number')->after('surname');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('surname');
|
||||
$table->dropColumn('phone_number');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -15,11 +15,6 @@ class DatabaseSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
// User::factory(10)->create();
|
||||
|
||||
User::factory()->create([
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
]);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class UserSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$totalToCreate = 525000;
|
||||
$chunkSize = 1000;
|
||||
|
||||
\Illuminate\Support\Facades\DB::disableQueryLog();
|
||||
|
||||
$this->command->getOutput()->progressStart($totalToCreate);
|
||||
|
||||
for ($i = 0; $i < $totalToCreate; $i += $chunkSize) {
|
||||
$createCount = min($chunkSize, $totalToCreate - $i);
|
||||
User::factory($createCount)->create();
|
||||
$this->command->getOutput()->progressAdvance($createCount);
|
||||
}
|
||||
|
||||
$this->command->getOutput()->progressFinish();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,14 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use \App\Http\Controllers\UserExportController;
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('welcome');
|
||||
});
|
||||
|
||||
Route::prefix('users-export')->group(function () {
|
||||
Route::get('/counter', [UserExportController::class, 'counter']);
|
||||
Route::get('/status', [UserExportController::class, 'status']);
|
||||
Route::post('/export', [UserExportController::class, 'export']);
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
Reference in New Issue
Block a user