Node.js CSV version 4 - réécriture et performances
By WORMS David
19 nov. 2018
Ne ratez pas nos articles sur l'open source, le big data et les systèmes distribués, fréquence faible d’un email tous les deux mois.
Nous publions aujourd’hui une nouvelle version majeure du projet CSV Parser pour Node.js. La version 4 est une réécriture complète du projet axée sur la performance. Il comporte également de nouvelles fonctionnalités, ainsi que des amélioriation dans les options et les informations exportées. Le site officiel est mis à jour et le Changelog contient la liste des modifications apportées à cette version majeure.
Un travail massif
Le projet CSV de Node.js a été lancé le 25 septembre 2010. C’est assez ancien dans ce monde de la tech en forte évolution. Depuis lors, il a survécu à plusieurs évolutions de Node.js, telles que les refontes de l’API Stream. Au fil des ans, le projet a été maintenu par de la correction de bugs, de la documentation et du support. Avec l’aide de la communauté, des fonctionnalités supplémentaires ont été fournies pour répondre aux besoins de chacun. La qualité de la suite de tests nous a conforté dans notre confiance pour se replonger régulièrement dans le code et y apporter de nouvelles fonctionnalités. Cependant, il y avait une tâche que je n’avais jamais eu le courage d’initier : réécrire le coeur du parseur pour tirer parti de l’API Buffer et de ses promesses de performances. Quelques jours de vacances m’ont permis d’engager ce travail.
La réécriture a commencé avec un nouveau projet vierge. Bien qu’il y ait probablement encore de la place pour des améliorations et des optimisations supplémentaires, je lancé plusieurs études comparatives pour mesurer l’impact de plusieurs implémentations sur les performances. C’est ainsi que j’ai créé la classe Resizable Buffer, qui réutilise le même Buffer interne en s’adaptant au jeu de données d’entrée au lieu d’instancier un nouveau tampon pour chaque champ. Une fois prêt, l’étape suivante consistait à écrire l’analyseur. Le processus a été divisé en plusieurs itérations, 13 exactement :
- Itération naîve sur le Buffer d’entrée
- Ajout de
__needMoreData
- Ajout de
__autoDiscoverRowDelimiter
- Première ébauches des options
quote
,escape
,delimiter
, etrecord_delimiter
- Finalisation des options
quote
,escape
,delimiter
etrecord_delimiter
- Option
comment
- Options
relax_column_count
etskip_empty_lines
ainsi que les informationcount
,empty_line_count
etskipped_line_count
- Options
skip_lines_with_empty_values
,skip_lines_with_error
,from
,to
- Option
columns
- Option
trim
- Option
relax
- Options
objname
,raw
,cast
etcast_date
- Réécriture des compteur de l’objet
info
L’implémentation n’utilise plus CoffeeScript et est écrite directement sous JavaScript 6. Ne vous méprenez pas, je suis toujours un grand fan de CoffeeScript et nous l’utilisons encore dans les tests pour son expressivité. Cependant, j’avais besoin d’un contrôle précis du code et utiliser JavaScript comme langue principale encouragera, espérons-le, davantage de contributions.
Évolutions entraînant des incompatibilités
Dans l’ensemble, il n’y a pas de changements majeurs. Les modules sont les mêmes et l’API pour les utiliser est restée inchangée. Quelques modifications mineures sont toutefois à prendre en compte, telles que l’option rowDelimiter
désormais renommée en record_delimiter
, certaines options précédemment dépréciées maintenant supprimées et les compteurs disponibles regroupés dans la nouvelle propriété info
:
- Option
rowDelimiter
devientrecord_delimiter
- Drop the
record
event - Normalisation des messages d’erreur
{error type}: {error description}
- Isolation des information de compteur dans l’objet
info
count
devientinfo.records
lines
devientinfo.lines
empty_line_count
devientinfo.empty_lines
skipped_line_count
devientinfo.invalid_field_length
context.count
dans la functioncast
devientcontext.records
- Suppression des options dépréssiées
auto_parse
etauto_parse_date
- Suppression de l’évènement
record
- Dans l’option raw, la propriété
row
est renomméerecord
max_limit_on_data_read
devientmax_record_size
- Valeur par défault de
max_record_size
devient0
(pour illimité)
Le changement le plus important est probablement celui concernant l’option rowDelimiter
en record_delimiter
en raison de sa forte utilisation. De plus, max_record_size
est maintenant illimité par défaut et doit être explicitement défini s’il est utilisé.
Nouvelles fonctionnalités
Cette nouvelle version comprend également de nouvelles fonctionnalités. Le nouvel objet d’information regroupe quelques propriétés de compteur anciennement disponibles directement à partir de l’instance du parseur. Ces propriétés ont été renommées pour être plus expressives. L’objet d’information est directement disponible à partir de l’instance du parseur en tant que info
. Pour les utilisateurs de callback, cet object est exporté en tant que troisième argument de la fonction. Ils peuvent également être disponibles pour chaque entrée en activant l’option info
avec la valeur true
.
Cette version introduit 3 nouvelles options que sont info
, from_line
et to_line
:
info
: génère deux propriétésinfo
etrecord
,info
étant un instantané de l’objetinfo
au moment de la création d’une entrée, sous forme d’array ou d’objet ; remarque, il peut être utilisé conjointement avec l’optionraw
.from_line
: commence le traitement des entrées à partir de la ligne donnée.to_line
: arrête le traitement des entrées après la ligne donnée.
L’option info
est utile pour débuguer ou donner aux utilisateurs finaux des informations sur leur erreur.
Les options from_line
et to_line
filtrent respectivement les première et dernière lignes d’un ensemble de données. En parlant de lignes, les versions précédentes du parseur étaient confuses quand il s’agissait de compter des lignes et des délimiteurs d’entrées. Cela fonctionnait pour la plupart des utilisateurs pour la bonne raison qu’ils sont généralement égaux. Cette nouvelle version corrige le problème.
Voici la liste des nouvelles fonctionnalités extraites du Changelog :
- Nouvelles options
info
,from_line
etto_line
- trim : respecte
ltrim
etrtrim
lorsque définies delimiter
: accepte un Bufferdelimiter
: supporte plusieurs bytes/characterscallback
: exporte l’objetinfo
en tant que 3ème argumentcast
: attrape les erreurs dans les fonctions utilisateurs- TypeScript : définie les propriétés d’
info
comme étant en lecture seule et toujours présentes comment_lines
: compte le nombre de lignes entièrement commentés (sans données)
Prochaines évolutions
Le validité du code source est garanti par une large suite de tests. Aucun test n’a été supprimé et de nouveaux tests sont apparus pour renforcer les garanties de l’analyseur. Il est toutefois possible que certains tests ne traitent pas de certains comportements et, dans les semaines à venir, nous comptons sur vos retours pour résoudre les éventuels problèmes.
Bien que n’étant pas un grand fan des Promise ES6 dans le contexte de l’analyseur, la demande a été faite à plusieurs reprises et n’est pas bien compliqué à mettre en oeuvre. Il sera également implémenté dans les autres packages CSV.
Une autre amélioration potentielle consiste à étendre les objets d’erreur avec des informations supplémentaires telles qu’un code unique associé à chaque type d’erreur. Bien qu’amélioré, le contenu des message mériterait à être encore normalisé.
Je prévois également de supporter Flow, le vérificateur de type statique. Je ne l’ai jamais utilisé auparavant. Son intégration me semble approprié et cela me donnera l’occasion de l’essayer.
Enfin, j’envisage d’écrire un outil en ligne de commande qui exposera toutes les options disponibles et fournira plusieurs formats de sortie (JSON, ligne JSON, YAML, …).