CSV: Внутреннее устройство
Формат CSV — общий обзор
CSV (Comma-Separated Values) — текстовый формат хранения табличных данных, где каждая строка = запись, а значения разделены запятой (или другим символом). Формат существует с 1970-х — старше Unix. RFC 4180 (2005) попытался стандартизировать CSV, но реальные файлы редко следуют стандарту полностью.
Ключевая проблема CSV: формат не самоописывающий. Нет метаданных о типах, нет схемы, нет magic bytes. Файл — просто текст, и каждый reader интерпретирует его по-своему.
RFC 4180: что считается стандартом
RFC 4180 описывает MIME-тип text/csv и формализует минимальные правила:
CRLF = %x0D %x0A ; \r\n
COMMA = %x2C ; ,
DQUOTE = %x22 ; "
TEXTDATA = %x20-21 / %x23-2B / %x2D-7E ; printable без " и ,
field = escaped / non-escaped
escaped = DQUOTE *(TEXTDATA / COMMA / CRLF / 2DQUOTE) DQUOTE
non-escaped = *TEXTDATA
record = field *(COMMA field)
file = [header CRLF] record *(CRLF record) [CRLF]
RFC 4180 требует CRLF (\r\n) как разделитель записей. В реальности большинство Unix-инструментов генерируют LF (\n). Spark и DuckDB принимают оба варианта. Но wc -l на файле с CRLF покажет правильное число строк, а парсер, ожидающий только LF, может оставить \r в последнем поле каждой строки.
Quoting и escaping
Quoting — единственный механизм CSV для обработки спецсимволов. Правила просты, но порождают множество edge cases:
Quoting с переводом строки — главный источник проблем при параллельной обработке: нельзя просто разбить файл по \n и раздать фрагменты разным worker’ам, потому что \n внутри quoted field не означает конец записи.
Парсинг CSV: конечный автомат
Правильный CSV-парсер — это конечный автомат (finite state machine) с тремя основными состояниями:
Проблемы кодировки: BOM, UTF-8, Latin-1
CSV не содержит указания кодировки. Reader угадывает — и часто ошибается:
Проверяйте BOM в первую очередь: hexdump -C file.csv | head -1. Если первые три байта ef bb bf — BOM присутствует. Удаление: sed -i '1s/^\xEF\xBB\xBF//' file.csv или iconv --unicode-subst='' -f UTF-8-BOM -t UTF-8.
Отсутствие типов: schema-on-read
CSV не содержит информации о типах — каждое значение это строка. Типизация происходит при чтении (schema-on-read), и каждый reader может интерпретировать данные по-своему:
Альтернативные разделители: TSV, PSV, и другие
Запятая — не единственный разделитель. Некоторые данные содержат запятые в значениях так часто, что проще использовать другой символ:
Spark CSV Reader: опции и подводные камни
Spark CSV reader (spark.read.csv()) — один из самых используемых CSV-парсеров. Ключевые опции:
mode=PERMISSIVE + отсутствие проверки _corrupt_record — рецепт для silent data loss. В production ETL всегда используйте mode=FAILFAST или mode=PERMISSIVE + обязательная проверка df.filter(col("_corrupt_record").isNotNull()).count().
DuckDB CSV Reader: автоматический подход
DuckDB использует другой подход: сначала sniff (автоматическое определение delimiter, quote char, header, типов), затем чтение. read_csv_auto() анализирует первые N строк и угадывает формат:
Чтобы увидеть, что DuckDB определил: SELECT * FROM sniff_csv('file.csv'). Функция возвращает detected delimiter, quote char, escape char, header, column names и types. Полезно для отладки неправильного парсинга.
Производительность: CSV vs бинарные форматы
CSV — самый медленный формат для аналитических запросов. Каждая операция требует full table scan:
Итог
CSV — формат-компромисс: максимальная совместимость за счёт минимальной надёжности. Подходит для обмена данными между разными системами, но не для хранения и аналитики. Каждый CSV pipeline требует явного контракта: кодировка, разделитель, quoting, типы, формат дат, обработка null — всё должно быть задокументировано вне файла, потому что файл эту информацию не содержит.