Ein FULLTEXT-Index zwingt MySQL bei JEDEM ADD COLUMN auf ALGORITHM=COPY. Auf press_releases (>1 GB) bedeutet das einen vollständigen Rebuild, während dessen kein Schreibzugriff möglich ist — im August hat genau das die Seite zum Stehen gebracht, weil die wartenden Requests PHP-FPM-Worker und DB-Verbindungen belegten. Die durchsuchbaren Felder liegen jetzt gespiegelt in `press_release_search`, die die Indizes trägt. Ihr Schema ist per Konstruktion stabil und wird nie erweitert; neue Felder gehören nach press_releases, wo sie ohne Volltext-Index Millisekunden kosten. Synchron hält sie ein Observer (alle Schreibzugriffe laufen über Eloquent, auch der Importer), reparieren lässt sie sich mit press-releases:reindex-search. Der Umstieg ist über mehrere Migrationen geteilt und schaltet erst um, wenn press_releases seine Indizes verloren hat — bis dahin sucht die Anwendung unverändert weiter. Die Drop-Migration verweigert den Dienst, solange der Suchindex unvollständig ist, und verwirft die gecachte Umschalt-Prüfung: Stünde dort noch der alte Stand, setzte der Rückfallpfad sein MATCH() auf einen soeben entfernten Index ab (MySQL-Fehler 1191) und jede Suche endete im 500er. Dazu die betriebliche Absicherung: - DB_LOCK_WAIT_TIMEOUT (Default 10 s) statt MySQLs einem Jahr. Requests scheitern bei einer laufenden Schemaänderung nach Sekunden, statt sich aufzustauen, bis keine Verbindung mehr frei ist. - Sessions und Cache nach Redis, damit eine blockierte MySQL-Tabelle nicht die gesamte Anwendung mitreißt. - db:online-alter als Notausgang (pt-online-schema-change), falls eine der kleinen FULLTEXT-Tabellen doch einmal groß wird. Percona-Toolkit ist bewusst nicht Teil des Standard-Setups. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
239 lines
9.3 KiB
PHP
239 lines
9.3 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
/*
|
|
* Wartezeit auf Tabellen-Sperren (Metadata Lock).
|
|
*
|
|
* MySQL wartet per Default 31536000 Sekunden — ein Jahr. Läuft eine
|
|
* Schemaänderung (auf press_releases/companies erzwingen die FULLTEXT-Indizes
|
|
* einen vollständigen COPY-Rebuild), blockiert jeder Schreibzugriff so lange
|
|
* und belegt dabei einen PHP-FPM-Worker samt DB-Verbindung. Sind die
|
|
* Verbindungen aufgebraucht, steht die gesamte Anwendung — auch Sessions,
|
|
* Cache und Queue, die hier ebenfalls in MySQL liegen.
|
|
*
|
|
* Requests scheitern deshalb nach wenigen Sekunden, statt sich aufzustauen.
|
|
* Begrenzt wird nur das WARTEN auf die Sperre — eine bereits laufende
|
|
* Migration wird davon nicht abgeschnitten.
|
|
*
|
|
* Bewusst ein einziger Wert aus der Umgebung statt einer Fallunterscheidung
|
|
* über PHP_SAPI: `config:cache` friert den zur Cache-Zeit (CLI!) ermittelten
|
|
* Wert ein, eine solche Weiche würde also im Web still den falschen Wert
|
|
* liefern. Braucht ein Lauf mehr Geduld — etwa eine Migration auf einer
|
|
* großen Tabelle —, wird er explizit hochgesetzt:
|
|
*
|
|
* DB_LOCK_WAIT_TIMEOUT=300 php artisan migrate --force
|
|
*/
|
|
$lockWaitTimeout = 'SET SESSION lock_wait_timeout = '.(int) env('DB_LOCK_WAIT_TIMEOUT', 10);
|
|
|
|
return [
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Default Database Connection Name
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Here you may specify which of the database connections below you wish
|
|
| to use as your default connection for database operations. This is
|
|
| the connection which will be utilized unless another connection
|
|
| is explicitly specified when you execute a query / statement.
|
|
|
|
|
*/
|
|
|
|
'default' => env('DB_CONNECTION', 'sqlite'),
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Database Connections
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Below are all of the database connections defined for your application.
|
|
| An example configuration is provided for each database system which
|
|
| is supported by Laravel. You're free to add / remove connections.
|
|
|
|
|
*/
|
|
|
|
'connections' => [
|
|
|
|
'sqlite' => [
|
|
'driver' => 'sqlite',
|
|
'url' => env('DB_URL'),
|
|
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
|
'prefix' => '',
|
|
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
|
|
'busy_timeout' => null,
|
|
'journal_mode' => null,
|
|
'synchronous' => null,
|
|
],
|
|
|
|
'mysql' => [
|
|
'driver' => 'mysql',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST', '127.0.0.1'),
|
|
'port' => env('DB_PORT', '3306'),
|
|
'database' => env('DB_DATABASE', 'laravel'),
|
|
'username' => env('DB_USERNAME', 'root'),
|
|
'password' => env('DB_PASSWORD', ''),
|
|
'unix_socket' => env('DB_SOCKET', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
'strict' => true,
|
|
'engine' => null,
|
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
PDO::MYSQL_ATTR_INIT_COMMAND => $lockWaitTimeout,
|
|
]) : [],
|
|
],
|
|
'mysql_presseecho' => [
|
|
'driver' => 'mysql',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST_PRESSEECHO', '127.0.0.1'),
|
|
'port' => env('DB_PORT', '3306'),
|
|
'database' => env('DB_DATABASE_PRESSEECHO', 'laravel'),
|
|
'username' => env('DB_USERNAME_PRESSEECHO', 'root'),
|
|
'password' => env('DB_PASSWORD_PRESSEECHO', ''),
|
|
'unix_socket' => env('DB_SOCKET', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
'strict' => true,
|
|
'engine' => null,
|
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
PDO::MYSQL_ATTR_INIT_COMMAND => $lockWaitTimeout,
|
|
]) : [],
|
|
],
|
|
'mysql_businessportal' => [
|
|
'driver' => 'mysql',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST_BUSINESSPORTAL', '127.0.0.1'),
|
|
'port' => env('DB_PORT', '3306'),
|
|
'database' => env('DB_DATABASE_BUSINESSPORTAL', 'laravel'),
|
|
'username' => env('DB_USERNAME_BUSINESSPORTAL', 'root'),
|
|
'password' => env('DB_PASSWORD_BUSINESSPORTAL', ''),
|
|
'unix_socket' => env('DB_SOCKET', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
'strict' => true,
|
|
'engine' => null,
|
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
PDO::MYSQL_ATTR_INIT_COMMAND => $lockWaitTimeout,
|
|
]) : [],
|
|
],
|
|
'mariadb' => [
|
|
'driver' => 'mariadb',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST', '127.0.0.1'),
|
|
'port' => env('DB_PORT', '3306'),
|
|
'database' => env('DB_DATABASE', 'laravel'),
|
|
'username' => env('DB_USERNAME', 'root'),
|
|
'password' => env('DB_PASSWORD', ''),
|
|
'unix_socket' => env('DB_SOCKET', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
|
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
'strict' => true,
|
|
'engine' => null,
|
|
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
|
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
|
|
PDO::MYSQL_ATTR_INIT_COMMAND => $lockWaitTimeout,
|
|
]) : [],
|
|
],
|
|
|
|
'pgsql' => [
|
|
'driver' => 'pgsql',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST', '127.0.0.1'),
|
|
'port' => env('DB_PORT', '5432'),
|
|
'database' => env('DB_DATABASE', 'laravel'),
|
|
'username' => env('DB_USERNAME', 'root'),
|
|
'password' => env('DB_PASSWORD', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
'search_path' => 'public',
|
|
'sslmode' => 'prefer',
|
|
],
|
|
|
|
'sqlsrv' => [
|
|
'driver' => 'sqlsrv',
|
|
'url' => env('DB_URL'),
|
|
'host' => env('DB_HOST', 'localhost'),
|
|
'port' => env('DB_PORT', '1433'),
|
|
'database' => env('DB_DATABASE', 'laravel'),
|
|
'username' => env('DB_USERNAME', 'root'),
|
|
'password' => env('DB_PASSWORD', ''),
|
|
'charset' => env('DB_CHARSET', 'utf8'),
|
|
'prefix' => '',
|
|
'prefix_indexes' => true,
|
|
// 'encrypt' => env('DB_ENCRYPT', 'yes'),
|
|
// 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'),
|
|
],
|
|
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Migration Repository Table
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| This table keeps track of all the migrations that have already run for
|
|
| your application. Using this information, we can determine which of
|
|
| the migrations on disk haven't actually been run on the database.
|
|
|
|
|
*/
|
|
|
|
'migrations' => [
|
|
'table' => 'migrations',
|
|
'update_date_on_publish' => true,
|
|
],
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Redis Databases
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| Redis is an open source, fast, and advanced key-value store that also
|
|
| provides a richer body of commands than a typical key-value system
|
|
| such as Memcached. You may define your connection settings here.
|
|
|
|
|
*/
|
|
|
|
'redis' => [
|
|
|
|
'client' => env('REDIS_CLIENT', 'phpredis'),
|
|
|
|
'options' => [
|
|
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
|
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
|
|
'persistent' => env('REDIS_PERSISTENT', false),
|
|
],
|
|
|
|
'default' => [
|
|
'url' => env('REDIS_URL'),
|
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
|
'username' => env('REDIS_USERNAME'),
|
|
'password' => env('REDIS_PASSWORD'),
|
|
'port' => env('REDIS_PORT', '6379'),
|
|
'database' => env('REDIS_DB', '0'),
|
|
],
|
|
|
|
'cache' => [
|
|
'url' => env('REDIS_URL'),
|
|
'host' => env('REDIS_HOST', '127.0.0.1'),
|
|
'username' => env('REDIS_USERNAME'),
|
|
'password' => env('REDIS_PASSWORD'),
|
|
'port' => env('REDIS_PORT', '6379'),
|
|
'database' => env('REDIS_CACHE_DB', '1'),
|
|
],
|
|
|
|
],
|
|
|
|
];
|