За последние 24 часа нас посетили 7423 программиста и 760 роботов. Сейчас ищут 329 программистов ...

Soft deletion and uniqueness

Тема в разделе "Laravel", создана пользователем artoodetoo, 20 июл 2021 в 09:00.

Метки:
  1. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.516
    Симпатии:
    1.073
    Адрес:
    там-сям
    Предлагаю обсудить тему . Ограничимся Laravel и MySQL для определённости.

    Суть проблемы:
    Допустим у нас есть таблица с профилями пользователей `users` и мы хотим чтобы данные не удалялись совсем, а только помечались как удалённые (поле deleted_at типа timestamp содержит непустое значение). Сложность возникает когда нам нужно уникальное поле, например email. С точки зрения пользователя или бизнеса, будет очень странным если система будет отказывать в регистрации пользователя с неким правильным имейл адресом, хотя такого адреса в системе [ уже ] нет. То есть он мог быть, но был "удалён" и сейчас его как бы нет.

    Я убеждён, что органичения базы данных должны служить последним рубежом валидации и гарантии непротиворечивости данных. Уникальный индекс даёт бо́льшую защиту, чем валидатор на бекенде и тем более на фронтенде. В хорошей системе будут все три барьера.

    Если добавить буквально уникальный индекс на поле email, то "удалённые" и рабочие записи будут равноправны. Не получится добавить запись с email, который уже был, хотя он был "удалён". Получается, что нам надо чтобы активные записи имели уникальные значения, а "удалённые" могли бы быть неуникальны.

    Можно попытаться добавить композитный индекс на email+deleted_at, но тут нас поджидает сюрприз: так как deleted_at может содержать NULL, то записи с пустым полем deleted_at (не удалённые) не будут проверяться на уникальность! По крайней мере в MySQL это работает так. Проверьте или поверьте.

    Есть такое решение: объявить deleted_at как TIMESTAMP NOT NULL или INT NOT NULL. И использовать свой перекрытый трейт MySoftDeletes который вместо null работает с unix epoch, например. Таким образом записи будут уникальными для неудалённых версий и не обязательно уникальными для удалённых, т.к. отметка времени у удаленных разная.

    Другой вариант: использовать отдельное поле deleted INT NOT NULL DEFAULT 0 и при удалении копировать в него значение id. Мягкое удаление оставить как есть, а уникальный индекс объявить по email+deleted.

    И то и другое имеет свои минусы. Ваши предложения?
     
  2. don.bidon

    don.bidon Новичок

    С нами с:
    28 мар 2021
    Сообщения:
    218
    Симпатии:
    28
    Голосую за email+deleted, но я бы предпочёл удалённых пользователей и прочую требуху переносить в таблицы с префиксом "deleted_", если есть такая возможность, иначе работа с актуальными данными будет со временем замедляться.
     
  3. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.516
    Симпатии:
    1.073
    Адрес:
    там-сям
    Это тоже вариант со своими издержками.
     
  4. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.099
    Симпатии:
    510
    Когда пользователь удаляется, предполагается, что его персональная инфа (мыло) удаляется вместе с ним сразу или через опред. промежуток времени. Ну, меняйте при удалении его мыло на <user_id>@yourhost.ru и не парьтесь ;)
    --- Добавлено ---
    P.S. А в течение этого «опред. промежутка времени» пользователю можно давать возможность восстановить акк (не давать возможности регаться с этим мылом, что юник на поле с мылом уже обеспечивает).
    --- Добавлено ---
    Или @example.com :D
     
    #4 miketomlin, 20 июл 2021 в 14:05
    Последнее редактирование: 20 июл 2021 в 14:21
  5. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.516
    Симпатии:
    1.073
    Адрес:
    там-сям
    Ох, нет. Это не вариант. ))) Мягкое удаление придумано как раз для того, чтобы данные сохранялись. users.email здесь просто для примера. Проблема куда более общая.
     
  6. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.099
    Симпатии:
    510
    @artoodetoo, вам никто не запрещает «тырить» данные молча :D

    Как выше написали, удаленные все равно надолго не остаются в осн. таблице ;)
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.516
    Симпатии:
    1.073
    Адрес:
    там-сям
    Не знаю откуда такие предположения. Временность не является условием задачи. Мягкое удаление нужно чтобы сохранять данные о "неактивных" или возможно "ошибочных" записях, если важно сохранять о них информацию или иметь возможность "реактивации".
    --- Добавлено ---
    Например, на форуме ты не откроешь профиль забаненного пользователя, но на него по прежнему могут ссылаться его посты, у него есть история и она может пригодиться (компетентным органам lol). Допускаю что великий Админ может даже разбанить (мы не можем).

    Не надо предполагать что "удаленная" информация имеет какой-то срок хранения! И наоборот, срок хранения, если такой понадобится, не надо увязывать с мягким удаленим. Не зря придумали принцип единственной ответственности.

    --- Добавлено ---
    Вот что за манера заваливать топик побочкой! :D
     
  8. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.099
    Симпатии:
    510
    @artoodetoo, меня по-прежнему не покидает ощущение, что ты путаешь какой-то «плэйсхолдер» в таблице (которого если не во всех, то во многих случаях может просто не быть) с записью для хранения какой-то полезной инфы. Но ОК, не буду больше «заливать топик побочкой». Даже если эта «побочка» – бочка, из которой все в основном и пьют :)
    --- Добавлено ---
    Вот мне прям оч. интересно, как «всемогущий Админ» будет «разбанивать» пользователя, чье мыло уже заюзано др. пользователем :)
    – Веришь, что раньше это было мое мыло (акк)?
    – Смотря сколько дашь.

    Я уже молчу о том, что бан и удаление – это немного разные вещи. ЧЁ реально, если ты меня забанишь, я смогу повторно зарегаться с тем же мылом? :)
     
    #8 miketomlin, 20 июл 2021 в 18:59
    Последнее редактирование: 20 июл 2021 в 19:18
  9. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.034
    Симпатии:
    1.616
    Когда-то делал подмену email на uniqid() при удалении, с сохранением бывшего мыла в другом поле. На том проекте прокатило, хотя и вызывает вопросы решение
     
  10. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.099
    Симпатии:
    510
    Зачем был нужен еще один uniqid, причем сомнительный, когда есть user_id? :) В любом случае это для ТСа «побочка». Он идет своим путем.
    --- Добавлено ---
    Вообще держать «архивные» данные можно где угодно. Но по-моему самое прямое решение – это в спец. таблицах. Там можно и мыло спокойно не делать юником, и оригинальные id при необходимости сохранить (можно даже их использовать в первичном ключе архива), и время удаления, чтобы не захламлять осн. таблицу, и т.д. ЧЁ хоШь, то и храни. У нас везде так.
     
    #10 miketomlin, 20 июл 2021 в 21:50
    Последнее редактирование: 20 июл 2021 в 21:58
  11. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    10.516
    Симпатии:
    1.073
    Адрес:
    там-сям
    @miketomlin не понял, что за плейсхолдер.

    Я не предполагал что ты так заведешся. :) И недостаточно знаю движок зенфоро, не знаю есть ли в нем более близкий аналог мягкого удаления. Просто пытался найти наглядный пример, наверное неудачно.

    Про архивные таблицы всё ясно, это вариант.
    --- Добавлено ---
    Восстановить пользователя должно быть невозможно, пока есть другой активный аккаунт с таким же адресом. Сначала надо деактивировать тот другой акк.

    Думаю такие пробоемы в публичном сервисе можно решать через подтверждение мыла. Кто сумел, тот и прав.
     
  12. mkramer

    mkramer Суперстар
    Команда форума Модератор

    С нами с:
    20 июн 2012
    Сообщения:
    8.034
    Симпатии:
    1.616
    Чтоб чем-то заполнить поле email, и оно не пересеклось с другими, примерно так:
    PHP:
    1. $deleted->emailBeforeDeletion = $user->email;
    2. $user->email = uniqid();
    А при восстановлении можно обратно переписать правильный майл
     
  13. miketomlin

    miketomlin Старожил

    С нами с:
    9 авг 2016
    Сообщения:
    3.099
    Симпатии:
    510
    Если удаляемый пользователь наплодил какие-то сущности, и ты не хочешь удалять их «каскадом» вслед за пользователем, переносить это творчество в архив или сливать в какой-то спец. акк, ты на месте записи пользователя оставляешь заглушку, уникализированную для всех пользователей, в том числе и будущих, причем так, чтобы она не порождала конфликтов по персональным учетным данным (мыло, телефон и т.п.). Оставляешь разве что ник, если он у тебя есть, чтобы никто не пытался прикидываться удаленным пользователем. Хотя ник тоже можно уникализировать.

    Не хочу, чтобы ты выдумывал более сложную конструкцию. Но если сильно охота использовать групповой ключ, для удаленных сохраняй в deleted вместо возведенного флага ненулевое уник. число (тот же user_id). При большом желании deleted_at тоже можно уникализировать подобным образом (есть фрактальная часть, при нехватке можно и секунды под эти цели задействовать).
     
    #13 miketomlin, 21 июл 2021 в 10:17
    Последнее редактирование: 21 июл 2021 в 10:31
  14. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    6.990
    Симпатии:
    1.142
    Адрес:
    Лень
    лучше +unix метку вместе. Иначе история как с md5 приключится, значения разные, а хэш одинаков.