Давным-давно, один специалист по базам данных (из тех, бородатых и уже седых) сказал мне, что метки времени (timestamp) — это самая сложная тема в базах данных. Я ему, правда, не поверил, но приколы со временем реально встречаются.

Есть стандартная проблема, которую часто вижу в чужих данных. Положим собрались вы отслеживать события/действия пользователя. Обычно у вас будет это делать некий код (JS в вебе или SDK для аппов), который будет слать данные серверу.

Каждому событию нужна метка времени. И есть выбор из двух: локальное время на клиенте или время получения события сервером. Один хороший совет что делать и загадка без ответа под катом

Серверное время:

  • Плюс: Полный контроль над точностью времени, форматом данных и часовым поясом. Всё стандартно, всё работает.
  • Минус: На метку времени влияют лаги сети. Более того, если это приложение для смартфона, то наверняка вы используете загрузку данных партиями, чтобы минимизировать использование сети. Обычно события от пользователя хранятся локально пока не наберётся достаточно (например, 10) и затем они сливаются все за раз. Особую важность эта тактика, если работаете с развивающимися рынками, где больше половины устройств это максимально дешёвый Android подключенный через EDGE. В результате данные приходят партиями и у них одна временная метка на всех. Понять порядок и время между событиями не получается. Вот, кстати, другой похожий пример с хабра.

Клиентское время:

  • Плюс: Даёт точные данные о порядке событий на клиенте и времени между ними
  • Минус: Вы будете удивлены как часто у юзеров на девайсе установлено некорректное время!


По моему опыту от 1% до 5% пользователей (я смотрю по разным проектам и аудиториям) живут в далёком прошлом или даже в будущем. Я, честно сказать, не понимаю зачем они это делают.



Я заметил, что особенно много таких пользователей на Филиппинах и в Японии.



Эта проблема – реальный кошмар. Она ломает все запросы об активности пользователей в целом, а это самые популярные вопросы в аналитике.

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

Так что же делать? Просто хранить обе метки времени и использовать более подходящую в зависимости от ситуации. Как всегда, больше данных — лучше. Но сделать это часто забывают. Затем и пишу, чтобы не забывали.

P.S. Вопросы залу:
  • Другие способы?
  • Есть идеи, что не так с японцами и филиппинцами?
Поделиться с друзьями
-->

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


  1. zelyony
    06.06.2016 09:20
    +5

    на клиенте хранить локальное время: localEventTime1,… localEventTimeN
    на момент передачи передавать последнее локальное время: localTransmitTime
    на сервере пересчитать на серверное время для N-ого события: clientX.serverEventTimeN = serverTransmitTime — (localTransmitTime — localEventTimeN)


    1. andreymal
      06.06.2016 11:13

      Это сработает, только если между localEventTime1 и localEventTimeN время не слетит ещё раз


      1. zelyony
        06.06.2016 11:18

        подписывайся на события смены времени и обновляй локальные временные метки

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


        1. andreymal
          06.06.2016 11:20

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


  1. Beanut
    06.06.2016 10:55
    -10

    Unix time


  1. TimsTims
    06.06.2016 10:56
    +1

    > Другие способы?
    Передавать разницу времени между событиями:
    Событие1: было 115секунд назад
    Событие2: было 23 секунды назад
    Событие3: 0 секунд назад(последнее)

    > Есть идеи, что не так с японцами и филиппинцами?
    Думается мне, что там много любителей игр на смартфонах, в которых реализованы разного рода Cooldown'ы: когда перезарядка умения/способности/предмета/строительства занимает определенное время. Например: здание достроится через 24 часа. Ты меняешь дату в телефоне и вуаля — здание уже достроено. А перемотав сразу на месяц ты увидишь, что на твоих счетах в виртуальных банках скопилась огромная сумма. Только думаю, что некоторые просто забывает вернуть дату назад.

    Есть еще предположение о Филиппинах, что телефоны производят синхронизацию часовых поясов с базовыми станциями, и есть предположение что раз беда по всей стране, то их настаривают криворукие неопытные умельцы.


  1. imhuman
    06.06.2016 10:57
    +2

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


    1. NNikolay
      06.06.2016 10:59

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


      1. TimsTims
        06.06.2016 12:44

        Если прям совсем закрыть такую «дыру», то в игру будут играть еще меньше. Те, кто очень хочет играть — пусть так и делают, никому хуже не будет. Если таких задротов выгнать из игры, то она потеряет в популярности, и уж точно наберет среди знакомых антирейтинги, мол: «эта игра настолько жестокая, что забанила меня, не советую играть в неё!», в то время как такие задроты вполне неплохо делают рекламу твоей игре.

        А те, кто так делать не додумался — с них и можно стричь деньги


  1. Shamov
    06.06.2016 11:53
    -1

    Тоже мне проблема! Каким бы странным время на клиенте не было, разница между ним и временем сервера в любом случае либо вообще фиксированная, либо поддаётся статистическому усреднению. Таким образом, за конечное (весьма небольшое) число запросов/ответов, эту разницу можно вычислить с точностью, достаточной для любых практических применений. У пользователя может быть какой угодно часовой пояс, и дата может быть по китайскому календарю. Главное, чтобы у него время шло строго вперёд со скоростью одна клиентская секунда за секунду сервера.


  1. grossws
    06.06.2016 12:44
    +1

    Клиентское время:
    Даёт точные данные о порядке событий на клиенте и времени между ними

    Не даёт, банальное обновление времени по ntp/от сотовой сети или перевод часов ломает эту линейность времени на ура. В равной степени это относится и к server-side ts, но там оно обычно более контролируемо.


    1. NNikolay
      06.06.2016 12:56

      Я в целом согласен, идеального в мире ничего нет. Но думается, что время не так часто переводится. В большой картине эти выбросы должны будут усредниться.


  1. Alexeyslav
    06.06.2016 13:46
    +1

    Батарейка в компьютере садится. В зависимости от чипсета начинаются сначала мягкие глюки — время начинает спешить или отставать с заметной скоростью, затем начинает страдать календарь.
    Но эта проблема всплывает очень быстро — как только пытаешься воспользоваться безопасным соединением система начинает ругаться на недействительные сертификаты, которые либо ещё не выданы либо уже просрочены. Но пока пользователь столкнётся с проблемой и решит её проходит время, он запускает приложение и сервер видит неправильное и странное время на клиенте. Самый правильный способ избегать таких проблем — настойчиво предупреждать клиента что даты сервер/клиент отличаются более чем на 2 суток(с запасом на часовые пояса и т.д.), а если отличаются больше чем на неделю — не допускать работу такого клиента.


  1. gsaw
    06.06.2016 14:35
    +1

    Если такие проблемы со временем, то не лучше ли использовать просто время приема данных на сервере + номер события. Ну и как подсказывают дополнительно хранить разницу во времени между событиями если хочется временные характеристики.


  1. M-A-XG
    06.06.2016 14:50

    Порядок событий можно отслеживать по ИД событий… :)


  1. NLO
    06.06.2016 17:21

    НЛО прилетело и опубликовало эту надпись здесь


    1. Shamov
      06.06.2016 17:44

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


      1. Vit_Am
        07.06.2016 11:10

        Идея Mixim333 наиболее подходящая под задачу, сам задумался о нечто подобном. А лаг можно приблизительно рассчитать, несколько раз послав запрос на сервер и замерив время получения ответа, затем усреднить эти данные, тем самым вычислив приблизительную задержку самой сети.


        1. Shamov
          07.06.2016 11:14

          Только лучше никому об этом не говорите. Я за такое решение чуть раньше минус получил :)


        1. grossws
          07.06.2016 15:09

          посмотрите на то, как работает ntp, всё давно придумано


        1. Alexeyslav
          07.06.2016 15:22

          Пробелма в том что єто надо будет делать постоянно. На каждую отправку данных надо будет измерять задержку сети, а не только в момент первичной синхронизации — со временем задержка может меняться от 10мс до пары секунд в зависимости от загрузки каналов передачи данных, изменения могут быть как длительные так и кратковременные.


          1. Vit_Am
            07.06.2016 18:44

            А зачем постоянно? Один раз получили наиболее точный timestamp и с момента получения запустили таймер, а дальше уже отталкиваемся от показаний таймера.


            1. Alexeyslav
              07.06.2016 22:56

              И через месяц аптайма получим ошибку в 15...30 секунд. Если повезёт, конечно, порядка 5 секунд.


              1. Shamov
                08.06.2016 10:27

                Вот это дискретизация! Если нужно делать что-то чаще, чем раз в месяц, то значит это нужно делать постоянно :)


    1. Alexeyslav
      07.06.2016 08:41
      +1

      Клиент не должен ничего прибавлять, что ему помешает прибавить на год вперёд? С таким же успехом можно просто отправлять время прошедшее с запуска клиента.
      Но и тут подстерегает ряд засад — как бы ни выкручивались но время на сервере и на клиенте идёт 1) с разным темпом, 2) с разрывами.
      Что если клиент подключен к серверу и аптайм соединения составляет год? За это время на клиенте время уйти может на час-другой а то и больше. Я вот видел сервер у которого часы шли на 1-2 сек в сутки быстрее и это ещё вполне приемлемая точность. Если там прикрутить периодическую синхронизацию времени, то будет возникать проблема в момент синхронизации — некоторые события внезапно придут в будущем или прошлом нарушив последовательность.
      Так же надо предусмотреть ситуацию когда клиент уйдёт в гибернацию на час-другой например, а потом вернётся — счётчик времени может быть нарушен в зависимости от способа реализации подсчёта времени.
      И если на физической машине можно обнаружить событие входа в гибернацию и выхода, то как быть с виртуальными машинами где вообще может не быть такого признака?
      По этой причине в Win7 и старше нет подсчёта времени аптайма, только время последнего старта…


      1. grossws
        07.06.2016 15:12

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


        В нормальных ОС ядро уже давно умеет небольшие подстройки делать не нарушая направление течения субъективного времени в userspace. Например, adjtime:
        If the adjustment in delta is positive, then the system clock is speeded up by some small percentage (i.e., by adding a small amount of time to the clock value in each second) until the adjustment has been completed. If the adjustment in delta is negative, then the clock is slowed down in a similar fashion.


        1. Alexeyslav
          07.06.2016 15:26

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


      1. NLO
        07.06.2016 18:16

        НЛО прилетело и опубликовало эту надпись здесь


        1. Alexeyslav
          07.06.2016 22:11

          Даже если не ломать у таймера будет постоянно накапливаться ошибка. К тому же нет причин не доверять системному счетчику микросекунд, который как раз и служит для этих целей и реализован аппаратно в каждом ядре процессора. Но даже эти счётчики могут рассинхронизироваться из-за ошибки БИОСа на AMD-процессорах.
          И даже если всё будет реализовано идеально, время между клиентом и сервером неизбежно будут расходится, при аптайме в месяц-другой ошибка запросто набежит в пол минуты и больше. Что с этим делать?


          1. grossws
            08.06.2016 04:46

            И кроме того, требовать модуля ядра для игрушки (или прав суперпользователя, если аппаратные таймеры экспонированы в userspace) — несколько слишком.


          1. NLO
            08.06.2016 16:59

            НЛО прилетело и опубликовало эту надпись здесь


            1. Alexeyslav
              08.06.2016 23:27

              Я тоже не думал о таком счётчике, пока не столкнулся с проблемой ТВ-тюнера на двухядерном AMD-процессоре, суть заключалась в том что приём команд с пульта программно-аппаратный и сильно зависит от синхронизации ядер процессора, если система использует аппаратные счетчики и БИОС неправильно инициализирует ядра процессора(баг такой) они в дальнейшем начинают работать несинхронно, за пол часа набегает разница в сотни микросекунд и команды с пульта постепенно перестают распознаваться. Никогда бы до этого не додумался если бы не форум посвящённый этому тюнеру, проблема оказывается известная… И это проблема на ровном месте на локальном компьютере, что уж говорить об интернете и разной скорости хода системных часов, там эта разница набежит ещё быстрее.
              Если вдруг кто наткнётся на аналогичную проблему, то решение заключается в том чтобы заставить ядро системы использовать программный таймер вместо аппаратного счётчика при помощи ключа /usepmtimer в boot.ini


            1. Alexeyslav
              09.06.2016 12:33

              Даже ссылку на проблему вспомнил… http://www.ice-graphics.com/ICEAffinity/IndexR.html