При разработке сложных WordPress-решений, особенно в B2B-сегменте, часто возникает необходимость обслуживать несколько клиентов (тенантов) в рамках одной установки. Это может быть сеть интернет-магазинов, система управления заказами для разных компаний или платформа для курсов. Стандартные таблицы WordPress (wp_posts, wp_usermeta) неплохо справляются с базовой изоляцией благодаря префиксам, но когда дело доходит до кастомных таблиц с уникальными данными каждого клиента, разработчики сталкиваются с серьезной проблемой: как гарантировать, что данные тенанта A не утекут к тенанту B?
WordPress изначально не проектировался как мультитенантная платформа. Механизм мультисайта (WPMS) решает задачу изоляции на уровне сайтов, создавая отдельные таблицы для каждого под-сайта. Однако, при разработке кастомных решений (например, плагинов для бронирования, CRM или аналитики) разработчики часто создают собственные таблицы в базе данных. Если не предусмотреть механизм изоляции, данные всех клиентов будут перемешаны. Это не только нарушает конфиденциальность, но и может привести к юридическим последствиям (GDPR, HIPAA).
Существует три основных подхода к организации мультитенантной архитектуры в контексте кастомных таблиц:
В этой статье мы сосредоточимся на третьем подходе, как наиболее популярном в экосистеме WordPress.
Рассмотрим ключевые техники, которые помогут обеспечить изоляцию:
В WordPress Multisite каждый сайт имеет свой префикс таблиц (например, `wp_2_`). При создании кастомных таблиц обязательно используйте `$wpdb->prefix`, чтобы таблица создавалась с префиксом текущего сайта. Это автоматически изолирует данные между сайтами.
Если вы не используете WPMS или хотите изоляцию внутри одного сайта (например, для разных ролей), добавьте в каждую кастомную таблицу столбец `tenant_id` (или `site_id`). Критически важно: каждый SQL-запрос (SELECT, INSERT, UPDATE, DELETE) должен содержать условие `WHERE tenant_id = %d`. Никогда не полагайтесь на то, что разработчик или другой плагин добавит это условие позже.
Современные СУБД (MySQL 8.0.13+, PostgreSQL) поддерживают RLS. Вы можете создать политику, которая автоматически добавляет фильтр по `tenant_id` для всех запросов, идущих от данного пользователя БД. Это обеспечивает защиту даже в случае ошибки в коде приложения.
Используйте UUID вместо автоинкремента для первичных ключей. Это предотвращает угадывание ID записей другого тенанта (например, при загрузке /order/123).
Рассмотрим пример создания кастомной таблицы для хранения заказов в мультитенантном плагине.
// Создание таблицы
function myplugin_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'orders';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
tenant_id bigint(20) NOT NULL,
order_data text NOT NULL,
created_at datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY tenant_id (tenant_id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
// Вставка данных с tenant_id
function myplugin_insert_order($order_data) {
global $wpdb;
$tenant_id = get_current_tenant_id(); // ваша функция получения ID тенанта
$wpdb->insert(
$wpdb->prefix . 'orders',
array(
'tenant_id' => $tenant_id,
'order_data' => maybe_serialize($order_data)
),
array('%d', '%s')
);
}
// Выборка данных с обязательным условием
function myplugin_get_orders() {
global $wpdb;
$tenant_id = get_current_tenant_id();
return $wpdb->get_results($wpdb->prepare(
"SELECT * FROM {$wpdb->prefix}orders WHERE tenant_id = %d",
$tenant_id
));
}
Обратите внимание на индексацию столбца `tenant_id` — это критически важно для производительности.
| Пункт | Статус |
|---|---|
| 1. Все кастомные таблицы используют префикс текущего сайта (для WPMS) | ☐ |
| 2. В каждой таблице есть столбец `tenant_id` с индексом | ☐ |
| 3. Каждый CRUD-запрос содержит условие `WHERE tenant_id = %d` | ☐ |
| 4. Используются подготовленные запросы (`$wpdb->prepare`) | ☐ |
| 5. Идентификатор тенанта никогда не берется из пользовательского ввода (URL, POST) | ☐ |
| 6. Включено логирование подозрительных запросов (например, отсутствие tenant_id) | ☐ |
| 7. Настроены политики RLS на уровне БД (опционально) | ☐ |
| 8. Проведено тестирование с несколькими тенантами на проникновение | ☐ |
Изоляция данных на уровне кастомных таблиц в WordPress — это не просто вопрос архитектуры, а вопрос доверия клиентов и соответствия регуляторным требованиям. Наиболее практичный подход для большинства проектов — это общая таблица с столбцом `tenant_id` и строгая фильтрация на уровне запросов. Использование WPMS с автоматическим префиксом таблиц добавляет дополнительный уровень изоляции. Не забывайте о производительности: правильное индексирование и кэширование (например, через Redis с ключом, включающим tenant_id) помогут избежать деградации при росте количества клиентов. Помните: лучше потратить время на проектирование изоляции сейчас, чем потом экстренно исправлять утечку данных.







