пятница, 14 сентября 2018 г.

На мосту стояли трое - он, она и Google Cache

Всем вайтишникам, яростно надрачивающим на https everywhere вообще и Let's Encrypt в частности, посвящается.

Пока вы с пеной у рта настаиваете на всеобщем шифровании, регулярно потрахиваясь с ежемесячной заменой сертов, ваш Let's Encrypt, подписав свои корневые серты у рутов, встал во всемирное "дерево доверия". Ну а Корпорация Бобра тихо-мирно, не афишируя на публику вообще ничего, подписывает соглашения о дистрибуции контента с мессенджерами и другими гигантами:



Ну, и до кучи, дабы убедиться:



Это из других сетей запрашивалось.

А вы там мандражируете на тему госсертификатов... Ха! И еще раз - ха! Да вы на гугл посмотрите! Святее Папы Римского и стоит прямо на дороге!

Маленькое лирическое отступление для тех, кто все еще свято верит в HTTPS. Я неоднократно говорил, и повторюсь снова - кэширование HTTPS без расшифровки невозможно. Это означает, что любой кэширующий прокси, будь то форвардинговый или реверсивный, в случае HTTPS может, в случае его кэширования, перешифровывать трафик и кэшировать его в расшифрованном виде. Разумеется, это означает, что расшифрованный трафик виден, может быть проанализирован, привязан в пользователю, логгирован и выдан по запросу. Это также означает, что ваш "HTTPS" безопасен и приватен ровно до точки терминирования TLS-трафика, то есть до ближайшего CDN/реверсивного прокси. О Gоogle Global Cache в деталях известно на удивление мало, по-сути, ничего не известно, однако, так как других технологий кэширования пока что не изобрели (вы помните, да, что TLS-трафик - это случайный мусор, раз от раза разный по содержанию и кэшировать его живьем по этой причине невозможно никому?), это почти наверняка реверсивный кэширующий прокси.

Апологеты CDN могут в этом месте начать метать икру, крича, что "репутация-де, она дороже, никто не будет сливать бла-бла-бла" и так далее. Сливать, может, и не будет. А вот насчет "не анализировать" и "не предоставлять никому, включая правительства" разговора (и договора) не было. Кстати, вы в курсе, что Акамай/Lime Light/Cloudflare это тоже CDN, построенные на реверсивных кэширующих прокси? Так вот. Не отвлекайтесь. Речь идет покамест не о них, а о Корпорации Бобра с ее глобальным кэшом.

Как вы думаете, кому выгодно топить за HTTPS повсюду, имея собственный PKI, собственный доверенный CA, установленный во все устройства планеты, и собственный Global Cache, установленный практически в каждом провайдере планеты? Может быть, провайдерам? Или CDN? А, может быть, вам?

Мы уже выяснили, что кэширование HTTPS без его расшифровки невозможно, так?

Соответственно, Global Cache (равно как и все остальные CDN, хотя те в большей степени ограничены лишь своими клиентами) видит практически весь передаваемый через него трафик. Бонусом он, конечно, кэшируется - должна же быть какая-то выгода для подопытных от его использования - но он весь просматривается - вы точно верите, что Гугл не станет этого делать? А проверить - сможете? - анализируется и логгируется. Можете быть уверены - Гугл не продает GGC, а лишь предоставляет его в пользование. В чем его выгода? В том, чтобы анализировать те самые 95% HTTPS трафика. Без уведомления пользователей - а вы, лично вы, видели пользовательское соглашение GGC?

Это было бы полбеды. Зная, как радостно Гугл раздвигает нижние конечности перед запросами правительств мира, предоставляя ваши данные - вы уверены, что ваш HTTPS-трафик так же точно не будет предоставлен? Во всяком случае, его логи? Шта? Гугл мамой клянется, что никому не выдаст ваших данных, кроме как в оговоренных законом случаях и строго по судебному предписанию или ордеру? Мде? И вас это устраивает? Скажите - в какой стране вы живете?

Nothing personal - just business.

Снова и снова повторюсь - TLS не является E2E и по определению не может им являться. Коль скоро это так - какой смысл в этих заячьих плясках с вашим LE?

Кстати, я очень сильно позабавлюсь через пару-тройку лет. Как, интересно знать, LE станет поддерживать CRL для миллионов протухших или отозванных сертов? SAN петабайтный поставит для его хранения и раздачи? А, может, затолкает все истекшие/отозванные серты в экзабайтный ZFS с NoSQL DB и построит супер-пупер-мега-дупер-OCSP responder? А на какие шиши, интересно, если он бесплатен и работает на чистом человеколюбии?

Как бы то ни было - "ищи, кому выгодно", как говорили латиняне. Всеобщий HTTPS выгоден только Корпорации Бобра - так как позволяет знать и то, что вы желали бы скрыть, а еще позволяет уделать кэширующих HTTPS конкурентов - потому что расшифровывает и кэширует все, а не только те сайты, на которые выписаны подписанные рутами серты. А вас еще и развели, как лохов, убедив в том, что всеобщий TLS якобы делает коммуникации безопаснее. Черта с два! Даже TLS 1.3 этого не сделает, так как все, что могло бы послужить к усилению защиты, оттуда старательно выхолостили. Ка-ак раз по вышеприведенным причинам.

Finally. По моему глубокому убеждению, повсеместный HTTPS не только не нужен - он опасен, так как создает иллюзию безопасности не только у хомячков, но и у айтишников (вы ж не желаете вникать - как оно там работает внутри, верно?). И, по моему глубокому убеждению, HTTPS должен сохраняться лишь на логин-страницах (и, может быть, на страницах с формами персональных данных), и то лишь в виде Certificate Pinning. Во всех остальных случаях HTTPS не нужен. А данные мессенджеров, включая медийные, на ракетный залп не должны подходить к HTTPS и передаваться исключительно под E2E по собственным каналам. HSTS тоже должен умереть, будучи лобковыми волосами, которые в одно действие могут быть выдернуты простыми манипуляциями с заголовками HTTP.

А Let's Encrypt можете смело засунуть прямо в задницу - до тех пор, пока существует Gogle Global Cache, стоящий посредине. И не пытайтесь меня убедить в том, что это-де защищает от чего бы то ни было и гарантирует хоть что бы то ни было.

То, что знают трое - знает и свинья.

среда, 12 сентября 2018 г.

Squid 5: make things faster - part 2

Одним из самых заметных узких мест в Squid является disk IO.

Проблема в том, что значительная часть кода Squid является самым настоящим legacy, с присущими стародавним временам алгоритмами, и, самое главное, константами и лимитами.

Разумеется, всегда можно затопить проблему деньгами и, в самом лобовом случае, просто купить SSD. Что будет не только дорого, но очень дорого.

С учетом того, что ванильный Squid имеет чудовищный объем записи, SSD просто долго не проживут, что дополнительно увеличивает TCO.

Вернемся к нашим константам. Одна из важнейших констант, впрямую влияющая на скорость записи, это SM_PAGE_SIZE. Ее размер по умолчанию равен 4096 (байт), что очень мало.

Скорость записи (swap out) в Squid сама по себе невысока. Запись (за исключением режима diskd, который считается современными админами архаичным, а напрасно) выполняется в один поток, и, при таком размере буфера, выполняется медленно и печально.

Небольшое лирическое отступление Вам следует иметь в виду, что неблокирующую параллельную запись и чтение имеет только diskd. С этой точки зрения выгодно создать cache_dir под diskd на физически разных шпинделях. aufs имеет потоковое чтение (64 треда по умолчанию, что может быть чрезмерным для некоторых контроллеров), но однопоточную запись. Многопоточная запись в Squid, несмотря на наши неоднократные попытки, легко не реализуется (затрагивается слишком много модулей) и, самое главное, не работает. Все это осложняется тем, что сам Squid в принципе однопоточный.

Строго говоря, выбор такого значения понятен - подавляющее большинство ОС имеет именно такой (десктопный) размер страницы памяти. Также, размер блока большинства файловых систем также равен 4-8 Kb.

К этому же размеру приведены и многие внутренние буферные структуры.

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

С одной стороны, многие ОС позволяют использовать бОльшие страницы памяти. Помимо того, что вы можете слинковать Squid с другим аллокатором (либо использовать кастомный аллокатор, как это сделали мы), вы можете использовать что-то вроде libumem.

С другой стороны, стоит также воспользоваться файловыми системами, позволяющими изменить размер кластера FS.

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

Мы провели ряд экспериментов, с использованием кастомного аллокатора памяти (pool-based) с размером пула 64 Кб (чтобы не трогать размер страницы памяти ОС - это не всегда легко и, тем более, не всегда возможно), и файловой системой с размером кластера также равным 64 Кб.

После чего задали SM_PAGE_SIZE равным 64 Кб:

 /* Default was #define SM_PAGE_SIZE 4096 */  
 /* With custom allocator we're increasing it due to */  
 /* reducing memory trashing/decreasing IO ops */  
 #define SM_PAGE_SIZE 65536

Эффект был очень неожиданным. Более, чем на 40% упало общее потребление памяти процессом Squid. То есть значительно снизился memory trashing и фрагментация heap.

Кроме того, заметно снизился CPU во время активной обработки трафика. И ускорилась запись на диск (чтение заметно не выросло, а вот запись стала выполняться значительно быстрее).


Прекратился пугающий непрерывный прирост памяти процесса без сколько-нибудь заметного снижения при длительном аптайме (синий график; до оптимизации на данном сервере с идентичной конфигурацией среднее потребление памяти процесса достигало 1,2-1,4 Гб):


Одновременно с увеличением SM_PAGE_SIZE оказалось необходимо также увеличить параметр MAX_CLIENT_BUF_SZ, также по умолчанию равный 4096.

Эффект от увеличения параметров оказался в особенности заметным на сиблингах (siblings).

Я хочу подчеркнуть следующее. Менять данные параметры со штатной библиотекой аллокатора Squid достаточно бессмысленно. При размере страницы памяти по умолчанию и без одновременного увеличения размера кластера ФС существенных улучшений в лучшем случае не произойдет (в худшем вы можете получить segfault - будьте к этому готовы).

То есть это не волшебная кнопка "Сделать зашибись". Потребуется довольно много предварительной кропотливой работы, однако результат того стоит - запись данных в кэш и отдача данных из кэша (в случае дополнительного увеличения MAX_CLIENT_BUF_SZ) становится значительно быстрее.

Не могу сказать, что обычные диски начинают превосходить SSD - однако IO перестает быть узким местом

воскресенье, 26 августа 2018 г.

Каким должен быть протокол для мессенджера

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


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

Давайте определим, какие, собственно, требования мы хотим видеть выполненными в протоколе и реализации нашего идеального мессенджера. Дабы кто-нибудь вменяемый мог учесть эти пожелания и реализовать, наконец, в софте то, что никто не сделал.
  1. Мессенджер должен без всяких дополнительных телодвижений самостоятельно выбираться из-за NAT любой сложности, а также самостоятельно траверсить любые виды прокси-серверов. Данным свойством в настоящее время не обладает почти ни один мессенджер, за исключением Tox.
  2. Бутстрап и начальная идентификация должны быть цензуроустойчивыми, защищенными от блокирования, проходить по E2E каналу, поддерживать торификацию и вход посредством любого прокси. Идентификация не должна привязываться к мобильному или иному номеру в принципе. Такая возможность должна быть опциональной и она не должна быть основной или заданной по умолчанию.
  3. В идеале протокол мессенджера должен быть децентрализованным, по образу P2P. В случае, если протокол не P2P, сервера должны быть размазаны по множеству сетей IP, не должны тривиально идентифицироваться, и не должны видеть какие угодно данные пользователей. Собственно, я предполагал некие генерируемые FQDN во множестве доменов второго и первого уровня, например, с хэш-функцией в имени хоста сервера, однако не выглядящие как хакерские визуально. Только IP-адреса серверов - скверная идея, сети элементарно идентифицируются и накрываются. Также точки входа палятся по отсутствию SNI (правда, в TLS 1.3 SNI обязателен, так что Телеграм и дальше будет палиться своими точками входа без SNI и легитимных сертификатов в connection probing), поэтому SNI должен присутствовать.
  4. Полный E2E. Протокол физически не должен иметь никаких третьих сторон, которые могут видеть передаваемые данные. Причем E2E должен распространяться также на медийный и файловый контент, передаваемый по каналам связи - говоря простым языком, никаких CDN с TLS для контента быть не должно. Все передается исключительно в защищенном E2E канале.
  5. Обновления проверяются и передаются по защищенному E2E-каналу.
  6. Протокол не должен иметь фиксированных портов, что облегчает блокировку до предела. В идеале протокол должен иметь возможность работать по любому свободному порту.
  7. Протокол не должен иметь идентифицируемой сигнатуры. То есть должна поддерживаться обфускация или полная мимикрия под известный широкораспространенный протокол, например, HTTPS. Мимикрия должна быть совершенной - то есть соединение должно быть устойчиво к connection probing. Иначе говоря, если это мимикрия под TLS - сервер должен корректно устанавливать TLS-сессию, отвечать легитимными (подписанными честными рутами) FQDN, а сертификаты и их подписи должны быть вне каких бы то ни было подозрений.
  8. Протокол не должен использовать DNS-over-HTTPS. Как я писал ранее, HTTPS не является E2E, и может быть использован для идентификации протокола в случае использования MiTM. Протокол вообще не должен зависеть каким либо образом от DNS либо основываться на DNSSEC - и это должно быть обязательное требование. Некоторые айтишники справедливо указывали на DANE.
  9. Handover - в случае использования серверов, при отказе connection к текущей группе серверов (не одиночного сервера!) соединение должно переходить на новые точки автоматически, подгружая их, скажем, с серверов authority/guards/directory - как это делает Тор. Это обеспечивает, с одной стороны, отказоустойчивость, а с другой - устойчивость к цензурированию в реальном времени. С другой стороны, конечное число точек входа позволяет цензурировать бутстрап. В этой связи handover должен поддерживаться серверами на основе, скажем, постоянного keep alive к клиентам, что позволит обнаруживать потерю соединений в результате нарушения связности сети и восстанавливаеть его. В идеале реализована идея группы супернод (как в первоскайпе), когда нет фиксированных конечных authority/guards/directory серверов и любой сервер точки входа может выполнять такую роль с handover функций между ними.
  10. Финальное требование таково. В идеале, протокол работает по 443 порту, использует как наружную обертку абсолютно легитимное HTTPS-соединение, с честными сертификатами на стороне сервера, честными SNI, и, в случае MiTM, протокол неотличим от честного HTTPS (обфускация под настоящий HTTPS,  с заголовками и содержимым). Обфусцированный под HTTPS туннель на самом деле является честным E2E и не поддается атаке MiTM ни при каких обстоятельствах. Протокол не имеет конечных и однозначно идентифицируемых IP-сетей/диапазонов либо однозначно идентифицируемых FQDN (при использовании MiTM). Bootstrap распределен и устойчив к площадным блокировкам. Протокол не поддается классификации и не имеет четких сигнатур на транспортном уровне и фиксированных портов.
Безусловно, мессенджер должен поддерживать внешнюю проксификацию/соксификацию, чтобы его можно было направить в любой внешний по отношению к приложению туннель.  Но это уже пожелание к реализации собственно мессенджера, нежели собственно протокола. Как я писал ранее, весьма немногие мессенджеры (включая заявленные как безопасные) имеют такую возможность в принципе.

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

Прошу заметить вот что. Частичная реализация не имеет смысла. Реализовывать такое надо полностью - это раз, и два - это должна быть массовая реализация (пепел Matrix/Riot/Tox/Bitmessage стучит в наши сердца), а три - HTTPS/TLS должен использоваться исключительно как уровень прикрытия и ничего более.

среда, 15 августа 2018 г.

Squid 5: make things faster - part 1

В этой статье я хочу поделиться некоторым опытом промышленной эксплуатации Squid 5 и его оптимизационных трюков. Частично это касается и предыдущих веток.


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

Память и IO

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

Одна из скверно написанных подсистем - управление памятью и ввод-вывод.

Я уже писал про память в Squid. Проблема в том, что управление памятью в сквиде - лоскутное одеяло, очень фрагментарное, без единой архитектуры. Используются и пулы (местами и очень скверно), и страничное выделение - очень маленькими страницами (в 2018 году страница 4096 байт для сервиса, предполагающего неограниченное масштабирование), и общие области памяти, ну и, вишенка на торте - память SSL управляется openssl (вообще без всякой связи с общей идеологией управления памятью). Накладывается на все это тот факт, что в коде посейчас раскидана масса int, с присущим этому типу ограничением, что, в случае 64битный сборки приводит к неожиданным 32битным лимитам в самых неожиданных местах (вплоть до антипереполнения при подсчете статистики). Есть до сих пор и неочевидные места утечек, и внезапные segfaults из-за повреждений памяти. Начисто отсутствует такая вещь, как GC. То есть память, единожды выделенная, не освобождается уже никогда - даже если она не нужна.

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

Причем, что самое характерное, конфигурационными параметрами это не лечится.

Также это не лечится простой линковкой какого-нибудь стороннего аллокатора, наподобие tcmalloc.

Вторая сторона медали - IO. Внутренний и внешний ввод-вывод в сквиде выполняется через буферы heap (что само по себе достаточно дорого), но проблема в том, что эти буферы крошечные по умолчанию.

Как следствие, мы имеем высочайшие показатели физических операций ввода-вывода - на всех уровнях. И, прежде всего, на уровне дискового IO.

Некоторые коллеги пытаются залить проблему деньгами, покупая под cache_dir SSD. Однако следует понимать, что SSD смертны. Самое неприятное - они внезапно смертны. И, хотя потеря кэша - это не потеря БД, время восстановления - скверная штука. Да и стоимость таких решений - гм......

Кроме того, SSD лишь слегка разгружают дисковый IO, но не решают проблему в целом. Проблема с жором оперативной памяти не решается. Проблема фрагментации оперативной памяти не решается. Проблема гигантских показателей физических операций IO не решается (еще есть заморочки, связанные с файловыми системами и размерами их кластеров, но эта проблема несколько на другом уровне находится).

Все это приводит нас к необходимости некоторых переделок кода для решения вышеуказанных проблем.

Так как нам до смерти надоело иметь перманентные проблемы с оперативной памятью, мы с коллегой подошли к решению вопроса радикально.

Во-первых, мы нашли хороший аллокатор пулов с алгоритмической сложностью O(1). Мы встроили его в пятый сквид и подчистили подавляющее большинство вызовов xalloc(), приведя управление памятью к чанкам размерами 64K по умолчанию. Также мы задействовали GC данного аллокатора, и каждые несколько секунд собираем мусор. Это практически решило проблему неограниченно растущей памяти (с фрагментацией отдельная песня, полностью простой заменой аллокатора эта проблема не решается).

Во-вторых, мы полностью отключили нативные пулы Сквид:
 memory_pools off  

Да, мы абсолютно уверены, что наша malloc library outperforms Squid routines.

Пулы не нужны - используемый нами аллокатор сам по себе pool-based, причем делает это единообразно и значительно лучше, чем ванильный Сквид.

В-третьих, мы прошлись по коду и заменили все опасные int, связанные с памятью (и вообще с объемами) на long - мы работаем в 64 битах и не хотим налетать на внезапные дампы, связанные с переполнением int.

В-четвертых, мы задали достаточный размер кластера на файловых системах - кто, скажите на милость, в 2018м на терабайтных дисках использует кластер 4к?


Понятное дело, что, в случае использования hardware RAID придется плясать от параметров RAID, но их тоже можно и нужно настраивать (сюрприз! правда, не в таких широких пределах, как ZFS).

В-пятых (но не в последних), мы полезли в defines.h и нашли вот такой параметр:


Этот параметр достаточно серьезно влияет на большинство операций IO (и затрагивает массу модулей) и он привязан к размеру страницы памяти аллокатора сквида. Так как мы уже заменили аллокатор, мы можем смело увеличить этот параметр до величины, равной размеру нашего чанка памяти:


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

Результаты наших каторжных интеллектуальных трудов вот:


Значительно (почти вдвое) уменьшилась общая память процесса за счет уменьшения фрагментации.


Заметно (в среднем на 30%) упало использование CPU. Уменьшилась латентность. И, самое главное, сильно снизилось число дисковых IO.

Q.E.D.

Другие пожиратели памяти

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

Это ошибка.

Если мы говорим о масштабировании, то дьявол часто кроется именно в таких деталях.
  1. Сиблинги. Они весьма часто используются при построении масштабируемых конфигураций, однако мало кто знает, что периодическое построение дайджестов - тяжелый по CPU и памяти процесс, который включен по умолчанию даже тогда, когда вы его не используете:
  2.  # Default is on  
     digest_generation off
    
  3.  client db. Это пожиратель памяти, которая не будет освобождаться, и который, по большому счету, вам не нужен - вы и так используете статистические отчеты по access.log. Он может быть вам полезен, если вы олдфаг с непрозрачным транспарентным прокси, авторизациями, всякими протухшими обвязками по контролю и управлению пользователями и так далее.  По умолчанию он включен - а что, экономить память в XXI веке больше не нужно? Процессорные циклы тоже? Итак, сделаем это:
  4.  # TAG: client_db     on|off  
     #     If you want to disable collecting per-client statistics,  
     #     turn off client_db here.  
     #Default:  
     # client_db on  
       
     # Turn off collect per-client statistics  
     client_db off  
    

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

Еще одна деталь. Если вы используете рекурсивный кэширующий DNS (а вы наверняка его используете в масштабируемой конфигурации), вам не надо увеличивать от дефолта такой параметр, как ipcache_size (более того, его имеет смысл уменьшить):

 # TAG: ipcache_size     (number of entries)  
 #     Maximum number of DNS IP cache entries.  
 #Default:  
 # ipcache_size 1024    

Это забавно, но хороший тюнинг совсем не предполагает гигантских конфигов с non-default параметрами.

понедельник, 13 августа 2018 г.

Оптимизация регулярных выражений ECMAScript

Обычно и сами-то регулярные выражения - проблема для среднего айтишника (знаю не понаслышке, сам таким был когда-то ;)). Что уж говорить об их оптимизации - удалось написать чтобы работало и делало что задумано - и слава подземным богам, остальное уже не колышет. :)))))))))
Однако, когда от скорости обработки регэкспов зависит латентность или, того круче, масштабирование - приходится тягостно задумываться. Я уже не говорю о такой мерзкой вещи, как catastrophic backtrace, чтобы словить которую достаточно скормить относительно много текста относительно расширенному регулярному выражению с кучей жадных подвыражений.

Принято считать, что обработка регулярных выражений - штука тяжелая, медленная, и оптимизации не поддается.
Для того, чтобы поговорить предметно, давайте зайдем на regex101 и, для примера, рассмотрим три регулярных выражения:
 \:\/\/(.+?)\/.*\?[\w]+\=(.+?\.exe)  
 \:\/\/(.+?)\/[\w\/\-\.]+\?[\w]+\=(.+?\.exe)  
 \:\/\/([\w\-\.]+)\/[\w\/\-\.]+\?[\w]+\=(.+?\.exe)  

Все три выражения делают одно и то же. Они применяются к URL, содержащим исполняемый файл (обновление), и на выходе дают две группы - FQDN сайта и имя файла.

Можете выбрать URL, дающий match по данной регулярке, а потом посмотреть, сколько степов будет выполнено. Да, это достаточно условно, однако число шагов, определенных regex101, достаточно четко коррелирует с реальным числом шагов вашего движка (библиотеки) регулярных выражений.

Что вы увидите?

Что данные регулярные выражения имеют уменьшающееся число шагов сверху вниз (примерно в три раза - от 66 шагов до 22-23, в зависимости от обрабатываемого текста).

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

Что это означает на пальцах?

На пальцах это означает следующее. Жадные выражения вида .*, применяемые к неопределенному тексту, дают бОльшее число шагов, так как проверить надо все. Выражения вида [\w]+, по логике эквивалентные выражениям .*, имеют мЕньшее число шагов просто за счет того, что на тот же самый объем текста накладывается мЕньшее число проверок. Соответственно, это выливается в более быстрое вычисление регулярного выражения.

Означает ли это, что надо всюду и везде заменять .* на \w+ ?

Нет, не означает.

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

Более того, возможно, вы сильно удивитесь, однако POSIX regex как правило имеют прямую зависимость скорости от сложности - то есть, чем проще регэксп - тем быстрее и в меньшее число шагов он выполняется. А вот ECMAScript - сюрприз! - наоборот. Более простые регэкспы весьма склонны к разрастанию числа шагов по мере упрощения регэкспа и зачастую число шагов прямо пропорционально числу жадных подвыражений.

Это довольно логично, учитывая внутренности POSIX regex engines и ECMAScript. Но не слишком известно. ;)

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

Меньшее число steps при обработке регулярных выражений в критичных местах - это меньшее число циклов CPU. Меньше миллисекунд. А миллисекунды складываются в секунды, а секунды - это боль.

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

Finally. Да, регэкспы это боль и проблема сами по себе. Однако, если вы все же озаботитесь их оптимизацией - боли будет несколько меньше. ;)