• Удаленка, но есть нюанс

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

    Есть, однако, и недостаток удаленной работы, о котором не пишут. Будучи удаленщиком, ты не будешь принимать решений. Вам просто не дадут полномочий. Вы будете делать только то, что скажет начальство и тимлид. Чтобы что-то внедрить или переделать, понадобится много сил — гораздо больше, чем у того, кто сидит в офисе и регулярно видится с начальством.

    Не нужно подробно описывать, почему так происходит. В любой группе людей коммуникация первична. Какие бы средства связи нас ни окружали — телефоны, Zoom, очки реальности, — общаться лицом к лицу проще. Поэтому сотрудник, который пьет кофе с боссом и ходит с ним в курилку, получит его расположение. Даже если офисный сотрудник что-то делает не так, его проще похлопать по плечу и наставить на путь истинный — в отличие от удаленщика, который появляется на экране раз в сутки.

    Будет неверно говорить о всех разработчиках, но мой опыт подтверждает сказанное выше. Где бы я ни работал, повторяется одно и то же. Если на горизонте появляется интересная задача, ее проработка достается тому, кто в тесном контакте с руководством. Это, кстати, не гарантирует навыков в разработке; гораздо чаще я вижу что-то вроде обратной зависимости.

    Может быть, вы пошли на обед, обдумывая новый сервис. В это время тимлид и коллега из офиса тоже пошли в “Жареный жир” и славно поболтали. К возвращению в офис уже все решено, формальности обговорены. На следующий день на созвоне вы излагаете свое видение сервиса, не зная, что в этом нет смысла. Вас вежливо выслушают и скажут, что решение отличное и его приберегут для будующих проектов. А пока что Джон уже приступил к разработке, и мы с нетерпением ждем.

    Сделав черновик, Джон с пафосом презентует его фирме, после чего его роль выполнена. Он уходит в другую команду или приступает к проработке нового проекта. Дальше наступает рутина удаленщика — поддержка этого чуда. Ваш выход! Моментально находятся краевые случаи, которые Джон не предусмотрел, недостаток тестов, банальные баги. Ты их чинишь. Дни становятся неделями, месяцами, годами. Когда проект заработает стабильно, вас перебросят на поддержку новой сырой поделки.

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

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

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

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

  • Запуск InDesign

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

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

    Но дело не в этом. После того, как окно распахнется на весь экран, программа вступает во вторую фазу: загружает внутренний экран с последними файлами и рекламой новых фич. Эта загрузка длится десять секунд. Целых. Десять. Секунд. Пока он не просрется, не видно последних файлов и кнопок создания нового документа.

    В итоге сидишь и тупишь в пустоту, пока что-то там не загрузится. Однако если нажать Ctrl+O, откроется диалог выбора файла. Выходит, программа прекрасно работает, даже если главный экран не загрузился! Если выбрать в диалоге файл с проектом, он откроется мгновенно. Можно не ждать эти 10 секунд, а открыть файл ручками и работать. Это подтверждает второе видео:

    Такие вот интересные программы Адоба.

    Чем-то это напоминает современные веб-приложения: программа загрузилась, но это присказка, не сказка. Мы только загрузили скрипты, далее они выгребут свое дерьмо с серверов и только затем покажут приложение. На телевизорах такое постоянно: первый прогрес-бар показывает загрузку приложения на уровне ОС, а приложение запускает второй прогресс-бар для загрузки своего барахла.

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

    Подозреваю, что упомянутый главный экран — ничто иное, как Хром + Node.js + React. Это видно по косвенным признакам: он похож на Слак и прочие поделки, которые не могут быть отрисованы частично, а только целиком. Возможно, скрипты ломятся в сеть, но блокируются из-за санкций или бог знает чего. Ожидание в 10 секунд похоже на таймаут, заданный при отправке HTTP-запроса. Спасибо, что хотя бы выставили его: по умолчанию он равен 30 секунд.

    Странно, что последние файлы отображаются внутри браузера, хотя никак не связаны с ним. Это банальная глупость, деградация разработки. Надо сказать, я не удивлен. В Адобе много что делают на Node.js, например дашборд, из которого ставятся приложения. Я как-то шарился по папкам и нашел ворох скриптов. Плюс часто выскакивает диалог с текстом “приложение node хочет доступ к такому-то сертификату.”

    В сотый раз повторю тезис: сапожник без сапог. Фирма, которая тридцать лет пишет настольные программы под Винду и Мак, не может нормально показать последние файлы. Для загрузки приложений делается поделка па Хроме и Js, глючная и тормозная. Деградация софта в угоду менеджменту: сделать тяп-ляп, но зато быстро и получить повышение.

    Хочется верить, что индустрия печати пересядет с иглы Адоба на что-то другое, но пока что просвета не видно.

  • Avoid code you cannot debug

    This is a small tip I’d like to share with Clojure programmers.

    In a project, avoid code you cannot debug. It’s simple: if you can put a tag like #debug or similar somewhere in the middle, run a test and hang in a debugging session, you’re good. But if you cannot, you’ll be in trouble one day.

    Thus, any kind of DSL or yet another “smart” solution is a source of potential problems. Take Meander, for example. Imagine I have a map like this:

    {:name "Ivan"
     :address {:city "Chita"}}
    

    and I want it to become this:

    {:name "Ivan"
     :city "Chita"}
    

    With Meander, I would write:

    (m/match
      {:name "Ivan" :address {:city "Chita"}}
      {:name ?name :address {:city ?city}}
      {:name ?name :city ?city})
    

    and it works fine. But one day, my datasource suddenly returns a user without an address:

    {:name "Ivan" :address nil}
    ;; or just {:name "Ivan"}
    

    which is completely fine because a user might have no address associated with them. By passing that map into m/match, I expect it to return {:name "Ivan" :city nil} but no: there will be an exception:

    (m/match
       {:name "Ivan" :address nil}
       {:name ?name :address {:city ?city}}
       {:name ?name :city ?city})
    
    Unhandled clojure.lang.ExceptionInfo
       non exhaustive pattern match
       {}
    

    The line “non exhaustive pattern match” tells nothing to me nor the ex-data does. The message is fuzzy, there is no context, the ex-data has nothing useful (it’s an empty map). Having such an entry in logs or Sentry would not help you in a bit.

    Moreover, you cannot debug it. The m/match macro expands into a huge block of code. Debugging it somewhere in the middle would be quite challenging.

    Now compare it with a plain function that splits the data step by step:

    (defn remap-user [entry]
    
      (let [{username :name
             :keys [address]}
            entry
    
            {:keys [city]}
            address]
    
        {:name username
         :city city}))
    

    First, it works with both maps:

    (remap-user {:name "Ivan"})
    => {:name "Ivan", :city nil}
    
    (remap-user {:name "Ivan" :address {:city "Chita"}})
    => {:name "Ivan", :city "Chita"}
    

    Second, I can always put a debugging tag into that function and observe the local variables, the state and even run some expressions. With Meander, it’s just impossible or only possible with certain effort.

    Third, if a city is required, I’d put something like this:

    (assert city "The city is missing")
    

    and get a clear exception I want.

    Vast Meander patterns are completely unmaintainable. Pass something weird and you’ll get a “non exhaustive pattern match” message with no idea about what went wrong.

    Finally, debugging is crucial. If you cannot hang in the middle of execution and observe the state, that’s bad. Most Clojure programmers believe it’s a special language liberating you from debugging errors, but it’s not true. Debugging has not gone anywhere even with such a great language as Clojure.

  • Пауза в Ютубе

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

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

    Со своими субтитрами Ютуб решил ловко: в момент паузы они приподнимаются. Но не могут же сдвинуться буквы на видео! Частично проблему можно решить, изменив размеры окна, но это уловки для бедных.

    Спрашивается, почему бы не вынести кнопки в отдельную полосу? Да, они займут место, ну и что? Почему проблему интерфейса рашают за счет контента? Почему интерфейс налазит на видео, вместо того чтобы быть снизу?

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

    В других сервисах бывает еще хуже. Поставил на паузу — и появляются темные градиенты, жирные блямбы, выезжают скрытые кнопки, рекомендации и все прочее. Я просто хочу что-то рассмотреть или прочитать, почему мне мешают? Дизайнер, добавь в угол экрана две белые палочки — знак паузы. И не в центр, потому что в центре самое нужное, а в угол. Этого хватит. Но нет, нужно загадить как можно больше пространства.

    Кстати, один из способов решить эту проблему — смотреть Ютуб в VLC, о чем я писал раньше. Слегка экстремально, но проблемы субтитров нет.

  • Корректура в PDF

    Если кто не знает, откуда столько ненависти к PDF, сейчас объясню.

    У PDF одно назначение — просмотр и печать документов. Это конечный артефакт, который получают при компиляции проекта — будь то Индизайн, Латех, что угодно. Править PDF после его получения — это то же самое, что править байтики в exe-файле, вместо того, чтобы поправить код.

    Один раз я даже писал про возможности Preview по работе с PDF, но все это уловки для бедных, когда нет исходника.

    Тезис о том, что с PDF нельзя работать как с документом, подтверждает следующий опыт: середина 2023 года, тридцать лет с момента появления PDF — и не найти программы, которая бы работала с ним без ошибок.

    Если конкретно, то я готовлю вторую книгу к печати. Заказал корректуру, говорю: готов распечатать и прислать любой службой за мой счет. Нет, берем только PDF. Ладно, выслал, получил обратно с комментариями.

    Теперь самое интересное. Внос правок сводится к шагам: перешел к очередному комментарию, поправил исходник, удалил коммент. Что же пошло не так? А вот что.

    Preview на Mac M1 не может удалить некоторые комментарии. Те, что с типом Highlight — да, а Note — нет. Специально проверил, старый Preview на Интеле может, но поведение неочевидно. В целом работа с аннотациями в Preview глючная: коммент может висеть еще долго после удаления. Иногда фокус смещается произвольно и удаляется не тот коммент.

    Не все это можно передать в картинках, но например: слева у комментария есть опция удаления, а справа у Note — нет.

    Для демонстрации других косяков нужно записывать гифки, к чему я не готов.

    Вы скажете — браузер. Не вопрос, открыл в Хроме, а там такое:

    Плюс комменты нельзя удалять, read-only.

    Может, Акробат наше все? Ну, уже сам факт, что установка требует sudo, внушает опасения. Ладно, поставил. Документ не открывается перетаскиванием в программу — только File -> Open, только хардкор. Удаление комментов работает, но если скопировать текст из PDF, получим такое:

    Âòîðàÿ ãëàâà ïîñâÿùåíà ðåëÿöèîííûì áàçàì äàííûõ,
    

    Словом, кто вносил корректуру, тот в цирке не смеется.

    Повторю тезис из начала заметки: любая работа с PDF — это боль по определению. Он специально сделан так, чтобы правки были максимально затруднены. Бинарный формат, девять редакций, адское легаси. Любая программа, которая предлагает редактирование PDF — это н…балово. Оно работает только в примитивных случаях, а как что-то серьезней — сплошная беда.

    Поэтому, кстати, я считаю, что корректор, который не работает с бумагой — это слабый корректор. Хороший как минимум не должен ее избегать.

    UPD

    Продолжение заметки. Добрые люди подсказали два варианта: PDF Expert и Master PDF Editor. Первая не обслуживает пользователей из России, но ставится из brew. Оказалось, она не работает без облачной учетной записи. Попап закрывает интерфейс, хоть усрись, но создай учетку. Скриншот не сохранил, прогу удалил. Поставил Master PDF Editor, на первый взгляд все отлично. Посидел два часа, вносил корректуру. Сохранил файл и перекинул на другой комп. Открываю, а там:

    Знаете, это уже не смешно. Похоже, на вопрос из прошлой заметки — есть ли в природе программа, которая работает с PDF без ошибок — ответ все-таки отрицательный.

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

  • Строки по 80

    Иной айтишник нет-нет да пожалуется на разбиение строк по 80 символов. Считаю нужным прояснить ситуацию.

    Коротко: если это текст в системе контроля версий — код, readme или вроде того — он должен быть разбит. Не обязательно по 80 символов, можно 90, 100 или 120. Главное выбрать число и следовать ему.

    Обыватель думает, что число 80 связано с перфокартой. Да, на них действительно было столько дырок, потому что 80 символов умещалось в терминале. Но дело не только в этом. Откройте любую книгу и посчитайте число знаков в строке. Их будет от 60 до 80, в среднем 65. Почему?

    Ответ никак не связан с перфокартой. Эти 65-70 знаков в сочетании с 10 шрифтом дают строку длиной 12 см. В свою очередь 12 см — та длина, которую глаз читает на комфортном расстоянии без поворота головы.

    Как-то так:

    Откройте любой журнал. Две колонки по 50 символов, потому что шрифт меньше. Сделайте в одну — никто не прочтет.

    Теперь я растянул редактор на всю ширину. Как это читать? Вы будете крутить головой, как лошадь на привязи:

    А так нормально:

    Тут влетает бунтарь с криками: как так, за меня решили, задели хрупкие чувства! Дайте свободу, я сам настрою. Мне не жалко, но есть проблема: редактор приходится ужимать до такого состояния:

    У меня так-то 400 файлов открыто, и под каждый менять размер окна — не много ль чести? Можно вынести файл в отдельное окно, но опять же — заморочки.

    Далее — конфликты. Если параграф не нарезан на строки по 80 символов, исправить конфликты невозможно. Пожалуйста, прочтите это предложение еще раз: исправить конфликты невозможно. Без нарезки параграф — это единая строка, и если Вася исправил букву в начале, а Петя — в конце, это конфликт. Пишите претензии создателям Git, CSV и другим системам контроля версий. Их рабочая единица — строка, и других не предвидится.

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

    Наконец, в хорошем редакторе проблема нарезки не стоит в принципе. C-x f задает ширину колонки, M-q поджимает текущий абзац. Чтобы поджать весь документ, жмем C-x h и M-q. Занимает секунду.

    Кстати, принцип, когда подряд идущие строки объединяются в параграф, а пустая строка отбивает параграфы, придумали не в markdown. Это сделал Кнут для своего TeX, за что ему честь и хвала.

    Вот, пожалуй, все, что нужно знать про нарезку строк.

  • Гигиена в Git

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

    Одна задача — один коммит. Слияние ветки происходит только через squash, другие методы запрещены.

    В сообщении к коммиту указан только номер задачи и ее заголовок, например [FOOBAR-123] Make some crap. Иная графомания нежелательна — идите за ней в задачу.

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

    После слияния ветка удаляется автоматом.

    У каждого PR есть expire. Висит две недели — значит никому не нужен и удаляется автоматом. В следующий раз команда будет расторопней.

    Чем больше веток — тем хуже. В идеале это master с тегами релизов и feature-ветки. И больше ничего.

    Пуш в мастер запрещен всем, кроме одного человека (техдиректора).

    Слияние без апрува запрещено (а лучше двух).

    Kdiff3 — пожалуй, лучшее средство разрешения конфликтов:

     > brew install --cask kdiff3
    
    # ~/.gitconfig
    
    [merge]
      tool = kdiff3
    [diff]
      tool = kdiff3
    [mergetool "kdiff3"]
      path = /Applications/kdiff3.app/Contents/MacOS/kdiff3
    [difftool "kdiff3"]
      path = /Applications/kdiff3.app/Contents/MacOS/kdiff3
    

    UPD Ограничение на один коммит не касается локальной ветки, в которой вы пилите задачу. Их может быть хоть два, хоть двести. Важно, что при слиянии методом squash они объединяются, и в главной ветке остается один коммит с номером задачи.

    Бывает, коллега делает задачу неделями и после аппрува жмет “Merge”. В истории оказывается 50+ коммитов с подписями “working”, “fix tests”, “fix linter”, “updated” и так далее. Спрашивается, зачем это команде? В своей ветке делай что хочешь, а в общей соблюдай приличия.

  • Apple VR

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

    Выглядит он как Дарт Вейдер или инвалид, вынужденный таскать какой-то девайс. Уж наверное игра с детьми — тот случай, когда нужно убрать гаджеты и предаться живому общению. А батя напялил ведро на голову и стал похож на киборгов из “Призрака в доспехах”, которым ставили бюджетные протезы вместо глаз.

    Я даже в телефон не смотрю при детях, чтобы не подавать плохой пример. А тут такое, жесть.

  • Десять советов, которые я дал бы себе в молодости

    Есть такой пошлый формат — кто-то успешный дает советы молодой аудитории. Заводи знакомства, качай софт-скилы, фокусируйся, помни о главном. Вся эта олдовая мудрость, поучение молодых, вы поняли.

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

    Как правило, человек плохо оценивает себя. Даже если он видит, что известней и богаче других, то находит этому нелепые объяснения. Это ошибка выжившего: причины успеха вовсе не те, что ты несешь со сцены. Они могут быть сомнительными, криминальными или вообще неизвестными. Это может быть совпадение времени и места. Но что-то нужно сказать, и он говорит, хотя все это не имеет смысла.

    В самом деле: начните делать то, что советовали Стив Джобс и Билл Гейтс, и ни к какому успеху вы не придете. Более того, вы же не пойдете с них догонять: смотри, я делал как ты говорил, но не помогает. Зачем тогда слушать, если нет гарантий?

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

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

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

  • SQL и DISTINCT ON

    Еще одна полезная штукенция в SQL — это оператор DISTINCT ON. От обычного DISTINCT он отличается тем, что определяет уникальность записей не по всем полям, а только указанным. Эта особенность позволяет писать интересные запросы.

    Предположим, мы ведем таблицу курсов валют относительно рубля. Вот ее структура:

    create table rates (
      id      serial primary key,
      value   text not null,
      instant timestamp without time zone not null default now(),
      cost    bigint not null
    );
    

    и данные:

    insert into rates (value, cost) values ('usd', 7968);
    insert into rates (value, cost) values ('usd', 7988);
    insert into rates (value, cost) values ('usd', 8031);
    
    insert into rates (value, cost) values ('eur', 8623);
    insert into rates (value, cost) values ('eur', 8581);
    insert into rates (value, cost) values ('eur', 8699);
    

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

    У подобных таблиц особенность: часто нужны их срезы по датам, например первые или последние на текущий момент. На первый взгляд не ясно, как это делать. До того, как я узнал про DISTINCT ON, я делал весьма топорно: сперва группировал по валюте, чтобы получить максимальную дату:

    select value, max(instant) as instant
    from rates
    group by value;
    
     value |          instant
    -------+----------------------------
     eur   | 2023-06-02 22:17:04.902392
     usd   | 2023-06-02 22:16:57.007947
    

    , а затем оборачивал это в подзапрос и соединял с основной таблицей по валюте и дате:

    select rates.*
      from rates
    join (
      select value, max(instant) as instant
      from rates
      group by value
    ) as sub
    on
          rates.value = sub.value
      and rates.instant = sub.instant;
    
     id | value |          instant           | cost
    ----+-------+----------------------------+------
      6 | usd   | 2023-06-02 22:16:57.007947 | 8031
      9 | eur   | 2023-06-02 22:17:04.902392 | 8699
    

    Это неуклюже и требует индексов для эффективного JOIN.

    Гораздо лучше смотрится с DISTINCT ON. Укажем в запросе, что записи уникальны в разрезе валют. В этом случае в выборке останется одна запись с usd и одна с eur, а остальные будут пропущены. Остается отсортировать их так, чтобы первыми шли записи с максимальной датой или минимальной.

    Пример с минимальной датой:

    select distinct on (value) *
    from rates
    order by value, instant;
    
     id | value |          instant           | cost
    ----+-------+----------------------------+------
      7 | eur   | 2023-06-02 22:16:59.46867  | 8623
      4 | usd   | 2023-06-02 22:16:50.603444 | 7968
    

    А это — с максимальной, потому что записи следуют по убываю instant:

    select distinct on (value) *
    from rates
    order by value, instant desc;
    
     id | value |          instant           | cost
    ----+-------+----------------------------+------
      9 | eur   | 2023-06-02 22:17:04.902392 | 8699
      6 | usd   | 2023-06-02 22:16:57.007947 | 8031
    

    Обратите внимание, что сортировка order by начинается с того поля, которое указано в distinct on. За счет этого база эффективно пропускает дубли без промежуточных списков.

    Если записей много, понадобится индекс на поле уникальности и критерий сортировки, в нашем случае value и instant:

    CREATE INDEX rates_last_idx ON rates (value, instant desc);
    

    Похоже решаются другие задачи, связанные со временем или версиями, например прогноз погоды, версионирование статей или сущностей.

Страница 17 из 79