Мое частое замечание к коду — неэффективный ввод-вывод. Примеры:

  • чтобы пройтись по строкам файла, человек читает его в память целиком и разбивает символами \r\n. Рано или поздно прилетает CSV на 5 гигабайт, и машине становится плохо.

  • То же самое с джейсоном: есть стрим, но разработчик читает его в гигантскую строку, а потом парсит ее.

  • Нужно записать в файл 100 тысяч строк? Человек джойнит их разделителем, получает километровую строку и пишет в файл.

  • Различные кодирования — base64, gzip и другие — делаются также: данные читаются в память целиком, из них получается результат тоже в памяти.

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

При этом разработчик обмазывает код вызовами gc в надежде, что это поможет.

Сколько подобных ошибок я исправил — не перечесть. В числе прочего был сервис, который падал от недостатка памяти, хотя ее было выделено запредельное количество. Оказалось, разработчик делал все из списка выше. Он получал огромные файлы, читал их в память, парсил, кодировал в JSON и gzip, используя строки и массивы. Когда код падал от OOM, он поднимал лимиты в облаке.

Это лишний раз подтверждает: сколько памяти ни дай, плохой код сожрет ее всю.

А решение простое — байтовые и символьные потоки. Ту же Джаву можно ругать за многое, но в ней очень хорошие потоки (абстрактные классы Input- и OutputStream, Reader и Writer). У них много наследников, каждый из которых делает свою работу. Например, буферизирующий поток, который сглаживает неравномерность сети и файлов. Потоки для сжатия, когда пишешь в него, а данные сжимаются в полете. Потоки, связанные с файлами, сокетами или устройствами. Потоки с подсчетом текущей строки и символа, потоки-пайпы (piped) для “переливания” данных между тредами — всего этого навалом.

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

Часто задача решается тем, что нужно построить стек потоков и скормить ему данные. Это труднее, чем прочитать файл в память и разбить на строки. Но не придется чинить в пятницу вечером.

Уделите время потокам ввода-вывода. Это прям очень полезная вещь.