В таблице чужие звонки, счета, фамилии. Поддержка списывает это на фильтр, следующий релиз прячет колонку, а через месяц то же самое делает выгрузка или ночное письмо. Продукт по-прежнему один. Обещание каждому клиенту было другим: его записи остаются его.
Лекция про схему базы тут не нужна. Нужны места, где граница обычно рвётся, вопрос перед следующим отчётом или интеграцией, и проверка двумя входами. Скорость — соседняя история. Отчёт, который открывается быстрее и всё равно показывает чужую компанию, лучше не стал. Ниже только граница. Заменять продукт я не предлагаю.
1. Отчёт — место, где путаницу наконец видят
Клиент не смотрит на схему. Он видит своё имя в углу и чужие цифры в таблице. Обычно это отчёт: звонки за месяц, неоплаченные счета, сравнение филиалов. Его собирали, когда компания была одна, или собирал человек, который видит всех и не заметил, что фильтр необязательный.
Слабое место почти всегда список компаний. Он помнит последнюю, в нём есть «все», до которого дотягивается клиент, или в письме уже зашит чужой id. Экран проверяли под администратором. Администратору как раз положено видеть всех. Клиенту нет. Вход администратора обещание не проверяет.
Возьмите два настоящих клиентских входа или два тестовых, которым нельзя видеть друг друга. Откройте один и тот же отчёт под каждым и запишите строки. Крутилка вас не интересует. Интересует фамилия, филиал или сумма, которые принадлежат другому входу. Если отчёт ещё и долго грузится, секунды засеките потом, на том же экране. Как разбирать ожидание, не меняя продукт: отчёты, из-за которых люди ждут. Не дайте ожиданию спрятать чужие строки и не расширяйте запрос «раз уж открыли», пока граница не держится.
2. Ночной файл и письмо живут отдельно от экрана
Экран может быть чистым, а задача, которая ночью собирает файл или шлёт сводку, нет. Её писали как «открой базу и пройди по строкам». Когда появился второй клиент, задача продолжила открывать ту базу, которую ей дали, или все строки подряд, потому что клиента так и не положили в сообщение, с которого она стартует.
На одной CRM у каждого филиала была своя база. Миграции и индексы собирали под этот разрез, а не приклеивали потом «для скорости». У фоновой работы было второе правило: повторный запуск не шлёт второе письмо и не пишет вторую оценку. И воркер отказывался от соединения, которое не совпадало с филиалом в сообщении. Задача, которая открывает «ту самую» базу и верит тексту, спокойно запишет строки одного филиала как строки другого. Очередь разницы не видит. Клиента называет сообщение, а воркер обязан отвергнуть соединение, которое с этим именем не сходится.
Спросить это можно, не читая воркер. Какая задача шлёт почту или собирает файл? Какого клиента этому запуску можно касаться? Что будет, если она стартует дважды? Если ответ «берёт основную базу», уточните, что такое «основная», когда клиентов уже несколько. Как устроен безопасный повтор и почему второй процесс не лечит путаное сообщение: когда работа уходит из веб-запроса.
3. Кто имеет право видеть всех
Кто-то должен видеть поперёк клиентов: вы, поддержка, иногда агентство, которое внедряет продукт. Сам доступ не баг. Баг — учётка, которая переключает компании, а экран не говорит, чьи данные открыты. Или переключение, которое оказалось закладкой и её переслали. Или общий пароль, оставленный в чате. Строки «утекли» руками человека, который думал, что смотрит свою компанию.
Текущая компания должна быть написана на экране. Переключение — отдельное действие, после которого остаётся след. Один пароль на всех, кто когда-либо был на созвоне, не подходит. Когда агентство отдаёт продукт новому разработчику, эта учётка — сюрприз, который должен лежать в первой записке, а не всплывать на живой системе. Пакет такой передачи: что агентство готовит до открытия чужого Laravel.
4. Поиск, новая интеграция, новая колонка
Поиск путает компании тише отчёта. Поле смотрит по всем, потому что индекс построили на общей таблице и клиента в него не положили. Нет индекса — поиск кажется сломанным, потому что долго грузится. Нет границы — поиск кажется странным, потому что нужная фамилия находится у чужой компании. Это разные поломки. Ускорить поиск, не закрыв границу, значит привозить чужие строки раньше. На той CRM индексы были частью разреза по базам филиалов, а не попыткой разогнать общую кучу.
Интеграция рвёт границу ещё тише. Телефония, сообщения, бухгалтерия, оценка звонка: внешняя система присылает идентификатор, ваш продукт пишет строку. Если воркер верит идентификатору и пишет в базу, которая уже была открыта, звонок садится не в тот филиал. На том продукте интеграции жили по тому же правилу, что отчёты. Сообщение называло филиал, воркер брал его базу. Бухгалтерии и переписке не делали исключение на том основании, что это «просто синхронизация».
Перед следующим отчётом, выгрузкой, полем поиска или интеграцией допишите в запрос одну строку: проверено двумя клиентами, которые не должны видеть друг друга. Если единственное место для проверки — прод, не гоняйте её по живым клиентам как первую попытку. Копия, даже черновая, — место, где кривому запросу позволено показать чужие строки. Лезть в боевой продукт «посмотреть, держится ли фильтр» — та самая ночная правка без свидетеля, о которой речь в заметке почему это плохо выяснять на живом сайте.
Своя база на филиал — рабочая форма, если миграции и индексы собраны вокруг разреза. Общая база на всех тоже работает, если каждый путь обязан назвать клиента и вы это доказали двумя входами. Ломается путь, который забыл. Чужой чертёж не доказывает, что у вас безопасно, и не повод переписывать продукт до того, как вы дважды открыли отчёт.
5. План на этот продукт, не на новый
Сегодня: два входа на отчёте, которому люди уже верят, и на одной выгрузке или письме. Запишите, что увидел каждый. Дальше: список задач и интеграций, у каждой — клиент, которого запуску можно касаться, и что разрешено повтору. Потом: каждый новый экран начинается со строки про двух клиентов. Замена продукта — разговор на потом, и только если текущий эту строку удержать не может. Чаще может. Его выпустили без проверки.
Когда двух входов уже мало
Открыть два входа и увидеть чужую фамилию в таблице можно без разработчика. Зовите, когда второго входа не достать, когда база у задачи — пожатие плечами, когда интеграция уже написала строки и непонятно, чьи они, или когда единственное предложение — ускорить запрос, в котором клиента по-прежнему нет.
На продукте, у которого уже есть клиенты, я начинаю с границы, потом с отчёта или задачи. Услуги — SaaS и backend, отчёты вокруг них и интеграции, которые пишут обратно. Проекты держат результат рядом с операцией, в том числе CRM, где у филиала была своя база. Если нужен второй взгляд, напишите в LinkedIn название отчёта и что показали два входа. Выгрузку клиентских строк не присылайте.