Представьте ситуацию: братья-близнецы — назовём их Дима и Саша — стали клиентами одного и того же банка. У братьев совпадают отчество, фамилия, дата рождения, а ещё — адрес прописки и домашний телефон.

В 2014 году в этом самом банке мы начали внедрять MDM-систему для клиентских данных. И вместе с братьями-близнецами — а точнее с их клиентскими карточками — много всего натерпелись  пережили.

Сегодня я расскажу, как одна чужая ошибка откликнулась нам спустя несколько лет и какие уроки мы вынесли из истории с очень похожими данными близнецов. А еще — как допиливали продукт, исходя из весьма небанальной ситуации. 

Под катом почти детективная история и много технических деталей.

История близнецов. Начало

Я — Миша Берёзин, в HFLabs я отвечаю за продукт «Единый клиент». Рассказ будет честным, так что я сразу скажу, что братья-близнецы были ещё и сотрудниками того самого банка. А значит, еще на первом этапе для нас было более чем очевидно, что они — разные люди. Для каждого из братьев существовали отдельные клиентские записи.

В 2014 году в банке работала легаси-система — CRM с механизмом ручной дедупликации. Дата-стюард при обнаружении дубликатов мог слить две карточки в одну. И в один прекрасный день именно это случилось с карточками братьев. Данные Димы и Саши признали дубликатами в CRM и объединили в одну карточку-кентавра (дисклеймер: ручная дедупликация – зло). Оттуда обновление пришло в «Единый клиент». А он тогда был ещё очень добродушным и слишком сильно верил исходным системам. 

Тут сделаю несколько важных замечаний про «Единый клиент»
  1. «Единый клиент» не является точкой ввода данных и не изменяет их в источниках. Задача CDI-системы (Customer Data Integration) брать все-все данные из источников, чистить, дедуплицировать и создавать эталонное представление этих данных.

  2. «Единый клиент» хранит всю историю изменения данных, которая до него доходила. Это даёт возможность откатить состояние на любой момент в прошлое. А также использовать эти данные для дополнительного анализа.

  3. «Единый клиент» не даст признать карточки похожими исключительно по воле оператора. Дубликаты могут быть найдены и объединены полностью автоматически на основе гарантированных «сильных» правил. Либо автоматически же помечены как потенциальные «слабые» дубликаты, требующие дополнительного признания человеком.

Итак, в момент времени T1 в нашу MDM-систему пришла карточка Димы с идентификатором CRM:1024. Эта карточка жила, объединялась с другими данными, в ней собирали эталонный профиль Димы. Потом в легаси-системе произошло некорректное изменение данных, связанное с ошибочным объединением. Для «Единого клиента» это выглядело как обновление данных в карточке CRM:1024: в момент времени Т2 имя «Дима» сменилось на имя «Саша». Мы его честно применили, нашли у этой карточки новые дубликаты с другими карточками Саши и объединили. В итоге получили карточку-кентавра уже в MDM.

Исправили эту ситуацию в два этапа:

  1. Откатили ошибочное слияние в «Едином клиенте» — благо при объединении карточек сохраняется история и все исходные данные. Так что можно без проблем вернуться на любой момент в прошлое.

  2. Исправили в легаси-системе ошибочное изменение данных. Для нас это выглядело как обновление данных в карточке CRM:1024 с Саши обратно на Диму.

В результате мы получили такое итоговое состояние данных в «Едином клиенте»: у карточки CRM:1024 в истории есть изменения Дима → Саша → Дима. 

После разделения ошибочно слитых карточек в «Едином клиенте» никакого дополнительного статуса не проставили. Потому что с таким состоянием данных карточки не найдутся как дубликаты.

На какую мысль навела нас эта история? Доверяй, но проверяй. Абсолютно точно не стоит верить всем изменениям данных в исходных системах. Изменения могут быть ошибочными, случаются проблемы в интеграциях. Да и мошеннические действия, когда данные одного человека подменяют данными другого, никто не отменял.

Поэтому в начале 2016 года мы сделали фичу мониторинга подозрительных изменений. Если «Единый клиент» видит значительное изменение данных, которые меняются в исключительно редких случаях, то карточка блокируется до ручного анализа. Пример — полная смена ФИО. В дальнейшем появились разные политики автоматического разрешения конфликтов, но это отдельная история.

Второй заход

Казалось, история закончена. Но это было бы слишком просто ;) 

Тут надо сделать небольшое отступление. Дело в том, что в развитии клиентской MDM-системы всегда действуют две противоположные силы.

  1. С одной стороны, система должна найти и автоматически объединить как можно больше дубликатов.

  2. С другой стороны, она не должна совершать ошибочных объединений. То есть не должна объединять разных людей.

Мониторинг конфликтных изменений внутри «Единого клиента», который мы рассмотрели ранее, работает на втором направлении.

При этом мы постоянно работаем над фичами и по первому направлению. Такой фичей является выявление и использование в поиске дубликатов прошлых значений ФИО и документов. В 2017 году мы научили «Единый клиент» анализировать все изменения данных и, если они подходят под определенные критерии, создавать прошлое значение. Такие данные полезны, так как люди иногда меняют документы, фамилию и приходят в компанию как новые клиенты.

Чтобы создать прошлые значения и по историческому объёму данных (а мы его храним, как уже сказали в первой части), мы специально сделали отдельную задачу.

А теперь интересный момент: и мониторинг конфликтных изменений, и создание прошлых значений «смотрят» на изменение данных и принимают решения. Как понять, конфликт это или прошлое значение?

Для ФИО по итогу нашего анализа мы сделали такое базовое разделение:

  • если достаточно сильно изменяется два и более компонента — это конфликт;

  • если меняется только один компонент — это прошлое значение.

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

Как результат — примерно в 2018-м, после обработки исторического объёма данных для инициализации прошлых значений, карточки братьев снова слились воедино!

Хорошо, достали мы прошлое значение. Но ведь мы уже разъединяли эти карточки, почему же их снова слили? А потому что мы не сохранили никакого статуса при их разделении в 2014 году. Не думали, что это нужно. Тем не менее, эту историю мы довольно быстро разрулили. А заодно доработали двумя фичами историю разъединения и объединения.

Первая фича — статус «Разлит». Добавили признак «Разлит» для пары разъединённых карточек.

Казалось бы статус и статус, в чём тут сложность? А в том, что карточки продолжают жить и меняться — объединяться и разъединяться. А значит, мы должны постоянно актуализировать этот статус. 

Пара примеров для наглядности.

Итак, мы разъединяем объединённую карточку HID1024 на две, добавляем между ними статус «Разлит».

Теперь представим, что статус «Разлит» стоял между одиночной и уже объединённой карточкой (слитой из нескольких одиночных, которые были признаны дубликатами). А после этого объединённая продолжает разделяться.

Все подобные сценарии необходимо учесть и правильно обработать. Мы сделали это в 2018 году.

Вторая фича — транзитивный анализ статусов. Блокировали слияние большой группы дубликатов при наличии внутри хотя бы одной разлитой пары.

Всё хорошо, когда мы рассматриваем только пары дубликатов. Но в реальной жизни бывает по-разному. Карточка A может быть похожа на карточку B, а карточка B на С и так далее. В этом случае А и С могут не быть прямыми дубликатами, но транзитивно они дубликатами являются. А что, если А и С не только не прямые дубликаты, а вообще разделённые или отклонённые?

В 2020 году мы научили систему полностью анализировать весь граф дубликатов перед слиянием. И блокировать его при наличии подобных противоречий. А в 2021 добавили ещё и анализ отсутствующих рёбер. Иначе говоря, даже если между А и С нет никакого статуса, «Единый клиент» проверит, нет ли там конфликта данных.

Мы разобрали самый простой кейс группы дубликатов, состоящей из трёх карточек. Чтобы понять масштаб сложности, приведу пример реальной объединённой карточки с демонстрацией найденных рёбер дубликатов в ней. Внимание на картинку: 

Это не какой-то исключительный сложный случай, а вполне обычная карточка в системе, в которой данные живут и меняются с 2014 года.
Это не какой-то исключительный сложный случай, а вполне обычная карточка в системе, в которой данные живут и меняются с 2014 года.

После этих доработок между Димой и Сашей уже встал статус «Разлит», прошлое значение было удалено. И мы думали, что теперь-то уж точно всё надёжно (ха!). 

Финальный акт

Однако восстановление прошлых значений происходит периодически — в случаях,  когда мы дорабатываем механизм или находим у себя какие-то баги. То есть задача порой запускается, и… в начале 2022 года она опять восстановила в карточке Саши прошлое значение! Система не учла тот факт, что когда-то прошлые значения закрыли вручную. 

Восстановим хронологию. В мае в «Единый клиент» пришла заявка Саши из заявочной системы банка. В ней был указан его мобильный телефон и мобильный телефон брата в качестве резервного. CDI видит, что есть заявка, в которой совпадают:

  • ФИО (оно ровно такое же, как и предыдущее ФИО в другой карточке); 

  • дата рождения; 

  • номер телефона.

Поскольку три важных компонента совпали, CDI применяет гарантированное правило. И решает, что перед ней один и тот же человек. Но пока ещё не объединяет, так как у нас есть статус «Разлит» и транзитивный анализ всего графа дубликатов!

Но всё же что-то снова пошло не так…

Слияние дубликатов может выполняться в «Едином клиенте» в двух процессах:

  1. Во время работы задачи «Слияние». Она запускается по расписанию не реже раза в сутки и обрабатывает всё, что осталось неслитым в данный момент.

  2. В онлайне. Для этого в API есть специальный метод, который под капотом делает сохранение, стандартизацию данных, поиск новых дубликатов и объединение.

В онлайн-методе с начала времён существовала оптимизация, благодаря которой объединяются только ближайшие рёбра графа дубликатов. Без полного анализа группы. И это работало нормально, потому что вероятность получения большой новой группы гарантированных дубликатов в онлайне или накопления её за сутки мала. Слияние ближайших ребёр не нарушает правил транзитивности.

Вернёмся к нашей картинке: 

У нас есть новая карточка Саши с телефоном Димы, то есть с подмешанными данными и одного, и другого. Она находится как гарантированный дубликат для обеих.

Обновляем в онлайне карточку Саши (с прошлым значением «Дима») методом, который вызывает слияние дубликатов. Так как он работает с оптимизацией, мы видим только старую пару «Дима-Саша». Она нас не интересует, поскольку имеет статус «Разлит». А пара «Саша-Саша» хорошая, гарантированная. Её мы объединяем.

Так как это слияние пары, а не группы, происходит ошибочная актуализация статуса «Разлит» «Дима-Саша». Он закрывается, поскрольку более актуальным считается гарантированный дубликат. 

Следом прилетает ещё один вызов этого же метода, и мы радостно объединяем Сашу с Димой, так как у них достаточно хорошо всё совпадает.

Это самый настоящий обидный баг, порождённый забытой оптимизацией в коде… Классика:)

Развязка

Но банк — внимание! — на уровне шины предусмотрел блокировку в случае, если приходит два идентификатора интернет-банка (золотая карточка каждого из братьев содержала исходную карточку из интернет-банка). Поэтому кейс отправился на ручной разбор, а близнецы на непродолжительное время лишились доступа к счетам в личных кабинетах. 

Буквально через несколько часов проблему мы решили. А на следующий день стали разбираться, что пошло не так. 

Оптимизацию выкинули, переписав за пару дней весь механизм онлайн-слияния. Для сохранения SLA по времени ответа в случае редких кейсов сверхмассивных групп дубликатов теперь будем отправлять их в асинхронную обработку.

Интересный факт в заключение: все это почти не повлияло на перфоманс. Первоначальную реализацию делали очень давно. Это прям классический техдолг, за который пришлось дорого заплатить.

Что ж, продолжаем развиваться! Сражаться за качество данных с энтропией, которая, как известно, постоянно растёт. Ну а задачу инициализации прошлых значений мы научим не создавать заново данные, которые были закрыты, — это тоже полезное улучшение.

Всё это не могло произойти там, где CDI появился год–два назад. Просто потому, что для этого должны были сойтись сразу несколько факторов: и длительная жизнь карточек внутри банка, и множество обновлений и слияний, и изменения самого CDI. Но вся эта история подкинула нам несколько нетривиальных задачек, а мы в HFLabs такое любим.

С другой стороны, не будь в банке «Единого клиента», ошибочное слияние и его последствия, возможно, не удалось бы раскрутить. И кто знает, как бы и когда это откликнулось банку и братьям-близнецам. Так что CDI однозначно добавляет прозрачности в работе с данными.

Больше историй про данные — в нашем телеграм-канале.  

P. S. Все совпадения случайные, имена героев вымышленные.

Комментарии (7)


  1. paranoid_sonata
    30.06.2022 11:24
    +2

    Досадно, и хорошо, что разобрались. Но как можно поменять Диму на Сашу?! Я понимаю, если было бы какое-нибудь сходство, но тут два совершенно различных имени. Рассказывали другой случай. Одна молодая женщина решила взять ипотеку на квартиру в Москве, не в самой разумеется Москве. Продала квартиру в провинции и собралась внести в качестве залога, или как-то иначе это называется, не знаю, не брал ипотеку ни разу. Банк отказал в ипотеке и заблокировал этот залог по причине того, что она не вернула якобы кредит. Оказалось, что её полная тёска (то есть имя и фамилия полностью совпадают, насчёт отчества не помню) брала когда-то кредит и не вернула. Потом вроде разобрались, что это была не наша героиня, но страху она натерпелась очень много. Всё таки два лимона тоже деньги, а это было четыре года назад, то есть до обвала рынка недвижимости.


    1. baldr
      30.06.2022 11:36
      +1

      У знакомого была точно такая же ситуация. Он внезапно выяснил что где-то через полстраны его полный тезка, правда с другой, кажется, датой рождения, взял кредит, а коллекторы (видимо, сперва банк) повесили долг на него и начали звонить. Имя и фамилия были очень распространенными, так что есть некоторый смысл называть детей более редкими именами.

      А банкам - использовать только номер паспорта для объединения карточек клиентов.


      1. paranoid_sonata
        30.06.2022 14:12

        До эпохи социальных сетей я думал, что сочетание ФИО у меня уникально, но теперь очевидно, что я ошибался. Номер паспорта это да, если паспорта не отменят в будущем.


  1. ercm
    30.06.2022 14:30

    Как по мне, так типичный "китайский комсомол". Вместо того, чтобы использовать удостоверение личности (раз уж они клиенты банка, а тем более и сотрудники при этом), взяли неуникальные данные и мучительно решали проблемы, как их отличить.


    1. chipQA
      30.06.2022 18:17

      Не поверите, но поправили не только имя, но и удостоверение личности, там же просто опечатка в один символ была...


  1. Old_Fabler
    30.06.2022 16:37
    +3

    Н-да...

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

    Пляски с бубнами были еще те. Но давно это было, во времена, когда все работало под Novell NetWare...

    А что до статьи...

    На мой скромный взгляд, проблема поставлена слишком узко, поэтому и не вызовет интереса у большинства читателей. Если привязать ее к более широкой проблеме дублирующих записей в базе... А если еще и олицетворить героем (например, как в комедии близнецов), то читательский интерес можно было бы значительно поднять.

    Что до проблемы "технического долга". Это собственно и не технический долг вовсе. Это проблема становления системы, когда проявляются скрытые ранее контексты. В истории науки и техники таких примеров полно.

    Вот, к примеру, ракета "Ариан". Разработчики улучшили управление двигателями (абсолютно объективно), а ракета разбилась потому, что старые рули не справились с новым "шустрым" двигателем.

    Или, скажем, кораблекрушение парохода "Истленд", когда на и так неустойчивый пароход навешали дополнительных спасательных шлюпок (после трагедии "Титаника" к безопасности стали относиться строже). В результате пароход перевернулся прямо у пирса, похоронив вдвое больше людей, чем "Титаник".
    Я рад, что вы успешно справляетесь с подобными проблемами. Желаю успехов.


    1. Skigh
      30.06.2022 17:42
      +1

      Или, скажем, кораблекрушение парохода «Истленд»… пароход перевернулся прямо у пирса, похоронив вдвое больше людей, чем «Титаник».
      Uhmm, actually… :)
      Вдвое меньше — 844 жертв против примерно полутора тысяч Титаника.