1. Куча / Говнокод #20128

    +5

    1. 1
    2. 2
    3. 3
    4. 4
    //Сегодня у меня вопрос по говнокоду.
    //Как у нас на говнокодике реализовано сколько непросмотренных комментариев в каждом треде?
    //Неужели на каждого юзера заводится счётчик и инкрементится при добавлении в каждой теме?
    //Неужели на каждого юзера на каждый комментарий заводится флаг, сообщающий какой комментарий прочитан, а какой нет?

    Запостил: LispGovno, 03 Июня 2016

    Комментарии (38) RSS

    • Вопрос ребром
      Ответить
    • > Неужели на каждого юзера на каждый комментарий заводится флаг

      Лень сейчас копаться в коричневом золоте исходников, но на всяких CMS делают по-другому: на каждого юзера заводится счётчик, сколько комментариев было в каждой теме на момент предыдущего просмотра. Количество непрочитанных — это разница между текущим состоянием и запомненным значением счётчика. Таких счётчиков понадобится не более, чем 20 к × 14 к = 280 к.

      Если на каждый комментарий для каждого юзера заводить флаг, то всего флагов будет 330 к × 14 к ≈ 4,6 млрд.

      Миллиардов, Карл!
      Ответить
      • Но ведь при заходе в чей-то говнокод с непрочитанными комментариями, непрочитанные отображаются желтым цветом. Тут одним счетчиком не обойтись
        Ответить
        • да
          желтые все комментарии, которые моложе твоего last_visit_time
          на каждый камент хранить видел ты его или нет не надо

          а вот +/- каментов реально хранится в логической модели { comment_id, user_id, mark, unique(comment_id, user_id) }
          Ответить
    • >Неужели на каждого юзера на каждый комментарий заводится флаг, сообщающий какой комментарий прочитан, а какой нет?
      что мешает хранить дату последнего обращения к треду и сравнивать с датой создания комментария

      >Количество непрочитанных — это разница между текущим состоянием и запомненным значением счётчика.
      для подстветки новых комментариев не сработает

      PS: исходники не смотрел, но осуждаю
      Ответить
      • > для подстветки новых комментариев не сработает

        Действительно, запоминаем дату предыдущего просмотра. Подсвечиваем те, которые новее этой даты.
        Ответить
        • зачем усложнять? ID последнего комментария
          SELECT count(id) FROM comments WHERE thread_id = $thread_id AND id > $last_user_comment_id
          Ответить
          • ненадёжно - сломается после переполнения id комментария
            Ответить
          • > ID последнего комментария

            Для этого нужно гарантировать, что ID комментариев строго возрастающие, а не просто уникальные. Т.е. нужна гарантия
            Id1 < Id2 <=> TimeOf(Id1) < TimeOf(Id2).
            Если для небольшого форума это ОК, т.к. сиквенсы в RDBMS такое гарантируют, то в системе чуть побольше с каким-нибудь шардированием это довольно трудно гарантировать.

            В принципе, с неудачным таймингом даже на одной машине такой подход может привести к неправильному поведению:
            Если одновременно происходит вставка двух комментариев и чтение поста, может произойти следующая последовательность операций (псевдокод):
            cid1 = NEXT(id_seq)
            cid2 = NEXT(id_seq)
            INSERT AND COMMIT COMMENT cid2
            SELECT POST COMMENTS FOR USER uid1
            UPDATE LAST_SEEN OF uid1 TO cid2
            INSERT AND COMMIT COMMENT cid2

            Так непрочитанный комментарий при следующей загрузке будет помечен прочитанным. Благо, цена ошибки невелика.
            Ответить
            • Fix: в последней строке
              INSERT AND COMMIT COMMENT cid1
              Ответить
            • > Если одновременно происходит вставка двух комментариев и чтение поста, может произойти следующая последовательность операций (псевдокод):

              Uhm... я не уверен про какой NEXT() идет речь, но типично: если текущий номер лежит в какой таблице, то его увеличение будет покрыто транзакцией. Поэтому `cid2 = NEXT(id_seq)` подвиснет и будет ждать пока результат `cid1 = NEXT(id_seq)` не будет закомиттен.

              > [...] с каким-нибудь шардированием это довольно трудно гарантировать.

              почти классическая проблема распределенного программирования. без синхронизации не решается.
              Ответить
              • > я не уверен про какой NEXT() идет речь, но типично: если текущий номер лежит в какой таблице, то его увеличение будет покрыто транзакцией.

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

                Открываем доку, к примеру, постгреса:

                nextval

                Advance the sequence object to its next value and return that value. This is done atomically: even if multiple sessions execute nextval concurrently, each will safely receive a distinct sequence value.

                Important: To avoid blocking concurrent transactions that obtain numbers from the same sequence, a nextval operation is never rolled back; that is, once a value has been fetched it is considered used and will not be returned again. This is true even if the surrounding transaction later aborts, or if the calling query ends up not using the value.

                -- https://www.postgresql.org/docs/current/static/functions-sequence.html


                Ну вы понели.
                Ответить
                • да. просто дешевый атомик. никогда ими не доводилось пользоваться.
                  Ответить
                  • > никогда ими не доводилось пользоваться.

                    А как ты записи в таблицы вставляешь? У тебя есть естественные уникальные ключи у всех объектов?

                    Мускульный AUTO_INCREMENT также работает, вот выдержка про оракловый сиквенс:

                    When a sequence number is generated, the sequence is incremented, independent of the transaction committing or rolling back. If two users concurrently increment the same sequence, then the sequence numbers each user acquires may have gaps, because sequence numbers are being generated by the other user.

                    -- https://docs.oracle.com/cd/B28359_01/server.111/b28286/statements_6015.htm
                    Ответить
                    • > У тебя есть естественные уникальные ключи у всех объектов?

                      Да. Типично. Я давно такого сам не делал. Там где я больше всего с базами работал, там модели и прочее были уже давно сделаны. (Но ни одного CREATE SEQUENCE я не видел, к слову.)
                      Ответить
                      • > ни одного CREATE SEQUENCE

                        если не говорить про случаи, когда айдишник генерится арифметикой (например, ууид, или источник записи сам прекрасно следит за уникальностью (например, там арифметика его личного счетчика и его айдишника)), то:

                        в оракле до 11 включительно не было никакого autogenerate, поэтому для вставки генерирующегося айдишника тебе надо написать триггер, работающий before insert when (new.id is null), который будет запрашивать nextval у сиквенса

                        сиквенс, понятное дело, надо создать до этого триггера

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

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

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

                        иногда люди настолько охуевают от безнаказанности, что делают таблицу my_fuckin_sequence(name, value), откуда до вставки в нужную таблицу сначала читают value, а потом обратно сбрасывают value+1 (а после уже ебутся с проблемами почему у меня айдишник дублируется - ну и похуй, значит надо сделать ещё одну попытку)

                        ну и особенно ебанутые мускулеводы могут ебошить insert into foo ..select max(id)+1 as id...from foo
                        тем тоже сиквенс не нужен
                        Ответить
                        • > ключительно не было никакого autogenerate, поэтому для вставки генерирующегося айдишника тебе надо написать триггер, работающий before insert when (new.id is null), который будет запрашивать nextval у сиквенса
                          > иногда люди ленятся и используют один сиквенс на всю схему, сквозно нумеруя все таблицы (на мой взгляд это ад - на ровном месте получили узкое горлышко инсертов всей схемы)

                          Каюсь. А если это гуиды, то чем-нибудь это грозит? Я правда не сиквенс использовал, а встроенную функцию генерации гуида нашел.
                          Ответить
                          • любой трейдоф это трейдоф

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

                            а с другой стороны, если у тебя айди сущностей в рестах используются, и у тебя
                            GET /api/foos/{fooId}/subfoos/{subfooId}/bars/{barId}, то видеть эти невъебенные гуиды в урлах, невъебенные гуиды в жсонах (когда они там друг на друга ссылаются, когда обмажутся этими гуидами и ябутца окаянныя, особенно, если выгрузить массив дочерних айдей!) и просто при отладке, когда приходится всматриватся, что же там за значение - тогда гуиды нахуй не интересны

                            это не говоря о том, что хранить 8 байтовый инт в 2 раза дешевле, чем 16 байтовое поле
                            Ответить
                            • опять же у гуида важное качество
                              он уникальный не просто в пределах твоей системы, он уникален в принципе
                              его можно отдавать в чужие системы и им будет норм
                              Ответить
                • Ну подожди. Читаешь ты при шардинге возможно из слейв БД, а комитишь в мастер. Оно или откатиться или закомититься, если не было успешных конкурентов за модифицированные строки
                  Ответить
                  • > Читаешь ты при шардинге возможно из слейв БД

                    Кстати, лол. С подходом выше каждое чтение должно ходить в мастер, т.к. меняет базу :)
                    Ответить
                    • весь день читаю тред не пойму почему сразу именно id надо?
                      для комментов треда есть created_time
                      для юзера для треда есть last_visit_time

                      время прекрасно "шардится" и ничего не блокирует в части вопроса "дай мне моё"
                      ntp хорошо синкает ноды с апп серверами и инстансы баз
                      наносекундная точность в этом вопросе лишняя

                      остается та же проблема
                      > Так непрочитанный комментарий при следующей загрузке будет помечен прочитанным
                      которую и решать не особо надо
                      Ответить
                      • Я не говорю, что использовать время - это плохо. Скорее наоборот, я пытаюсь ответить на вопрос, подчему надо "усложнять": http://govnokod.ru/20128#comment330837
                        Ответить
                    • > Кстати, лол

                      Чего-то я туплю, сама постановка задачи к этому ведёт. Вообщем, время рулит: так можно (когда-нибудь) пошардить по постам, и на каждой шарде хранить время, когда кто пост смотрел.
                      И не надо никакой глобальной синхронизации для айдишников комментов, шарды независимые.
                      Ответить
                      • > время рулит
                        Ну для говнофорума это может и подойдет, а для тех случаев когда надо точно определить список, например в транзакционной памяти в многопоточности или в самой реализации бд это бы уже не подошло. На время ориентироваться нельзя.

                        > время рулит
                        > пошардить
                        А потом на разных серверах время синхронизировалось в разное время))) Да что там, просто время синхронизировалось даже в рамках даже одной машины без шардов и уже неразбериха в постах.
                        Ответить
                • > Если бы сиквенсы были покрыты транзакциями, подозреваю, что всё бы нереально тормозило.

                  Необязательно. Можно попытаться развести вставку коммента (с внутренним IDом) и генерацию его публичного IDа в разные транзакции.

                  Или просто не парится: вероятность этого race condition достаточно низкая.
                  Ответить
                • да, понели

                  даже если транзакция откатилась, то все равно next увеличился
                  и слава богу)
                  Ответить
        • > Действительно, запоминаем дату предыдущего просмотра. Подсвечиваем те, которые новее этой даты.
          Вставили с 10ку комментариев одновременно на большом ресурсе и вот уже дата начнет врать что из этого ты просмотрел, а что нет.
          Ответить
    • показать все, что скрытоПо идее можно запоминать дату просмотра для каждого треда.
      Комменты не читал. Все кто выше - пидоры.
      Ответить
    • А комментарии в лиспе есть?
      Ответить

    Добавить комментарий