Анализ мультитенантной изоляции данных на уровне кастомных таблиц WordPress

Время чтения: 7 минут
Есть вопросы? Мы в соц сетях

При разработке сложных WordPress-решений, особенно в B2B-сегменте, часто возникает необходимость обслуживать несколько клиентов (тенантов) в рамках одной установки. Это может быть сеть интернет-магазинов, система управления заказами для разных компаний или платформа для курсов. Стандартные таблицы WordPress (wp_posts, wp_usermeta) неплохо справляются с базовой изоляцией благодаря префиксам, но когда дело доходит до кастомных таблиц с уникальными данными каждого клиента, разработчики сталкиваются с серьезной проблемой: как гарантировать, что данные тенанта A не утекут к тенанту B?

Вступление: Проблема мультитенантной изоляции

WordPress изначально не проектировался как мультитенантная платформа. Механизм мультисайта (WPMS) решает задачу изоляции на уровне сайтов, создавая отдельные таблицы для каждого под-сайта. Однако, при разработке кастомных решений (например, плагинов для бронирования, CRM или аналитики) разработчики часто создают собственные таблицы в базе данных. Если не предусмотреть механизм изоляции, данные всех клиентов будут перемешаны. Это не только нарушает конфиденциальность, но и может привести к юридическим последствиям (GDPR, HIPAA).

Типы мультитенантности в WordPress

Существует три основных подхода к организации мультитенантной архитектуры в контексте кастомных таблиц:

  • База данных на тенант (Database per Tenant): Каждый клиент получает отдельную базу данных. Максимальная изоляция, но сложность в управлении и высокая стоимость. В WordPress используется редко.
  • Таблица на тенант (Table per Tenant): Для каждого клиента создается отдельная таблица с суффиксом (например, `orders_tenant_1`, `orders_tenant_2`). Хорошая изоляция, но может привести к большому количеству таблиц.
  • Общая таблица с идентификатором тенанта (Shared Table with Tenant ID): Все данные хранятся в одной таблице, но каждая запись содержит столбец `tenant_id`. Самый гибкий и экономичный подход, но требует строгой фильтрации.

В этой статье мы сосредоточимся на третьем подходе, как наиболее популярном в экосистеме WordPress.

Методы изоляции данных на уровне кастомных таблиц

Рассмотрим ключевые техники, которые помогут обеспечить изоляцию:

1. Использование префиксов таблиц (для WPMS)

В WordPress Multisite каждый сайт имеет свой префикс таблиц (например, `wp_2_`). При создании кастомных таблиц обязательно используйте `$wpdb->prefix`, чтобы таблица создавалась с префиксом текущего сайта. Это автоматически изолирует данные между сайтами.

2. Столбец `tenant_id` и обязательная фильтрация

Если вы не используете WPMS или хотите изоляцию внутри одного сайта (например, для разных ролей), добавьте в каждую кастомную таблицу столбец `tenant_id` (или `site_id`). Критически важно: каждый SQL-запрос (SELECT, INSERT, UPDATE, DELETE) должен содержать условие `WHERE tenant_id = %d`. Никогда не полагайтесь на то, что разработчик или другой плагин добавит это условие позже.

3. Row-Level Security (RLS)

Современные СУБД (MySQL 8.0.13+, PostgreSQL) поддерживают RLS. Вы можете создать политику, которая автоматически добавляет фильтр по `tenant_id` для всех запросов, идущих от данного пользователя БД. Это обеспечивает защиту даже в случае ошибки в коде приложения.

4. Глобальные идентификаторы

Используйте 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) помогут избежать деградации при росте количества клиентов. Помните: лучше потратить время на проектирование изоляции сейчас, чем потом экстренно исправлять утечку данных.

Мы разрабатывали
apeironspace
jivosite
мтс
originalvirginia
эльдорадо
eparcel
decken-wood
wildberies