Les services en ligne génèrent d'importants volumes de journaux opérationnels et d'accès. Le stockage sous forme de texte brut permet des consultations rapides, mais s'avère inefficace lorsque vous devez filtrer, agréger et extraire des informations pertinentes à partir de données massives. Cette rubrique explique comment utiliser ApsaraDB for MongoDB pour stocker et analyser les journaux d'accès des serveurs web. Ces principes s'appliquent également à d'autres types de journaux.
Structuration des données de journal pour MongoDB
La structure que vous choisissez pour vos documents de journal impacte directement les performances des requêtes et les coûts de stockage. Cette section présente trois approches, de la plus simple à la plus optimisée.
Une entrée typique de journal d'accès d'un serveur web se présente comme suit :
127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "[http://www.example.com/start.html](http://www.example.com/start.html)" "Mozilla/4.08 [en] (Win98; I ;Nav)"
Cette entrée capture la source de la requête, le nom d'utilisateur, l'URL de la ressource, le code de réponse, le système d'exploitation et le navigateur.
Stockage de la ligne brute
L'approche la plus simple consiste à stocker chaque entrée de journal dans un champ de type chaîne unique :
{
_id: ObjectId('4f442120eb03305789000000'),
line: '127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "[http://www.example.com/start.html](http://www.example.com/start.html)" "Mozilla/4.08 [en] (Win98; I ;Nav)"'
}
Bien que fonctionnelle, cette méthode rend les requêtes complexes. ApsaraDB for MongoDB n'est pas un moteur d'analyse textuelle ; il est donc difficile de filtrer ou d'agréger des chaînes de journal brutes.
Analyse en champs structurés
Avant l'insertion, analysez chaque ligne de journal pour la diviser en champs distincts :
{
_id: ObjectId('4f442120eb03305789000000'),
host: "127.0.0.1",
logname: null,
user: 'frank',
time: ISODate("2000-10-10T20:55:36Z"),
path: "/apache_pb.gif",
request: "GET /apache_pb.gif HTTP/1.0",
status: 200,
response_size: 2326,
referrer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
user_agent: "Mozilla/4.08 [en] (Win98; I ;Nav)"
}
Les champs structurés permettent de filtrer et d'indexer des valeurs spécifiques, ce qui constitue un avantage majeur par rapport au stockage de lignes brutes.
Suppression des champs inutilisés
Des documents volumineux dégradent les performances et augmentent les coûts. Supprimez les champs qui ne feront pas l'objet de requêtes. Dans cet exemple, les champs user, request et status sont rarement nécessaires pour l'analyse. L'ObjectId _id encode déjà l'heure d'insertion ; vous pouvez donc également supprimer le champ time. Toutefois, conserver ce champ simplifie la rédaction des requêtes basées sur des plages horaires. Un document allégé se présente ainsi :
{
_id: ObjectId('4f442120eb03305789000000'),
host: "127.0.0.1",
time: ISODate("2000-10-10T20:55:36Z"),
path: "/apache_pb.gif",
referer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
user_agent: "Mozilla/4.08 [en] (Win98; I ;Nav)"
}
Utilisez des types de données compacts lorsque cela est possible. Par exemple, stockez les codes de statut HTTP sous forme d'entiers plutôt que de chaînes.
Écriture des journaux dans ApsaraDB for MongoDB
Les pipelines de journaux doivent souvent maintenir un débit d'écriture élevé. Maîtrisez le compromis entre durabilité et performance en définissant une option write concern pour chaque insertion :
db.events.insert({
host: "127.0.0.1",
time: ISODate("2000-10-10T20:55:36Z"),
path: "/apache_pb.gif",
referer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
user_agent: "Mozilla/4.08 [en] (Win98; I ;Nav)"
},
{
writeConcern:{w: 0}
}
)
Pour un débit d'écriture maximal, définissez l'option
write concernsur{w: 0}.Pour les journaux contenant des informations sensibles ou liées à la facturation, définissez l'option
write concernsur{w: 1}ou{w: "majority"}afin de garantir la durabilité.
Pour réduire les allers-retours réseau, regroupez plusieurs documents de journal dans une seule opération d'insertion :
db.events.insert([doc1, doc2, ...])
Interrogation des journaux dans ApsaraDB for MongoDB
Une fois les champs structurés en place, vous pouvez exécuter des requêtes ciblées sur la collection.
-
Requêtez toutes les demandes concernant un chemin spécifique :
q_events = db.events.find({'path': '/apache_pb.gif'})RemarqueSi vous exécutez fréquemment cette requête, créez un index sur le champ
path:db.events.createIndex({path: 1}). -
Requêtez toutes les demandes comprises dans une plage horaire donnée :
q_events = db.events.find({'time': { '$gte': ISODate("2016-12-19T00:00:00.00Z"),'$lt': ISODate("2016-12-20T00:00:00.00Z")}})RemarqueCréez un index sur le champ
timepour accélérer les requêtes par plage horaire :db.events.createIndex({time: 1}). -
Requêtez toutes les demandes provenant d'un hôte spécifique dans une plage horaire donnée :
q_events = db.events.find({ 'host': '127.0.0.1', 'time': {'$gte': ISODate("2016-12-19T00:00:00.00Z"),'$lt': ISODate("2016-12-20T00:00:00.00Z" } })Pour des analyses plus complexes — regroupement, comptage ou calcul d'agrégats — utilisez le pipeline d'agrégation ou le framework MapReduce. Indexez les champs utilisés pour le filtrage ou le tri afin de maintenir une latence de requête faible.
Partitionnement des données
À mesure que le volume de journaux augmente, la capacité d'écriture et de stockage d'un nœud unique devient un goulot d'étranglement. Partagez la collection pour distribuer les données sur plusieurs nœuds. Le choix de la clé de partitionnement influence directement le débit d'écriture et les performances des requêtes.
-
Clé de partitionnement basée sur l'horodatage (par exemple,
_idoutime) : Les horodatages augmentant de manière monotone, toutes les nouvelles insertions de journaux atterrissent sur le même shard. Cela crée un point chaud d'écriture et signifie que les requêtes portant sur les données récentes n'interrogent qu'un ou deux shards, au lieu de répartir la charge sur l'ensemble du cluster.Les nouvelles données de journal se concentrent sur un seul shard ; le débit d'écriture ne s'adapte pas à la taille du cluster.
Les requêtes portant sur les journaux récents ciblent uniquement quelques shards, laissant inexploitée la capacité d'interrogation totale du cluster.
Partitionnement haché : Utilise
_idcomme clé de partitionnement par défaut. Le hachage distribue les insertions uniformément sur tous les shards, permettant au débit d'écriture de monter en charge linéairement avec le nombre de shards. En contrepartie, les requêtes par plage — courantes dans l'analyse des journaux — doivent être diffusées sur tous les shards et fusionner les résultats, ce qui augmente la latence.-
Partitionnement par plage sur un champ bien distribué : Si un champ tel que
pathcomporte de nombreuses valeurs distinctes et uniformément réparties, et qu'il est fréquemment utilisé dans les requêtes, il constitue une bonne clé de partitionnement. Avantages :Les demandes d'écriture sont réparties uniformément sur les shards.
Les requêtes par plage sur
pathciblent un petit nombre de shards, améliorant ainsi l'efficacité.
Inconvénients :
Une valeur
pathà haute fréquence concentre ses documents dans un seul chunk ou shard, créant un point chaud.Un champ
pathà faible cardinalité entraîne une distribution inégale des données.
Pour remédier aux points chauds et à la faible cardinalité, ajoutez un champ secondaire à la clé de partitionnement. Par exemple, étendez
{path: 1}vers{path: 1, ssk: 1}.Attribuez une valeur aléatoire à
ssk— telle que le hachage de_id— pour disperser les chemins à fort trafic sur plusieurs shards. Vous pouvez également utiliser un horodatage afin que les documents partageant le mêmepathsoient triés chronologiquement au sein de chaque shard.La clé composite empêche toute valeur unique de la clé de partitionnement de dominer, équilibrant ainsi le débit d'écriture et la localité des requêtes. Chaque stratégie de partitionnement présente des compromis ; choisissez celle qui correspond à vos modèles d'accès et à votre trajectoire de croissance.
Gestion de la croissance des données
Les données de journal s'accumulent rapidement, mais leur valeur diminue avec le temps. Les données vieilles de trois mois ont rarement un impact sur l'analyse actuelle, tout en consommant de l'espace de stockage et en augmentant les coûts. ApsaraDB for MongoDB propose trois approches pour gérer le cycle de vie des données.
-
Index TTL (Time To Live) : Un index TTL est un index à champ unique qui supprime automatiquement les documents après une période spécifiée. Pour faire expirer les entrées de journal 30 heures après l'heure de la requête, exécutez :
db.events.createIndex( { time: 1 }, { expireAfterSeconds: 108000 } ).RemarqueLa tâche d'arrière-plan qui supprime les documents expirés s'exécute toutes les 60 secondes et est monothreadée. Sous de fortes charges d'écriture, les documents expirés peuvent s'accumuler plus vite que la tâche ne peut les supprimer, consommant temporairement plus d'espace de stockage.
Collections plafonnées (Capped collections) : Pour limiter l'espace de stockage plutôt que d'imposer une expiration basée sur le temps, utilisez une collection plafonnée. Lorsque la collection atteint sa taille configurée ou sa limite de documents, MongoDB supprime automatiquement les documents les plus anciens. Exemple : db.createCollection("event", {capped: true, size: 104857600000}.
-
Archivage périodique par collection : À la fin de chaque mois, renommez la collection actuelle et créez-en une nouvelle pour le mois suivant. Incluez l'année et le mois dans le nom de la collection :
events-201601 events-201602 events-201603 events-201604 .... events-201612Pour purger les données d'un mois, supprimez la collection correspondante :
db["events-201601"].drop() db["events-201602"].drop()Le compromis : les requêtes transversales sur plusieurs mois sont plus complexes car les résultats doivent être fusionnés à partir de plusieurs collections.