Переписал SiteKnockerBot на Go

Вчера вечером выполнил еще один пункт из своего списка дел на период загородной жизни: дописал новую версию SiteKnockerBot на языке Go, что позволило выполнять проверки сайтов параллельно. Но закончил весьма поздно, поэтому тестирование на боевом сервере решил отложить на сегодня. Причем, думал, что управлюсь за час-полтора, а остаток дня потрачу на отдых и компьютерные игры. Но увы, я был бы не я, если бы не растянул намеченное дело на всё доступное время. Началось всё с того, что при первом запуске бот просто упал. Причина нашлась довольно быстро: в одной ситуации я поставил defer resp.Close до проверки на то, что нет ошибок. Перекомпилировал, загрузил на сервер, запустил снова. Вроде работает, но то и дело появляются ошибки — невозможно открыть сокет из-за нехватки количества файлов. Пришлось срочно разбираться, как увеличить количество открытых файлов, так как прежде я с этим лимитом никогда не сталкивался. Ещё один запуск: вроде работает, но появляется подозрительно много таймаутов. Уменьшил число goroutines, вроде стало лучше, но опять таймауты есть. Какое-то время пытался играться с числом goroutines и интервалом, через который они порождаются, но увы, таймауты никуда не девались. Пробовал смотреть эти сайты через curl на локальном компьютере — вроде всё нормально, выдаются 301 или 302 редиректы. Думал, что в Go они как-то некорректно обрабатываются, но нет. Разгадка пришла тогда, когда попытался открыть соответствующие адреса в броузере. Оказывается, в таких случаях происходят редиректы на другой адрес, который не отвечает.
И вообще, в базе обнаружилось на удивление много «битых» и подозрительных адресов. Такое ощущение, что чуть ли не четверть пользователей использовало бота для каких-то сайтов-однодневок и подозрительных целей…
После того, как разобрался с таймаутами, снова стал подбирать число goroutines и интервалы. Изначально план был таким: 500 параллельных запросов каждые 150 миллисекунд, что давало 1 миллион проверок за пятиминутный цикл. Но при этом время ответа многих сайтов оказывалось гораздо больше, чем на самом деле. Например, даже 2seconds, где вообще лежит статичная страница, открывался чуть больше 1 секунды. Я стал пытаться уменьшить интервалы до 20—30 мс, уменьшив также и количество параллельных запросов, но увы, либо это не помогало, либо приходилось ставить такие значения, что число проверок за цикл получалось бы равным 200—300 тыс, а не миллиону, как я задумывал изначально. В итоге уже почти смирился с тем, что придётся тремястами тысячами и ограничиться. Но потом решил пойти на крайние меры: интервал между запросами снизил всего до 6 мс, а их число — до 10. Сделав это, я обнаружил, что время снизилось до почти приемлемого: тот же 2seconds загружается за 400 мс, а в среднем время ответа менее на 100 мс больше, чем на старой версии бота. Но увы, даже так получается всего полмиллиона запросов за цикл.
На этом я и решил остановиться. Потом еще какое-то время провозился с настройкой systemd. Почему-то из командной строки новый бот запускался нормально, а через него — нет. Вскоре выяснилось, что systemd запускает старую версию бота, не видя изменений в unit-файле. Так до конца и не понял, почему, но в итоге добился нормальной работы. Потом стал искать, нельзя ли как-то оптимизировать работу HTTP-клиента в Go, наткнулся на проект fasthttp, где обещают гораздо более высокую скорость запросов. Но увы, переписывать бота ещё раз уже нет ресурса — выгорел полностью.
Но в любом случае, я впервые написал что-то достаточно серьёзное и законченное на относительно новом для себя языке — Go. До этого я использовал его только для решения задач на CodinGame, и то очень мало. Впечатления остались очень противоречивые. Понравилась лёгкость распараллеливания, легкость компиляции под другую архитектуру (я сначала собирал бота под x86, забыв, что у меня и на втором VDS стоит 64-битная OS) и то, что он делает полностью статичные бинарники бе всяких so. Правда, у этого есть и обратная сторона: большая прожорливость памяти. PHP-шная версия бота занимала вроде около 30 Мб, а новая — 65, а когда параллельных запросов было больше, то и все 110. Но что реально не нравится, так это использование всяких Readers/Writers для работы с HTTP-ответом (и не только с ним, они там на каждом шагу используются). Ну почему его нельзя получить просто в строку/массив байтов, как в PHP или JavaScript? Зачем эти лишние (и труднодоступные для понимания) сущности? (И тут я подумал, что надо было вообще использовать node.js, а не Go.)
А еще завершение бота — это ещё один выполненный пункт моего списка дел. Причем восьмой из шестнадцати — ровно половина сделана. Хех, как всегда, живу в 3—4 раза медленнее: планировал сделать 16 пунктов за месяц, сделал половину почти за два. Но, пожалуй, теперь стоит устроить несколько дней отдыха, чтобы прийти в себя после всего этого!