Сальваторе Санфилиппо aka antirez — итальянский программист, проживающий в Катании (на острове Сицилия). Провинциальное местонахождение не мешает звезде IT-индустрии: он известен на весь мир как автор СУБД Redis. 46-летний antirez входит в число самых авторитетных и талантливых программистов-одиночек с хакерским бэкграундом.

В 2020 году он завязал было с программированием, чтобы целыми днями писать научную фантастику. Но закончив книгу, в конце 2022-го снова вернулся к коду. Оказалось, что между литературным творчеством и программированием сложных систем много общего.

Путь сицилийца в программирование стартовал в 80-е годы, когда отец, инженер-электрик, начал работать с программируемыми логическими контроллерами (PLC), в том числе на процессорной плате Z80.

Когда мальчику было шесть, отец купил первый персональный компьютер TI99/4A, на котором написал несколько программ на BASIC, а маленький Сальваторе последовал его примеру.


TI99/4A

Потом были ZX Spectrum и Olivetti PC1 Prodest (MS-DOS).

По мере взросления он продолжал программировать, хотя после 14 увлёкся прочими подростковыми интересами вроде мотоциклов и девушек, но в 18 лет вновь открыл страсть к компьютерам, занявшись 3D-моделированием, играми и написанием простых программ. После поступления на архитектурный факультет в Университете Палермо начал применять в архитектурной работе BASIC — язык программирования, знакомый с детства.

В одной из статей про юного antirez'а говорится, что однажды он пошёл (на рынок?) за графической картой, но по ошибке купил модем, а продавец отказался возвращать деньги, когда обнаружили ошибку. В 90-е годы интернет ещё был весьма нишевой технологией и только набирал популярность. Возможно, тот момент стал решающим в жизни студента-архитектора. Он вышел в интернет (или на BBS, история умалчивает), скачал дистрибутив Linux и заинтересовался вопросами информационной безопасности и сетевыми протоколами. Купил книжку по Си и начал его изучать. Вскоре обнаружил уязвимость в программе ping.

Вот то историческое сообщение о баге от 9 апреля 1998 года в списке рассылке Bugtraq. В нём опубликован код программки pingflood, которая с помощью простого синтаксиса pingflood <hostname> позволяет зафлудить произвольный сайт, используя стандартный ping:

#include <signal.h>

#define PING "/bin/ping"

main( int argc, char *argv[] )
{
  int pid_ping;

  if (argc < 2) {
    printf("use: %s <hostname>\n", argv[0]);
    exit(0);
  }

  if(!(pid_ping = fork()))
    execl(PING, "ping", argv[1], NULL);

  if ( pid_ping <=0 ) {
    printf("pid <= 0\n");
    exit(1);
  }

  sleep (1);  /* give it a second to start going  */
  while (1)
    if ( kill(pid_ping, SIGALRM) )
      exit(1);
}

С того дня карьерный путь архитектора изменился: последующие месяцы он провёл за чтением литературы о протоколах TCP/IP, файрволах, криптографии и инфобезе, а потом взял отпуск в университете и переехал в Милан для работы в компании SECLAB, где трудились лучшие итальянские хакеры. Его первой хакерской программой была Idle Scan, которая до сих пор считается одним из самых популярных способов атаки через сканер Nmap, а потом ещё одна утилита — hping, которая отправляет произвольные пакеты TCP/IP и отображает ответ от жертвы. Но через полгода изматывающей работы в мегаполисе 21-летний студент понял, что офисная работа не для него. Он уволился из SECLAB и устроился в компанию Linuxcare, разработчика опенсорсного софта, позволявшего работать удалённо.

Через два года у Сантьяго родился ребёнок, так что ему пришлось изучать новые технологии и искать варианты, как заработать больше денег. Он начал изучать современный стек, фронтенд и бэкенд, а с началом хайпа вокруг Веб 2.0 они с другом основали стартап Merzia для разработки приложений Веб 2.0 (телекоммуникационный гигант Telecom Italia заказал у них два модных сайта: UGC-новости OKNOtizie с голосованием за каждую статью, а также сайт закладок Segnalo в стиле Delicious).


OKNOtizie поддерживал голосование за статьи — одну из революционных концепций Веб 2.0 и UGC начала 2000-х

Через пару лет antirez с другом решили сфокусироваться на веб-аналитике и разработать инструмент, с помощью которого блогеры могут отслеживать поведение пользователей на своём сайте в реальном времени. Так появился LLOOGG (2007). Аналогичную функцию Google Analytics реализовали только в 2011-м, а до тех пор обновление данных в реальном времени оставалось уникальной инновацией LLOOGG, вот как это выглядело:

Именно для нужд веб-аналитики написали БД Redis (remote dictionary server), поскольку Санфилиппо угнетала низкая производительность MySQL при обработке логов в реальном времени. Это резидентная БД, которая все данные хранит в оперативной памяти для минимальной задержки и максимальной производительности. Написана на Си, сейчас считается самой популярной СУБД NoSQL и самой любимой СУБД у разработчиков.

Первый выпуск состоялся 10 мая 2009 года: сначала был написан прототип на Tcl. Тогда она ещё называлась LMDB (LLOOGG Memory DB), вот её код, 319 строк:

LMDB
# LVDB - LLOOGG Memory DB
# Copyriht (C) 2009 Salvatore Sanfilippo <antirez@gmail.com>
# All Rights Reserved

# TODO
# - cron with cleanup of timedout clients, automatic dump
# - the dump should use array startsearch to write it line by line
#   and may just use gets to read element by element and load the whole state.
# - 'help','stopserver','saveandstopserver','save','load','reset','keys' commands.
# - ttl with milliseconds resolution 'ttl a 1000'. Check ttl in dump!
# - cluster. Act as master, send write ops to all servers, get from one at random. Auto-serialization.
# - 'hold' and 'continue' command, for sync in cluster mode
# - auto-sync, consider lazy copy or log of operations to re-read at start
# - client timeout
# - save dump in temp file.[clock ticks] than rename it

package require Tclx ;# For [fork]

array set ::clients {}
array set ::state {}
array set ::readlen {}
array set ::readbuf {}
array set ::db {}
array set ::ttl {}
set ::dirty 0
set ::lastsaved 0
set ::listensocket {}

signal -restart block SIGCHLD

# the K combinator is using for Tcl object refcount hacking
# in order to avoid useless object copy.
proc K {x y} {
    set x
}

proc headappend {var e} {
    upvar 1 $var l
    set l [lreplace [K $l [set l {}]] -1 -1 $e]
}

proc log msg {
    puts stderr "[clock format [clock seconds]]\] $msg "
}

proc warning msg {
    log "*** WARNING: $msg"
}

proc writemsg {fd msg} {
    puts -nonewline $fd $msg
    puts -nonewline $fd "\r\n"
}

proc resetclient {fd} {
    set ::clients($fd) [clock seconds]
    set ::state($fd) {}
    set ::readlen($fd) 0
    set ::readbuf($fd) {}
}

proc accept {fd addr port} {
    resetclient $fd
    fconfigure $fd -blocking 0 -translation binary -encoding binary
    fileevent $fd readable [list readrequest $fd]
}

proc readrequest fd {
    if [eof $fd] {
        closeclient $fd
        return
    }

    # Handle bulk read
    if {$::state($fd) ne {}} {
        set buf [read $fd [expr {$::readlen($fd)-[string length $::readbuf($fd)]}]]
        append ::readbuf($fd) $buf
        if {[string length $::readbuf($fd)] >= $::readlen($fd)} {
            set ::readbuf($fd) [string range $::readbuf($fd) 0 end-2]
            lappend ::state($fd) $::readbuf($fd)
            cmd_[lindex $::state($fd) 0] $fd $::state($fd)
        }
        return
    }

    # Handle first line request
    set req [string trim [gets $fd] "\r\n "]
    if {$req eq {}} return

    # Process command
    set args [split $req]
    set cmd [string tolower [lindex $args 0]]
    foreach ct $::cmdtable {
        if {$cmd eq [lindex $ct 0] && [llength $args] == [lindex $ct 1]} {
            if {[lindex $ct 2] eq {inline}} {
                cmd_$cmd $fd $args
            } else {
                set readlen [lindex $args end]
                if {$readlen < 0 || $readlen > 1024*1024} {
                    writemsg $fd "protocol error: invalid bulk read length"
                    closeclient $fd
                    return
                }
                bulkread $fd [lrange $args 0 end-1] $readlen
            }
            return
        }
    }
    writemsg $fd "protocol error: invalid command '$cmd'"
    closeclient $fd
}

proc bulkread {fd argv len} {
    set ::state($fd) $argv
    set ::readlen($fd) [expr {$len+2}]  ;# Add two bytes for CRLF
}

proc closeclient fd {
    unset ::clients($fd)
    unset ::state($fd)
    unset ::readlen($fd)
    unset ::readbuf($fd)
    close $fd
}

proc cron {} {
    # Todo timeout clients timeout
    puts "lmdb: [array size ::db] keys, [array size ::clients] clients, dirty: $::dirty, lastsaved: $::lastsaved"
    after 1000 cron
}

set ::cmdtable {
    {ping 1 inline}
    {quit 1 inline}
    {set 3 bulk}
    {get 2 inline}
    {exists 2 inline}
    {delete 2 inline}
    {incr 2 inline}
    {decr 2 inline}
    {lpush 3 bulk}
    {rpush 3 bulk}
    {save 1 inline}
    {bgsave 1 inline}
}

proc okreset {fd {msg OK}} {
    writemsg $fd $msg
    flush $fd
    resetclient $fd
}

proc cmd_ping {fd argv} {
    writemsg $fd "PONG"
    flush $fd
    resetclient $fd
}

proc cmd_quit {fd argv} {
    okreset $fd
    closeclient $fd
}

proc cmd_set {fd argv} {
    set ::db([lindex $argv 1]) [lindex $argv 2]
    incr ::dirty
    okreset $fd
}

proc cmd_get {fd argv} {
    if {[info exists ::db([lindex $argv 1])]} {
        set val $::db([lindex $argv 1])
    } else {
        set val {}
    }
    writemsg $fd [string length $val]
    writemsg $fd $val
    flush $fd
    resetclient $fd
}

proc cmd_exists {fd argv} {
    if {[info exists ::db([lindex $argv 1])]} {
        set res 1
    } else {
        set res 0
    }
    writemsg $fd $res
    flush $fd
    resetclient $fd
}

proc cmd_delete {fd argv} {
    unset -nocomplain -- ::db([lindex $argv 1])
    incr ::dirty
    writemsg $fd "OK"
    flush $fd
    resetclient $fd
}

proc cmd_incr {fd argv} {
    cmd_incrdecr $fd $argv 1
}

proc cmd_decr {fd argv} {
    cmd_incrdecr $fd $argv -1
}

proc cmd_incrdecr {fd argv n} {
    if {[catch {
        incr ::db([lindex $argv 1]) $n
    }]} {
        set ::db([lindex $argv 1]) $n
    }
    incr ::dirty
    writemsg $fd $::db([lindex $argv 1])
    flush $fd
    resetclient $fd
}

proc cmd_lpush {fd argv} {
    cmd_push $fd $argv -1
}

proc cmd_rpush {fd argv} {
    cmd_push $fd $argv 1
}

proc cmd_push {fd argv dir} {
    if {[catch {
        llength $::db([lindex $argv 1])
    }]} {
        if {![info exists ::db([lindex $argv 1])]} {
            set ::db([lindex $argv 1]) {}
        } else {
            set ::db([lindex $argv 1]) [split $::db([lindex $argv 1])]
        }
    }
    if {$dir == 1} {
        lappend ::db([lindex $argv 1]) [lindex $argv 2]
    } else {
        headappend ::db([lindex $argv 1]) [lindex $argv 2]
    }
    incr ::dirty
    okreset $fd
}

proc savedb {} {
    set err [catch {
        set fp [open "saved.lmdb" w]
        fconfigure $fp -encoding binary -translation binary
        set search [array startsearch ::db]
        set elements [array size ::db]
        for {set i 0} {$i < $elements} {incr i} {
            set key [array nextelement ::db $search]
            set val $::db($key)
            puts $fp "[string length $key] [string length $val]"
            puts -nonewline $fp $key
            puts -nonewline $fp $val
        }
        close $fp
        set ::dirty 0
        set ::lastsaved [clock seconds]
    } errmsg]
    if {$err} {return $errmsg}
    return {}
}

proc backgroundsave {} {
    unset -nocomplain ::dbcopy
    array set ::dbcopy [array get ::db]
}

proc cmd_bgsave {fd argv} {
    backgroundsave
    okreset $fd
}

proc cmd_save {fd argv} {
    set errmsg [savedb]
    if {$errmsg ne {}} {
        okreset $fd "ER"
        warning "Error trying to save: $errmsg"
    } else {
        okreset $fd
        log "State saved"
    }
}

proc loaddb {} {
    set err [catch {
        set fp [open "saved.lmdb"]
        fconfigure $fp -encoding binary -translation binary
        set count 0
        while {[gets $fp len] != -1} {
            set key [read $fp [lindex $len 0]]
            set val [read $fp [lindex $len 1]]
            set ::db($key) $val
            incr count
        }
        log "$count keys loaded"
        close $fp
    } errmsg]
    if {$err} {
        warning "Loading DB from file: $errmsg"
    }
    return $err
}

proc main {} {
    log "Server started"
    if {[file exists saved.lmdb]} loaddb
    set ::dirty 0
    set ::listensocket [socket -server accept 6379]
    cron
}

main
vwait forever

Затем antirez переписал прототип на Си, реализовал первый тип данных (список), а после нескольких недель внутреннего тестирования в стартапе решил выложить код в открытый доступ и анонсировать проект на HN. Как говорится, на следующее утро проснулся знаменитым. Уникальную по производительности РБД заметили все, а самого Сальваторе впоследствии взяла на работу компания VMware, таким образом выступившая спонсором разработки Redis. Потом были другие спонсоры, и только в 2020 году antirez ушёл с поста мейнтейнера, как мы уже упоминали.

Первые годы antirez программировал преимущественно в туалете, в одиночку, на маленьком ноутбуке MacBook Air 11, но проект вышел поистине великим. Это не просто самая распространённая СУБД, но и в принципе один из самых популярных системных инструментов нашего времени.

antirez в одиночку поддерживал проект от прототипа до версии 6.0, вот таймлайн этих одиннадцати изматывающих лет:

Дата Версия Новые функции
Февраль 2009 Строки, списки
Сентябрь 2009 1.0 Наборы, сортированные наборы, сохраняемость (RDB, AOF) и др.
Сентябрь 2010 2.0 Хэши, pub/sub, битмапы, скрипты Lua, HyperLogLog и др.
Апрель 2015 3.0 Кластеры, геопространственные индексы
Июль 2017 4.0 Модули: RediSearch, RedisJSON, RedisGraph и др.
Октябрь 2018 5.0 Потоки
Январь 2020 6.0 SSL, ACL и др.

▍ Другие проекты


  • Dump1090 — декодер Mode-S для программного радио на базе дешёвых ТВ-тюнеров (RTL-SDR).
  • The Linenoise — минималистичный редактор строк (с историей), который сейчас используется в Redis, MongoDB, Android и многих других проектах, замена readline.
  • Kilo — очень простой текстовый редактор (менее 1000 строк кода), ставший примером программирования на Си для тех, кто изучает этот язык (демо).
  • Load81 — язык программирования для детей;
  • Disque — распределённая очередь заданий в памяти (хотя проект был заброшен, но автор очень гордится задуманной архитектурой).
  • Интерпретатор Jim Tcl — в своё время дал понимание ключевых вещей, которые легли в фундамент СУБД Redis.
  • Сетевая утилита Hping — отправляет произвольные пакеты TCP/IP и отображает ответ от жертвы, прикольный проект 1998 года (упомянута ранее).

Как видим, большинству проектов автора присущ минимализм с упором на максимальную производительность и минимальное потребление ресурсов.

Кроме того, antirez является активным участником сообщества Flipper Zero. Для этого хакерского мультитула с модулями NFC, RFID, Bluetooth, ИК-передатчиком и слотом SD он лично реализовал детектор/редактор сигналов ProtoView (в дополнение к дефолтному приложению Subghz) и Asteroids for Flipper, порт популярной игры Asteroids.



Flipper Zero умеет даже копировать ключи от домофона:



Как известно, проектом Flipper Zero руководит легенда Хабра zhovner Павел Жовнер, так что мир тесен, а таланты притягиваются друг к другу…

▍ Научная фантастика


Упомянутый роман «Вохпе» (Wohpe), который отвлёк Сальваторе от программирования на два года, посвящён актуальной теме ИИ, причём написан до нынешнего хайпа. Прочитав два десятка страниц бесплатного фрагмента в электронной версии на Amazon, кажется, что сюжет интересный. Сам автор говорит, что бумажная версия на итальянском языке продаётся очень хорошо. Книга вошла в число самых популярных научно-фантастических произведений в Италии (англоязычная версия на бумаге пока не вышла).

… Натан Фрай, инженер-тестировщик беспилотных машин, получил на внедрение в прототип новенькую неопробованную нейросеть с 1016 параметров. Естественно, компания не собирается тратить годы на предварительное тестирование и выравнивание этой нейросети. Внедрять надо сейчас, бизнес есть бизнес.

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

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

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

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

Но через 20 лет учёные всё-таки запустили экспериментальный проект «Вохпе». С соблюдением всех мер предосторожности, в физически изолированной сети. Казалось бы, нечего бояться...

Средняя оценка читателей 4,5 (80% оценок 5) выглядит многообещающе, хотя там всего 24 оценки, это могут быть родственники и друзья Сальваторе.

Многие талантливые программисты планеты сейчас заняты созданием разных версий ИИ, даже Сергей Брин ради этого вернулся из многолетнего отпуска. А вот antirez предпочитает писать о них книги, а не разрабатывать. В любом случае, приоритет развития человечества на ближайшие годы вполне ясен, и автор Redis не остался в стороне…


Telegram-канал с розыгрышами призов, новостями IT и постами о ретроиграх ????️

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


  1. 0Bannon
    05.09.2023 05:05
    +1

    Сказочный фантаст