Правильное использование структур данных поможет оптимизировать скорость/память кода. В больших продуктах каждая оптимизация умножается на миллионы/миллиарды сессий пользователей. Так я сэкономил Uber $22 000 в год используя базовые знания структуры данных Set.

Проблема

Часто используемый скрин приложения отправлял impression аналитику при прокрутке экрана с дублированием. Например если вы видели на экране “XYZ” 10 раз прокрутив контент вверх вниз несколько раз, то аналитика отправлялась 10 раз.

Я поговорил с нашими аналитиками и они сказали что им достаточно 1 раза отправки данного события. Они добавили что делают дедупликацию этих событий на своей стороне.

Решение

Я добавил дедупликацию аналитики перед отправкой. Ниже упрощенный псевдо код:

// Было
logAnalytics(for: uuid)

// Стало
let uuids = Set<String>()
...
if uuids.insert(uuid).inserted {
    logAnalytics(for: uuid)
}

Результат

На графике ниже показан объём аналитики для конкретного события. С момента выпуска оптимизированного кода (красная стрелка) видно падение в количестве событий. Новый код сократил расходы на аналитику этого события на $22 000 в год.

Падение в конце графика это не очередная оптимизация. Сервер тоже не падал. Данные аналитики поступают с задержкой. Поэтому новые события еще не поступили.

Вывод

Знание структур данных поможет оптимизировать код. Чем больше продукт, тем больше эффект оптимизации.


Спасибо за прочтение чернового варианта статьи Азизбеку Матчанову, Айгуль Джуманазаровой, Муслимбеку Абдуганиеву.

Обо мне

Меня зовут Дархонбек. Автор ИТ блога 10x Engineer. Я работаю iOS разработчиком в Сан Франциско в компании Uber.

Связаться со мной можно по почте: darkhonbek.interview@gmail.com

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


  1. Zuy
    03.05.2022 04:08
    +60

    У этих 3х абзацев ещё и черновой вариант был?! Чуствую все таки дождусь когда статьи на хобре в виде твитов появятся.


    1. SDKiller
      03.05.2022 11:10
      +3

      У этих 3х абзацев еще и tldr есть в телеграм-канале автора


    1. goldrobot
      03.05.2022 11:36
      +1

      Чуствую все таки дождусь когда статьи на хобре в виде твитов появятся.

      Как бы вам помягче рассказать...


  1. agent10
    03.05.2022 07:26
    +23

    А причём тут структуры данных? Если бы я использовал List, то что-то изменилось бы в логике?


    1. randomsimplenumber
      03.05.2022 08:50
      +24

      Вот видите, знать 2 структуры данных ещё лучше ;)

      ЗЫ: автор конечно же молодец. Почти как дорожник, наконец заасфаальтировавший дырку в асфальте, и сэкономивший сотне автомобилистов ресурс ходовой.


    1. bugy
      03.05.2022 09:29
      +11

      Справедливости ради, проверка contains в List'e может занимать изрядное время. Насколько это критично зависит от количества и качества данных

      ПС видимо автор Set таки и не знает, иначе бы не добавлял эту проверку


      1. askharitonov
        03.05.2022 10:07
        +1

        ПС видимо автор Set таки и не знает, иначе бы не добавлял эту проверку

        А почему? Он ведь проверяет наличие uuid в uuids для того, чтобы определить, отправлять ли информацию, или она уже была ранее отправлена, а не для того, чтобы избежать повторного добавления данных в Set.


        1. Gordon01
          03.05.2022 10:32
          +3

          .contains() для

          List: O(min(in-i))

          Set: O(1)~


          1. askharitonov
            03.05.2022 10:39

            Если я правильно понял, комментарий в постскриптуме не относился к List, а был просто про Set.


          1. reatfly
            03.05.2022 11:41
            +1

            Вы еще забыли константу перед сложностью, и для небольших размеров в десятки записей линейный поиск по вектору (я про С++, где вектор - это непрерываня область памяти; не знаю, как лист а iOS устроен) быстрее и выгоднее, чем любые сеты/мапы.


        1. bugy
          03.05.2022 10:44

          Теоретически да, но мне кажется там просто неправильный код. Вряд ли они отправляют все uuid каждый раз при добавлении одного элемента.


        1. mayorovp
          03.05.2022 12:48
          +4

          Потому что во многих языках у множеств операция add возвращает boolean, и скорее всего можно было написать вот так:


          if uuids.add(uuid) {
              logAnalytics(for: uuid)
          }

          Ещё бы понять что это вообще за язык, в хабах только Swift указан, но там у множества метода add нету.


        1. GerrAlt
          03.05.2022 23:48

          А если бы просто сложил все в сет без проверки на наличие, и потом уже отправил весь полученный сет возможно сэкономил бы еще больше (особенно если там пакетная обработка возможна)


    1. sshikov
      03.05.2022 09:30

      > Если бы я использовал List, то что-то изменилось бы в логике?
      Внутри Set как правило хеш таблица.


      1. randomsimplenumber
        03.05.2022 09:46
        -2

        А внутри list что?


        1. sshikov
          03.05.2022 09:51
          +12

          Что-то другое (массив или связанный список). Хеш таблиц внутри list точно нет.


          1. AnthonyMikh
            03.05.2022 21:54
            -1

            Если только речь не идёт о PHP


            1. sshikov
              03.05.2022 22:30
              -1

              Ну… это скорее исключение, подтверждающее правило. Там же поди строковые ключи, а не целочисленные индексы?


            1. mayorovp
              04.05.2022 10:24

              В PHP и структуры данных-то такой нет


              1. eandr_67
                04.05.2022 15:50

                Списки в PHP есть: www.php.net/manual/ru/class.spldoublylinkedlist.php, а множества элементарно реализуются через массивы.

                Но речь выше была о том, что массив в PHP — достаточно необычный тип данных, одновременно совмещающий в себе и линейный, и ассоциативный массивы.


                1. mayorovp
                  04.05.2022 15:59

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


    1. lamerok
      03.05.2022 09:36
      +1

      Ну наверное поиск по списку был бы дольше. О(n) , против O(1) в среднем.


      1. agent10
        03.05.2022 09:40
        +2

        И? Логика то бы не изменилась. Как будет отправляться аналитика один раз так и будет. Цимес в том, что автору помогли не знания структур данных(отличие Set от List), а понимание предметной области и процессов внутри проекта.


        1. lamerok
          03.05.2022 12:23
          +2

          Логика нет, а скорость да. Если список большой, может прилично так тормознуть.


          1. agent10
            03.05.2022 13:10
            +5

            И что? Экономия в бабле случилась из-за идеи не отправлять кучу ненужной аналитики, а не от выбора между set и list.


    1. vladimirelizarov89
      03.05.2022 10:26

      Да.

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

      Если бы был list, то одинаковые события так бы и сыпались и сохранялись как и раньше, тк list позваляет хранить внутри себя одинаковые элементы.


      1. agent10
        03.05.2022 10:32
        -2

        Это смотря в каком языке. В Java, скажем, метод contains использует для сравнения метод объектов - equals. А значит для одинаковых строк(как в примере), List будет работать также как и Set. Просто дольше.


  1. CoolCmd
    03.05.2022 08:15
    +15

    я не согласен с автором, потому чт


    1. justmara
      03.05.2022 12:35
      +12

      наша группа копирайтеров всё ещё вычитывает черновой вариант комментария


      1. CoolCmd
        03.05.2022 16:03
        +2

        говорите тише — Азизбек, Айгуль и Муслимбек еще спят (про часовые пояса не забываем)


      1. Muslimbek_Abduganiev
        04.05.2022 17:53
        +1

        Лол. Мы просто пытаемся минимазировать недочёты в постах у друг друга.

        P.S.

        Спасибо Дархонбеку Маматалиеву и Азизбеку Матчанлву за ревью чернового варианта данного коментария.


  1. saipr
    03.05.2022 08:27
    +8

    Знание структур данных поможет оптимизировать код.

    Для начала позволит создать работающий код.
    P.S.


    В больших продуктах каждая оптимизация умножается на миллионы/миллиарды пользователей.

    И сколько же у нас МИЛЛИАРДОВ пользователей?


    1. masv
      03.05.2022 08:47
      +6

      Не только пользователи страдают от вездесущей, всеятормозящей, трафикожрущей телеметрии.


  1. nerudo
    03.05.2022 09:11
    +5

    Одного меня взволновал вопрос, как еще соптимизировали аналитику в самом конце графика?


    1. censor2005
      03.05.2022 09:14
      +8

      Сервер уронили )


    1. lamerok
      03.05.2022 09:42
      +2

      Запретили прокручивать. После 10 прокруток в бесплатной версии, дальше 10 баксов в месяц на безлимитные прокрутки.


    1. ssj100
      03.05.2022 11:15
      -2

      Сразу видно человека который не работал с аналитикой.

      Она полностью отображается через 1-2 дня, а сегодняшние значения они в основном лежат на нуле


      1. darhonbek Автор
        04.05.2022 17:58

        @ssj100все верно. Аналитика поступает с задержкой


  1. ssurrokk
    03.05.2022 10:05

    почему именно 22 000 $ , как считали?


    1. ialexander
      03.05.2022 10:43
      +4

      Этот момент как раз не вызывает вопросов, если вкратце $22000 - виртуальные попугаи.

      Компании с достаточно обширной инфраструктурой ведут мониторинг используемых ресурсов, как минимум сколько каждое приложение в данный момент потребляет CPU и сколько памяти, делается это как минимум для того, чтобы «сошедшее с ума» приложение которое внезапно начинает потреблять много ресурсов можно было прибить, чтобы оно не аффектило другие приложения.

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

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

      К CPU/RAM можно добавить и другие компоненты - жесткий диск, сеть. Тут уж что важно для компании и что можно посчитать.

      Ну и походя к сути вопроса, после внедрения той фичи метрика показала падение использования ресурсов эквивалентного 22000 попугаев в год.


    1. aPiks
      03.05.2022 11:53

      Если использовать для сбора аналитики какой-нибудь segment, то количество запросов будет равно количеству уплаченных денег.


  1. soundengineer
    03.05.2022 10:26
    +3

    Только ногами не пинайте, я не настоящий сварщик, но нельзя ли как-то последовательно пронумеровать элементы на экране числами от 0 и выше? Тогда для хранения информации о том, что аналитика по элементу отправлена, хватило бы битового вектора, где id элемента - это позиция бита в векторе. Думаю, что одного единственно int на экран хватило бы за глаза. Ну и пара битовых операций к нему. Быстрее просто некуда.


    1. ialexander
      03.05.2022 10:49
      +2

      У разных пользователей могут показываться разные элементы на экране, поэтому нужны глобальные идентификаторы. Какие-нибудь A/B тесты проводят.


    1. merinoff
      03.05.2022 11:15
      +1

      Так вроде это и сделано, у объектов вычисляются хэши. А что в функции logAnalytics мы не знаем, может там по уиду находится объект и отсылается все состояние.


    1. elve
      03.05.2022 11:17
      +2

      когда-нибудь он освоит и эту структуру =)


  1. Wyrd
    03.05.2022 11:07
    +11

    Например если вы видели на экране “XYZ” 10 раз прокрутив контент вверх вниз несколько раз, то аналитика отправлялась 10 раз. Хоть 1 раза было достаточно.

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


    1. Kotsusamu
      03.05.2022 11:15
      +5

      Вот вот, обычно аналитикам крайне важно, сколько раз и как было показано ключевое слово/фраза/картинка.

      Чета тут не то...


      1. ialexander
        03.05.2022 11:59
        +1

        Их больше метрики типа кликов на показы интересуют. Притом показ пользователю должен считаться один раз за сессию, скроллинг не должен генерировать новые показы. Похоже автор топика ещё и баг исправил. У них теперь CTR должны немного подрасти.


    1. kubk
      03.05.2022 11:54
      +4

      Очень верное замечание. Такими темпами можно вообще аналитику удалить и сэкономить ещё больше непонятно как посчитанных денег.


  1. dimka11
    03.05.2022 12:57
    +1

    Про  $22 000 экономии в год немного смешно, явно один разработчик получает в год больше.


    1. sshikov
      03.05.2022 13:05
      +3

      Ну так, если вы не заметили — эту статью писал один человек, и трое вычитывали. Видимо, одного по итогу сократили, но осталось еще трое. Еще есть куда расти.


  1. astralNord
    03.05.2022 12:59
    +2

    -Босс, я придумал как фирме экономить 2 тысячи баксов в месяц!
    -Решил уволится?

    Не, фикс, наверно полезный для Uber, но памятуя какие у них зарплаты вспоминается этот анекдот.


  1. yukon39
    03.05.2022 13:26
    +4

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

    До кучи, писать в такой «лог» чревато потерей информации — представленный фрагмент кода просто отбрасывает ЛЮБЫЕ новые сообщения с тем же идентификатором.


    1. makar_crypt
      03.05.2022 15:20
      +2

      Сто процентов , только хотел написать. Проблема явно в архитетуре , а это попытка замазать проблему. Ну а по поводу статьи , больше самопиар


    1. nochkin
      03.05.2022 18:47
      -1

      Справедливости ради, там написано "дедупликация аналитики", а не "дедупликация UUID". Это не одно и тоже.

      Да и по логике видно, что идёт проверка на наличие UUID, а не проверка на его уникальность.


  1. hellamps
    03.05.2022 15:20
    +6

    Я правильно понял, что в конце графика бэкенд упал с ООМ?

    Этот сет по-хорошему должен быть ограниченным по количеству LRU list


  1. yegreS
    03.05.2022 21:43
    +1

    Проблема

    Часто используемый скрин приложения отправлял impression аналитику при прокрутке экрана с дублированием. Например если вы видели на экране “XYZ” 10 раз прокрутив контент вверх вниз несколько раз, то аналитика отправлялась 10 раз. Хоть 1 раза было достаточно.

    А с каких пор для аналитики это стала проблема? мне кажется это автор своим решением сделал проблему для аналитиков


  1. Ar2r
    04.05.2022 19:46
    +3

    Тот момент, когда комментарии читал намного дольше, чем статью.