Export users 500k+ lines, seed, supervisord setup
This commit is contained in:
@@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libzip-dev \
|
||||
unzip \
|
||||
git \
|
||||
supervisor \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Configure and install PHP extensions
|
||||
@@ -55,7 +56,14 @@ USER www-data
|
||||
# 6. Download and unpack the Laravel installer directly into www-data's profile folder
|
||||
RUN composer global require laravel/installer --no-interaction
|
||||
|
||||
# 7. Switch back to root user
|
||||
USER root
|
||||
|
||||
# Supervisor
|
||||
RUN mkdir -p /var/log/supervisor
|
||||
COPY ./supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
|
||||
# Expose PHP-FPM default communication port
|
||||
EXPOSE 9000
|
||||
|
||||
CMD ["php-fpm"]
|
||||
CMD ["/usr/bin/supervisord", "-n", "-c", "/etc/supervisor/conf.d/supervisord.conf"]
|
||||
@@ -4,5 +4,6 @@ opcache.memory_consumption=256
|
||||
opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=20000
|
||||
opcache.revalidate_freq=0
|
||||
opcache.validate_timestamps=0
|
||||
opcache.validate_timestamps=1
|
||||
opcachee.revalidate_freq=0
|
||||
opcache.fast_shutdown=1
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
[supervisord]
|
||||
nodaemon=true
|
||||
user=root
|
||||
|
||||
[program:php-fpm]
|
||||
command=php-fpm
|
||||
autostart=true
|
||||
autorestart=true
|
||||
priority=5
|
||||
stdout_logfile=/dev/stdout
|
||||
stdout_logfile_maxbytes=0
|
||||
stderr_logfile=/dev/stderr
|
||||
stderr_logfile_maxbytes=0
|
||||
|
||||
[program:laravel-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /var/www/test-fhr/artisan queue:work --sleep=3 --tries=3
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=www-data
|
||||
numprocs=4
|
||||
redirect_stderr=true
|
||||
stdout_logfile=/var/www/test-fhr/storage/logs/worker.log
|
||||
stopwaitsecs=3600
|
||||
+14
-1
@@ -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 тысяч элементов
|
||||
@@ -75,3 +75,16 @@ mysqldump -u root -psecret test_fhr clubs players > clubs_and_players_dump.sql
|
||||
Тестовый дамп лежит в корне проекта **clubs_and_players_dump.sql**
|
||||
|
||||
**Время: 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
|
||||
@@ -44,6 +44,6 @@ server {
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri /index.php?$args;
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user