• PG2 release 0.1.18

    PG2 version 0.1.18 is available (it’s a client for Postgres). This release brings two major features:

    • built-in pgvector extension support;
    • better type mapping between Postgres and Clojure.

    PGVector Support

    Pgvector is a well known extension for PostgreSQL. It provides a fast and robust vector type which is quite useful for heavy computations. Pgvector also provides a sparse version of a vector to save space.

    This section covers how to use types provided by the extension with PG2.

    Vector

    First, install pgvector as the official readme file prescribes. Now that you have it installed, try a simple table with the vector column:

    (def conn
      (jdbc/get-connection {...}))
    
    (pg/query conn "create temp table test (id int, items vector)")
    
    (pg/execute conn "insert into test values (1, '[1,2,3]')")
    (pg/execute conn "insert into test values (2, '[1,2,3,4,5]')")
    
    (pg/execute conn "select * from test order by id")
    
    ;; [{:id 1, :items "[1,2,3]"} {:id 2, :items "[1,2,3,4,5]"}]
    

    It works, but we got the result unparsed: the :items field in each row is a string. This is because, to take a custom type into account when encoding and decoding data, you need to specify something. Namely, pass the :with-pgvector? flag to the config map as follows:

    (def config
      {:host "127.0.0.1"
       :port 5432
       :user "test"
       :password "test"
       :database "test"
       :with-pgvector? true})
    
    (def conn
      (jdbc/get-connection config))
    

    Now the strings are parsed into a Clojure vector of double values:

    (pg/execute conn "select * from test order by id")
    
    [{:id 1, :items [1.0 2.0 3.0]}
     {:id 2, :items [1.0 2.0 3.0 4.0 5.0]}]
    

    To insert a vector, pass it as a Clojure vector as well:

    (pg/execute conn "insert into test values ($1, $2)"
                {:params [3 [1 2 3 4 5]]})
    

    It can be also a lazy collection of numbers produced by a map call:

    (pg/execute conn "insert into test values ($1, $2)"
                {:params [4 (map inc [1 2 3 4 5])]})
    

    The vector column above doesn’t have an explicit size. Thus, vectors of any size can be stored in that column. You can limit the size by providing it in parentheses:

    (pg/query conn "create temp table test2 (id int, items vector(5))")
    

    Now if you pass a vector of a different size, you’ll get an error response from the database:

    (pg/execute conn "insert into test2 values (1, '[1,2,3]')")
    
    ;; Server error response: {severity=ERROR, code=22000, file=vector.c, line=77,
    ;; function=CheckExpectedDim, message=expected 5 dimensions, not 3,
    ;; verbosity=ERROR}
    

    The vector type supports both text and binary modes of PostgreSQL wire protocol.

    Sparse Vector

    The pgvector extension provides a special sparsevec type to store vectors where only certain elements are filled. All the rest elements are considered as zero. For example, you have a vector of 1000 items where the 3rd item is 42.001, and 10th item is 99.123. Storing it as a native vector of 1000 double numbers is inefficient. It can be written as follows which takes much less:

    {3:42.001,10:99.123}/1000
    

    The sparsevec Postgres type acts exactly like this: internally, it’s a sort of a map that stores the size (1000) and the {index -> value} mapping. An important note is that indexes are counted from one, not zero (see the README.md file of the extension for details).

    PG2 provides a special wrapper for a sparse vector. A brief demo:

    (pg/execute conn "create temp table test3 (id int, v sparsevec)")
    
    (pg/execute conn "insert into test3 values (1, '{2:42.00001,7:99.00009}/9')")
    
    (pg/execute conn "select * from test3")
    
    ;; [{:v <SparseVector {2:42.00001,7:99.00009}/9>, :id 1}]
    

    The v field above is an instance of the org.pg.type.SparseVector class. Let’s look at it closer:

    ;; put it into a separate variable
    (def -sv
      (-> (pg/execute conn "select * from test3")
          first
          :v))
    
    (type -sv)
    
    org.pg.type.SparseVector
    

    The -sv value has a number of interesting traits. To turn in into a native Clojure map, just deref it:

    @-sv
    
    {:nnz 2, :index {1 42.00001, 6 99.00009}, :dim 9}
    

    It mimics the nth access as the standard Clojure vector does:

    (nth -sv 0) ;; 0.0
    (nth -sv 1) ;; 42.00001
    (nth -sv 2) ;; 0.0
    

    To turn in into a native vector, just pass it into the vec function:

    (vec -sv)
    
    [0.0 42.00001 0.0 0.0 0.0 0.0 99.00009 0.0 0.0]
    

    There are several ways you can insert a sparse vector into the database. First, pass an ordinary vector:

    (pg/execute conn "insert into test3 values ($1, $2)"
                {:params [2 [5 2 6 0 2 5 0 0]]})
    

    Internally, zero values get eliminated, and the vector is transformed into a SparseVector instance. Now read it back:

    (pg/execute conn "select * from test3 where id = 2")
    
    [{:v <SparseVector {1:5.0,2:2.0,3:6.0,5:2.0,6:5.0}/8>, :id 2}]
    

    The second way is to pass a SparseVector instance produced by the pg.type/->sparse-vector function. It accepts the size of the vector and a mapping of {index => value}:

    (require '[pg.type :as t])
    
    (pg/execute conn "insert into test3 values ($1, $2)"
                {:params [3 (t/->sparse-vector 9 {0 523.23423
                                                  7 623.52346})]})
    

    Finally, you can pass a string representation of a sparse vector:

    (pg/execute conn "insert into test3 values ($1, $2)"
                {:params [3 "{1:5.0,2:2.0,3:6.0,5:2.0,6:5.0}/8"]})
    

    Like the vector type, sparsevec can be also limited to a certain size:

    create table ... (id int, items sparsevec(5))
    

    The sparsevec type supports both binary and text Postgres wire protocol.

    Custom Schemas

    The text above assumes you have the pgvector extension installed globally meaning it is hosted in the public schema. Sometimes though, extensions are setup per schema. For example only a schema named sales has access to the pgvector extension but nobody else.

    If it’s your case and you installed pgvector into a certain schema, the standard :with-pgvector? flag won’t work. By default, PG2 scans the pg_types table for the public.vector and public.sparsevec types. Since the schema name is not public but sales, you need to specify it by passing a special option called :type-map. It’s a map where keys are fully qualified type names (either a keyword or a string), and values are predefined instances of the IProcessor interface:

    (def config
      {:host "127.0.0.1"
       :port 5432
       :user "test"
       :password "test"
       :database "test"
       :type-map {"sales.vector" t/vector
                  "sales.sparsevec" t/sparsevec}})
    

    You can rely on keywords as well:

    (def config
      {:host "127.0.0.1"
       :port 5432
       :user "test"
       :password "test"
       :database "test"
       :type-map {:sales/vector t/vector
                  :sales/sparsevec t/sparsevec}})
    

    The t alias references the pg.type namespace.

    Now if you install the extension into the statistics schema as well, add it into the map:

    (def config
      {:host "127.0.0.1"
       :port 5432
       :user "test"
       :password "test"
       :database "test"
       :type-map {:sales/vector t/vector
                  :sales/sparsevec t/sparsevec
                  :statistics/vector t/vector
                  :statistics/sparsevec t/sparsevec}})
    

    Should you make a mistake in a fully qualified type name, it will be ignored, and you’ll get value from the database unparsed. The actual value depends on the binary encoding and decoding options of a connection. By default, it uses text protocol so you’ll get a string like “[1, 2, 3]”. For binary encoding and decoding, you’ll get a byte array that holds raw Postgres payload.

    Custom Type Processors

    PG2 version 0.1.18 has the entire type system refactored. It introduces a conception of type processors which allows to connect Postgres types with Java/Clojure ones with ease.

    When reading data from Postgres, the client knows only the OID of a type of a column. This OID is just an integer number points to a certain type. The default builtin types are hard-coded in Postgres, and thus their OIDs are known in advance.

    Say, it’s for sure that the int4 type has OID 23, and text has OID 25. That’s true for any Postgres installation. Any Postgres client has a kind of a hash map or a Enum class with these OIDs.

    Things get worse when you define custom types. These might be either enums or complex types defined by extensions: pgvector, postgis and so on. You cannot guess OIDs of types any longer because they are generated in runtime. Their actual values depend on a specific machine. On prod, the public.vector type has OID 10541, on pre-prod it’s 9621, and in Docker you’ll get 1523.

    Moreover, a type name is unique only across a schema that’s holding it. You can easily have two different enum types called status defined in various schemas. Thus, relying on a type name is not a good option unless it’s fully qualified.

    To deal with all said above, a new conception of type mapping was introduced.

    First, if a certain OID is builtin (meaning it exists the list of predefined OIDs), it gets processed as before.

    When you connect to a database, you can pass a mapping like {schema.typename => Processor}. When pg2 has established a connection, it executes an internal query to discover type mapping. Namely, it reads the pg_type table to get OIDs that have provided schemas and type name. The query looks like this:

    select
        pg_type.oid, pg_namespace.nspname || '.' || pg_type.typname as type
    from
        pg_type, pg_namespace
    where
        pg_type.typnamespace = pg_namespace.oid
        and pg_namespace.nspname || '.' || pg_type.typname in (
            'schema1.type1',
            'schema2.type2',
            ...
        );
    

    It returns pairs of OID and the full type name:

    121512 | schema1.type1
     21234 | schema2.type2
    

    Now PG2 knows that the OID 121512 specifies schema1.type1 but nothing else.

    Finally, from the map {schema.typename => Processor} you submitted before, PG2 builds a map {OID => Processor}. If the OID is not a default one, it checks this map trying to find a processor object.

    A processor object is an instance of the org.pg.processor.IProcessor interface, or, if more precisely, an abstract AProcessor which is partially implemented. It has four methods:

    ByteBuffer encodeBin(Object value,  CodecParams codecParams);
        String encodeTxt(Object value,  CodecParams codecParams);
        Object decodeBin(ByteBuffer bb, CodecParams codecParams);
        Object decodeTxt(String text,   CodecParams codecParams);
    

    Depending on whether you’re decoding (reading) the data or encoding them (e.g. passing parameters), and the current format (text or binary), a corresponding method is called. By extending all four methods, you can handle any type you want.

    At the moment, there are about 25 processors implementing standard types: int2, int4, text, float4, and so on. Find them in the pg-core/src/java/org/pg/processor directory. There is also a couple of processors for the pgvector extension in the pgvector subdirectory.

    The next step is to implement processors for the postgis extension.

  • Память у Андроида (2)

    Продолжение прошлой заметки про Андроид.

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

    Человек запускает менеджер очистки, но это сплошное издевательство. Менеджер говорит: у тебя занято 20 гигов фотками и видео, удаляй сам. А так я могу очистить временные файлы на сумму 30 мегабайтов.

    Я проходил это десять раз, не меньше. Покупаешь телефон маме, жене, детям, родственникам — и через полгода они приходят к тебе с вопросом “закончилось место”. Ты скидываешь фотки на комп, и на год тебя оставляют в покое.

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

    Принято считать, что за местом должен следить пользователь: регулярно смотреть статистику, удалять файлы, настраивать кеши в мессенджерах. Да, некоторые это делают. Но как должны справляться с этим обычные люди? Скажем, чтобы настроить кеш в Телеграме, нужно нажать многоточие, потом гайку, потом смотаться до Data & Storage, и там выставить опции. Вы считаете, ваша мама это сделает? А потом то же самое в Вацапе и Вайбере?

    Когда я на это жалуюсь, мне говорят — что ты хотел? Тут ничего не поделаешь. А на самом деле поделать можно много чего.

    Заботу о диске должна брать на себя операционная система. Во-первых, если свободного места меньше 70%, то старые фотки уменьшаются в разрешении. Современные телефоны производят джипеги по 3-7 магабайтов — такие фотографии можно печатать в натуральный рост. Это, мягко говоря, избыточно для экрана размером с ладонь. Когда места перестает хватать, старые фотки сжимаются до 700 килобайт, освобождая от 2.5 до 5 магабайтов. Сто фоток — полгига.

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

    То же самое с видео: если сейчас ночь, и заряд батареи выше порога, то берется старое видео и перегоняется в низкое разрешение. Как и в случае с джипегами, на экране размером в ладонь никто не заметит разницы, а это дает 200-300 мегов с каждого ролика.

    Насчет кешей и разбухающих приложений. Ожидать, что человек откроет каждое приложение и все настроит — идиотизм. Когда системе не хватает места, она рассылает приложениям системный вызов а-ля “срочно удали кеши”. Можно добавить параметр — уровень критичности. Если он зеленый, то можно ничего не удалять, если желтый — удалить все данные, кроме последнего месяца, если красный — то все.

    Вы скажете, что никто не будет этого делать. А между прочим, это в интересах самих приложений. Потому что если на телефоне 2% свободного места, то пользоваться условным Телеграмом невозможно. А если каждое приложение освободит по 200-400 мегабайтов, то в сумме будет пара гигов, и телефон худо-бедно заработает.

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

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

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

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

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

    Хочу лишь показать — многие вещи можно переосмыслить, и технически они возможны. Дело в бизнесе и отношении к ним.

  • Память у Андроида (1)

    В Андроидах — я имею в виду телефоны — мне не нравится одна вещь, которая там с рождения. Это вечная борьба с тем, кто и куда пишет: во внутреннюю память или на сд-карту.

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

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

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

    Пишу это, потому что наболело. Жена пожаловалась, что телефон Xiaomi тормозит. В нем 32 гига памяти, и дополнительно я вставил карту на 32 гига. Везде указал приоритет записи на карту. И что вы думаете? Каким-то образом настройки слетели, и память оказалась забита видео и фотками под ноль. На карте 20 гиг свободного места, а телефон едва ворочается. Открывает менеджер диска, запускает сканирование и говорит: могу освободить аж 30 мегабайтов за счет каких-то там темповых файлов.

    Просто какая-то дичь. Что мешает переместить фотки на SD-карту в фоне? Пользователь ничего не заметит. У нас везде ИИ, всякие модели, предсказание фраз и покупок — и блин, телефон не может перетащить фотки. Он будет тормозить, тупить, но ни в коем случае не решит проблему.

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

    Я согласен, что иметь память и съемную карту удобно в техническом плане. Но на практике это напоминает Линукс: вы можете настроить что угодно, и поэтому всегда будете настраивать все, что угодно.

    Продолжение

  • Авторское право

    Интернет прощается со Стивером — анонимным создателем пиратской библиотеки Флибуста. Я ей никогда не пользовался, но краем уха слышал о сообществе и создателе. Его уверенность в своем деле вызывала уважение, ровно как и добровольный уход из жизни методом эвтаназии — не каждый решится на этот шаг.

    Его уход напомнил мне об отношению к авторскому праву. На мой взгляд, любое авторское право должно быть конечным и не передаваться по наследству. Это работает так: в течение десяти-двадцати лет ты продаешь книгу, получаешь гонорар. Но потом она становится достоянием общественности, и ее можно печатать и продавать кому угодно. Можно снять фильм или сериал, использовать персонажей в любом сеттинге, не опасаясь судебных исков.

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

    Более важный факт — авторское право не должно передаваться по наследству. Мне кажется бредовой ситуация, когда условная внучка Агаты Кристи указывает издательствам, что и когда печатать, а также вносит правки в соответствии с повесткой из телевизора. Так и хочется ей сказать: дорогая, а не пошла бы ты в жопу? Произведения Кристи — классика, с какой стати тебе наживаться на таланте бабушки?

    То же самое я бы сказал Успенскому, который судится из-за персонажей “Простоквашино”. Формально ты автор, но персонажи ушли в народ, стали фольклором. Придумай новых персонажей и зарабатывай на них. Хочешь, чтобы как у Чижа — “всю жизнь получать гонорар”?

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

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

    То же самое относится к айтишным продуктам. Каждые 10-20 лет разработчик лишается авторского права на продукт, и он становится общественным. Нужно уточнить — не на сегодняшний продукт, а его прошлую версию. Другими словами, Windows 10 по-прежнему остается коммерческой и закрытой, а Windows XP обязаны передать в общественное достояние. И через 10-15 лет то же самое произойдет с Windows 10.

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

  • О пропаганде

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

    В понимании европейца жизнь в России выглядит как повесть 1984. На каждом углу висит монитор, где Путин призывает давить танками Европу. Россиянин, если не занят на работе, постоянно смотрит внутреннее ТВ. Все каналы информации заняты только пропагандой. Сеть заполонили фабрики троллей: люди, которые пишут пропаганду за деньги под видом обывателей.

    Это, мягко говоря, не так. Аудитория государственных каналов падает уже много лет, и ничего поделать с этим нельзя. Популярны онлайн-кинотеатры и тематические каналы (спорт, сериалы). Фабрики троллей были проблемой лет десять назад во времена олимпиады в Сочи. От них страдали многие площадки, и со временем кто-то вообще убрал комментарии, кто-то ввел пост-модерацию, кто-то ужесточил регистрацию с подтверждением по телефону.

    Нужно понимать, что жизнь европейца не отличается от уклада жизни в России. Мы ведь не на разных планетах, и у нас не разное число голов. Европеец горбатится в офисе, читает Фейсбук(1) и Твиттер(2), вечером смотрит сериалы и Би-Би-Си. А Би-Би-Си — то же самое, что Первый канал: площадка, которая освещает повестку государства. Другими словами, там показывают то, что хочет текущий режим. Техника и подход разные, смысл одинаков.

    Из государственных новостей европеец знает, что в России все заполонила пропаганда, а любая реплика в интернете оставлена либо ботом, либо оплаченным троллем. Разумеется, европеец не ходит на российские сайты, не читает их через гугл-переводчик, не состоит в русских сообществах. Зачем, если по телевизору все объяснили?

    Спорить в данном случае бесполезно, потому что получается взаимоблокирующая ситуация: собеседник говорит о пропаганде, слушая пропаганду.

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

    UPD: продолжение

    1,2 — запрещенные, нежелательные, экстремистские и т.д. организации.

  • Разные припевы

    Почти любая песня строится по шаблону: куплет, припев, куплет, припев, проигрыш, припев, припев, припев. Этот принцип лежит во всех песнях Linkin Park, Korn, Cold, Queen и других исполнителей. Порой видно, что автор удачно сочинил припев, а с остальным не задалось: куплеты — просто мясо, чтобы занять окно между припевами.

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

    На ум приходит пара примеров. Первый — Pink Floyd. Их классические песни строились иначе: сначала Роджер Уотерс писал стихи, а потом их накладывали на музыку. Поэтому нет такого, что одно четверостишие повторяется пять раз. Песни: Time, Echoes, Us plus Them, альбомы Animals, The Wall.

    Второй пример — песни Диснея, особенно из “Холодного Сердца”. Я разбирал их на уроках английского: поразила плотность текста, игра слов, двойной смысл. В этих песнях разные припевы: хотя опорная фраза повторяется (Let it go, let it go или For the first time in forever), после нее идут другие слова, которые продолжают тему куплета. Другие мульты с богатым саундтреком: Покахонтас, Моана.

    Горячо советую послушать то и другое.

  • Пагинация

    Не люблю, когда для обхода данных нужна пагинация. Всякие LIMIT/OFFSET, nextToken и прочие костыли.

    Я понимаю, что нельзя отдать все одним запросом, поэтому делается апишка с пагинацией. Но не понимаю, почему не пишут функцию, которая пагинирует сама и возвращает ленивую коллекцию. Чтобы раз — зарядил один цикл for, и оно внутри само пагинируется: пробрасываются токены, офсеты и остальное.

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

    Даже если в языке нет ленивых коллекций, то наверняка есть итераторы или стримы. Скажем, смотрю на джавный S3 SDK. Есть метод listObjects, который вернет до тысячи объектов. Нужен следующий чанк — прокидывай nextToken. А что мешало написать метод, который вернет Iterator<ListObjectResult> или Stream<S3Object>? Ведь это же так просто.

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

    Более широкий тезис: источник данных хорош настолько, насколько легко забрать из него данные. Скажем, знаете ли вы, насколько кривая апишка у OpenSearch? У него нет встроенной функции “дай мне все” или “сбрось данные в CSV”. Для забора данных нужны две апишки. Первая вернет ScrollID и первый чанк данных. Далее нужно вызывать другую апишку с этим скроллом, пока не соберешь остальные данные. При этом ты обязан сообщить время жизни скролла, после которого он умирает. Время должен прикинуть сам на глазок.

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

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

  • О событиях с Линуксом

    Очевидно, что последняя заметка вызвана событиями вокруг Линукса и русских разработчиков. Скорее всего, вы в курсе, но на всякий случай напомню, что произошло.

    Несколько дней назад один из руководителей Linux Foundation разослал письмо, что сразу одиннадцать разработчиков лишаются статуса ментейнеров ядра. Примечательно, что у всех были русские имена, а почта — преимущественно на домене mail.ru.

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

    В том письме были крайне расплывчатые формулировки: санкции, обратитесь к юристу, сотрудничество с вами “ограничено”, предъявите документацию. Что значит “ограниченно”? Что за документацию, кому и в какой срок предоставить — не ясно.

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

    Линус ответил, и интернет порвался. В грубой манере он написал четыре параграфа, которые можно описать тезисами ниже:

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

    • Допускаю, что среди них есть несколько честных людей, но ничего не могу поделать.

    • Вы не слышали про санкции? Проверьте новости, но не ту дичь, что называется новостями в России.

    • Попытайтесь использовать то, что у вас вместо мозгов.

    • Вообще-то я финн, что вы хотели? Почитайте историю.

    • Мне так сказали юристы. Я программист, а не юрист, разбираться не буду.

    • Я не говорю на юридические темы с незнакомыми людьми на прикорме у государства.

    Получилось бодро, с огоньком. По слухам, в почтовой рассылке начался шторм. В числе прочего пользователи, как и просил Линус, почитали историю и напомнили про нападение Финляндию на Россию в 1918—1920 годах, ее позицию во время Второй мировой войны (коллаборацию с СС), а еще — оккупацию Финляндии Швецией на протяжении семи столетий.

    Изюминка в том, что Линус — финн шведского происхождения, то есть потомок оккупантов, живущий на территории жертв. Еще пользователи интересовались, чем занимался отец Линуса в период 1941—1945 годов: по слухам, он был коммунистом и часто бывал в Москве.

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

    Меня задевает, что с людьми обошлись грубо в одностороннем порядке. Это всегда неприятно, даже когда касается других. Никто не предупреждал разработчиков, что в их отношении готовится проверка. Сбор данных оказался неточным: по крайней мере один разработчик уже давно жил в США и работал в Амазоне. Скорее всего, информацию собрали из LinkedIn и устаревших резюме.

    Письмо Линуса в высшей степени грубо и инфантильно. В нем смешались эмоции, высокомерие, исторические отсылки. Это типичное сообщение для срача на Reddit, где участники забыли, о чем спорят, и каждый пишет про войну и Гитлера. От руководителя я бы ждал более внятной позиции.

    Процесс полон нарушений от начала до конца. Разработчиков лишили статуса до того, как они закончили важный патч ядра. Их имен нет даже в файле CREDITS, где упоминается любой человек, чей код попал в патч, даже если он умер.

    Все большие обещания на проверку оказались ложью. Нам долго рассказывали, что Линукс — это открытый код и международное сообщество. На проверку все это оказалось бутафорией. Линус — гражданин США, основные спонсоры Linux Foundation — компании США, юристы — граждане США. По факту Linux Kernel стал цифровым продуктом США со всеми юридическими последствиями. И хотя прецедентов не было, я уверен, любой суд придет к такому же выводу. Судья посмотрит на состав руководителей и спонсоров — и все станет ясно.

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

    Читаешь этот текст и думаешь: какие люди важнее проекту — у которых заскоки на тему истории или кто может сохранить лицо даже в такой ситуации?

    Я хорошо понимаю, что ощутили те разработчики. Напомню, что когда началось то, что сегодня называется “СВО”, меня уволили из Exoscale одним письмом. Никаких приватных разговоров, предупреждений. Просто я получил письмо, и пока вчитывался, чтобы понять смысл, пропал доступ ко всей инфраструктуре.

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

    Как я уже писал, обсуждение в почтовой рассылке перешло в клоунаду про Гитлера и фашистов. Были массовые письма с почтой putin@kremlin.ru с угрозами ядерных ударов и прочее. Но если говорить в серьезном ключе, беда в том, что уже первое сообщение Линуса — про кремлевских ботов и финнов — было троллингом. Оно уже было высказано с той позиции, когда никакое трезвое обсуждение невозможно. Фактически Линус стал нулевым троллем, который все это устроил.

    В ироническом смысле удивляет осведомленность Линуса о российских новостях и оплаченных ботах. Интересно, сталкивался ли он чем-либо из этого? Имеет хотя бы общее представление о том, какие медиа работают в России? Заходил ли хотя бы на какой-то сайт? Я в этом сомневаюсь. Линус пишет так, потому что так сказали его местные сми: если видишь сообщение с русского домена, это кремлевский бот.

    Это в точности подтверждает тезис прошлого поста: когда человек пишет о пропаганде в другой стране, он пересказывает свою пропаганду.

    Странно, что Линус, человек пятидесяти лет или около, не понимает: подкреплять свою позицию историей — в высшей степени моветон. Это глупо, грубо, это фундамент жирного троллинга, словом — что угодно, только не общение по делу. Историю человечества можно описать тремя словами: все воевали со всеми. Если ты взялся отслеживать, кто кому и когда был врагом и переносить это на софт, ты уже не программист, а диванный воин.

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

    Линус долгое время вдохновлял нас бунтарством. Своей грубостью он мог перевернуть доску в свою сторону, как это было в случае с “Fuck you, Nvidia”. Однако важно понимать, что во-первых, это был показной ход, а во-вторых, грубость относилась к гиганту — а пнуть гиганта всегда приятно, и приятно за этим наблюдать. Публичная грубость в адрес конкретного человека, увы, смотрится совсем по-другому.

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

    Завершая эту простыню, хочется пожелать разработчикам не терять энтузиазма и направить талант во что-то полезное людям. Это не их вина в том, что с ними обошлись невежливо. Уверен, они это переживут и пойдут дальше.

    Оставив выживание за скобками, Linux позволяет людям соединить удовольствие от интеллектуальных усилий и социальные отношения в единой команде ее создателей.

    Линус Торвальдс

  • Механическая работа

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

    Каждый вид хорош по-своему, и секрет в том, как их чередовать.

    Механическую работу нельзя делать, пока остался потенциал для осмысленной. Лучше сделать осмысленную, а механическую оставить на потом. Обратное тоже верно: нельзя браться за осмысленную, если уже устал — лучше заняться рутиной.

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

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

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

  • Крайний

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

    Но я никогда не думал, что прочту “крайний” в значении “последний” в книге. И не в прямой речи простолюдина, скажем, “братан, предупреждаю, это крайний раз”. Слово “крайний” использует рассказчик книги, по сюжету — развитый, интеллектуальный персонаж.

    Как же случилось, что такое просторечение пропустили переводчик, корректор и редактор? Это уму не постижимо — разве что все трое приехали из одной рязанской деревни. Другой версии у меня нет.

    Книга — “Звездный десант”, крайний перевод.

    UPD: в Телеграме объяснили, что “крайний” пришло из военных и прочих профессий, связанных с риском. Там не говорят “последний раз”, опасаясь, что он станет таковым буквально. Можно понять: у военных, моряков и летчиков целый букет прибабахов. Но зачем перенимать это в обычную жизнь – загадка.

Страница 1 из 84