IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

25.2. Serveurs de Standby par transfert de journaux

L'archivage en continu peut être utilisé pour créer une configuration de cluster en haute disponibilité (HA) avec un ou plusieurs serveurs de standby prêts à prendre la main sur les opérations si le serveur primaire fait défaut. Cette fonctionnalité est généralement appelée warm standby ou log shipping.

Les serveurs primaire et de standby travaillent de concert pour fournir cette fonctionnalité, bien que les serveurs ne soient que faiblement couplés. Le serveur primaire opère en mode d'archivage en continu, tandis que le serveur de standby opère en mode de récupération en continu, en lisant les fichiers WAL provenant du primaire. Aucune modification des tables de la base ne sont requises pour activer cette fonctionnalité, elle entraîne donc moins de travail d'administration par rapport à d'autres solutions de réplication. Cette configuration a aussi un impact relativement faible sur les performances du serveur primaire.

Déplacer directement des enregistrements de WAL d'un serveur de bases de données à un autre est habituellement appelé log shipping. PostgreSQL™ implémente le log shipping par fichier, ce qui signifie que les enregistrements de WAL sont transférés un fichier (segment de WAL) à la fois. Les fichiers de WAL (16Mo) peuvent être transférés facilement et de façon peu coûteuse sur n'importe quelle distance, que ce soit sur un système adjacent, un autre système sur le même site, ou un autre système à l'autre bout du globe. La bande passante requise pour cette technique varie en fonction du débit de transactions du serveur primaire. La technique de streaming replication permet d'optimiser cette bande passante en utilisant une granularité plus fine que le log shipping par fichier. Pour cela, les modifications apportées au journal de transactions sont traitées sous forme de flux au travers d'une connexion réseau (voir Section 25.2.5, « Streaming Replication »).

Il convient de noter que le log shipping est asynchrone, c'est à dire que les enregistrements de WAL sont transférés après que la transaction ait été validée. Par conséquent, il y a un laps de temps pendant lequel une perte de données pourrait se produire si le serveur primaire subissait un incident majeur; les transactions pas encore transférées seront perdues. La taille de la fenêtre de temps de perte de données peut être réduite par l'utilisation du paramètre archive_timeout, qui peut être abaissé à des valeurs de quelques secondes. Toutefois, un paramètre si bas augmentera de façon considérable la bande passante nécessaire pour le transfert de fichiers. L'utilisation de la technique de streaming replication (voir Section 25.2.5, « Streaming Replication ») permet de diminuer la taille de la fenêtre de temps de perte de données.

La performance de la récupération est suffisamment bonne pour que le standby ne soit en général qu'à quelques instants de la pleine disponibilité à partir du moment où il aura été activé. C'est pour cette raison que cette configuration de haute disponibilité est appelée warm standby. Restaurer un serveur d'une base de sauvegarde archivée, puis appliquer tous les journaux prendra largement plus de temps, ce qui fait que cette technique est une solution de 'disaster recovery' (reprise après sinistre), pas de haute disponibilité. Un serveur de standby peut aussi être utilisé pour des requêtes en lecture seule, dans quel cas il est appelé un serveur de Hot Standby. Voir Section 25.5, « Hot Standby » pour plus d'information.

25.2.1. Préparatifs

Il est habituellement préférable de créer les serveurs primaire et de standby de façon à ce qu'ils soient aussi similaires que possible, au moins du point de vue du serveur de bases de données. En particulier, les chemins associés avec les tablespaces seront passés d'un noeud à l'autre sans conversion, ce qui implique que les serveurs primaire et de standby doivent avoir les mêmes chemins de montage pour les tablespaces si cette fonctionnalité est utilisée. Gardez en tête que si CREATE TABLESPACE(7) est exécuté sur le primaire, tout nouveau point de montage nécessaire pour cela doit être créé sur le primaire et tous les standby avant que la commande ne soit exécutée. Le matériel n'a pas besoin d'être exactement le même, mais l'expérience monte que maintenir deux systèmes identiques est plus facile que maintenir deux différents sur la durée de l'application et du système. Quoi qu'il en soit, l'architecture hardware doit être la même -- répliquer par exemple d'un serveur 32 bits vers un 64 bits ne fonctionnera pas.

De manière générale, le log shipping entre serveurs exécutant des versions majeures différentes de PostgreSQL™ est impossible. La politique du PostgreSQL Global Development Group est de ne pas réaliser de changement sur les formats disques lors des mises à jour mineures, il est par conséquent probable que l'exécution de versions mineures différentes sur le primaire et le standby fonctionne correctement. Toutefois, il n'y a aucune garantie formelle de cela et il est fortement conseillé de garder le serveur primaire et celui de standby au même niveau de version autant que faire se peut. Lors d'une mise à jour vers une nouvelle version mineure, la politique la plus sûre est de mettre à jour les serveurs de standby d'abord -- une nouvelle version mineure est davantage susceptible de lire les enregistrements WAL d'une ancienne version mineure que l'inverse.

25.2.2. Fonctionnement du Serveur de Standby

En mode de standby, le serveur applique continuellement les WAL reçus du serveur maître. Le serveur de standby peut lire les WAL d'une archive WAL (voir restore_command) ou directement du maître via une connexion TCP (streaming replication). Le serveur de standby essaiera aussi de restaurer tout WAL trouvé dans le répertoire pg_xlog du cluster de standby. Cela se produit habituellement après un redémarrage de serveur, quand le standby rejoue à nouveau les WAL qui ont été reçu du maître avant le redémarrage, mais vous pouvez aussi copier manuellement des fichiers dans pg_xlog à tout moment pour qu'ils soient rejoués.

Au démarrage, le serveur de standby commence par restaurer tous les WAL disponibles à l'endroit où se trouvent les archives, en appelant la restore_command. Une fois qu'il a épuisé tous les WAL disponibles à cet endroit et que restore_command échoue, il essaye de restaurer tous les WAL disponibles dans le répertoire pg_xlog. Si cela échoue, et que la réplication en flux a été activée, le standby essaye de se connecter au serveur primaire et de démarrer la réception des WAL depuis le dernier enregistrement valide trouvé dans les archives ou pg_xlog. Si cela échoue ou que la streaming replication n'est pas configurée, ou que la connexion est plus tard déconnectée, le standby retourne à l'étape 1 et essaye de restaurer le fichier à partir de l'archive à nouveau. Cette boucle de retentatives de l'archive, pg_xlog et par la streaming replication continue jusqu'à ce que le serveur soit stoppé ou que le failover (bascule) soit déclenché par un fichier trigger (déclencheur).

Le mode de standby est quitté et le serveur bascule en mode de fonctionnement normal quand pg_ctl promote est exécuté ou qu'un fichier de trigger est trouvé (trigger_file). Avant de basculer, tout WAL immédiatement disponible dans l'archive ou le pg_xlog sera restauré, mais aucune tentative ne sera faite pour se connecter au maître.

25.2.3. Préparer le Maître pour les Serveurs de Standby

Mettez en place un archivage en continu sur le primaire vers un répertoire d'archivage accessible depuis le standby, comme décrit dans Section 24.3, « Archivage continu et récupération d'un instantané (PITR) ». La destination d'archivage devrait être accessible du standby même quand le maître est inaccessible, c'est à dire qu'il devrait se trouver sur le serveur de standby lui-même ou un autre serveur de confiance, pas sur le serveur maître.

Si vous voulez utiliser la streaming replication, mettez en place l'authentification sur le serveur primaire pour autoriser les connexions de réplication à partir du (des) serveur de standby ; c'est-à-dire, créez un rôle et mettez en place une ou des entrées appropriées dans pg_hba.conf avec le champ database positionné à replication. Vérifiez aussi que max_wal_senders est positionné à une valeur suffisamment grande dans le fichier de configuration du serveur primaire.

Effectuez une sauvegarde de base comme décrit dans Section 24.3.2, « Réaliser une sauvegarde de base » pour initialiser le serveur de standby.

25.2.4. Paramétrer un Serveur de Standby

Pour paramétrer le serveur de standby, restaurez la sauvegarde de base effectué sur le serveur primaire (voir (see Section 24.3.3, « Récupération à partir d'un archivage continu »). Créez un fichier de commande de récupération recovery.conf dans le répertoire de données du cluster de standby, et positionnez standby_mode à on. Positionnez restore_command à une simple commande qui recopie les fichiers de l'archive de WAL. Si vous comptez disposer de plusieurs serveurs de stanby pour mettre en œuvre de la haute disponibilité, définissez recovery_target_timeline à latest, pour indiquer que le serveur de standby devra prendre en compte la ligne temporelle définie lors de la bascule à un autre serveur de standby.

[Note]

Note

N'utilisez pas pg_standby ou des outils similaires avec le mode de standby intégré décrit ici. restore_command devrait retourner immédiatement si le fichier n'existe pas; le serveur essayera la commande à nouveau si nécessaire. Voir Section 25.4, « Méthode alternative pour le log shipping » pour utiliser des outils tels que pg_standby.

Si vous souhaitez utiliser la streaming replication, renseignez primary_conninfo avec une chaîne de connexion libpq, contenant le nom d'hôte (ou l'adresse IP) et tout détail supplémentaire nécessaire pour se connecter au serveur primaire. Si le primaire a besoin d'un mot de passe pour l'authentification, le mot de passe doit aussi être spécifié dans primary_conninfo.

Si vous mettez en place le serveur de standby pour des besoins de haute disponibilité, mettez en place l'archivage de WAL, les connexions et l'authentification à l'identique du serveur primaire, parce que le serveur de standby fonctionnera comme un serveur primaire après la bascule.

Si vous utilisez une archive WAL, sa taille peut être réduite en utilisant l'option archive_cleanup_command pour supprimer les fichiers qui ne sont plus nécessaires au serveur de standby. L'outil pg_archivecleanup est conçu spécifiquement pour être utilisé avec archive_cleanup_command dans des configurations typiques de standby, voir pg_archivecleanup. Notez toutefois que si vous utilisez l'archive à des fins de sauvegarde, vous avez besoin de garder les fichiers nécessaires pour restaurer à partir de votre dernière sauvegarde de base, même si ces fichiers ne sont plus nécessaires pour le standby.

If you're using a WAL archive, its size can be minimized using the parameter to remove files that are no longer required by the standby server. Note however, that if you're using the archive for backup purposes, you need to retain files needed to recover from at least the latest base backup, even if they're no longer needed by the standby.

Un simple exemple de recovery.conf est:

standby_mode = 'on'
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'
restore_command = 'cp /path/to/archive/%f %p'
archive_cleanup_command = 'pg_archivecleanup /path/to/archive %r'

Vous pouvez avoir n'importe quel nombre de serveurs de standby, mais si vous utilisez la streaming replication, assurez vous d'avoir positionné max_wal_senders suffisamment haut sur le primaire pour leur permettre de se connecter simultanément.

25.2.5. Streaming Replication

La streaming replication permet à un serveur de standby de rester plus à jour qu'il n'est possible avec l'envoi de journaux par fichiers. Le standby se connecte au primaire, qui envoie au standby les enregistrements de WAL dès qu'ils sont générés, sans attendre qu'un fichier de WAL soit rempli.

La streaming replication est asynchrone par défaut (voir Section 25.2.6, « Réplication synchrone »), auquel cas il y a un petit délai entre la validation d'une transaction sur le primaire et le moment où les changements sont visibles sur le standby. Le délai est toutefois beaucoup plus petit qu'avec l'envoi de fichiers, habituellement en dessous d'une seconde en partant de l'hypothèse que le standby est suffisamment puissant pour supporter la charge. Avec la streaming replication, archive_timeout n'est pas nécessaire pour réduire la fenêtre de perte de données.

Si vous utilisez la streaming replication sans archivage en continu des fichiers, vous devez positionner wal_keep_segments sur le maître à une valeur suffisamment grande pour garantir que les anciens segments de WAL ne sont pas recyclés trop tôt, alors que le standby pourrait toujours avoir besoin d'eux pour rattraper son retard. Si le standby prend trop de retard, il aura besoin d'être réinitialisé à partir d'une nouvelle sauvegarde de base. Si vous positionnez une archive de WAL qui est accessible du standby, wal_keep_segments n'est pas nécessaire, puisque le standby peut toujours utiliser l'archive pour rattraper son retard.

Pour utiliser la streaming replication, mettez en place un serveur de standby en mode fichier comme décrit dans Section 25.2, « Serveurs de Standby par transfert de journaux ». L'étape qui transforme un standby en mode fichier en standby en streaming replication est de faire pointer primary_conninfo dans le fichier recovery.conf vers le serveur primaire. Positionnez listen_addresses et les options d'authentification (voir pg_hba.conf) sur le primaire pour que le serveur de standby puisse se connecter à la pseudo-base replication sur le serveur primaire (voir Section 25.2.5.1, « Authentification »).

Sur les systèmes qui supportent l'option de keepalive sur les sockets, positionner tcp_keepalives_idle, tcp_keepalives_interval et tcp_keepalives_count aide le primaire à reconnaître rapidement une connexion interrompue.

Positionnez le nombre maximum de connexions concurrentes à partir des serveurs de standby (voir max_wal_senders pour les détails).

Quand le standby est démarré et que primary_conninfo est positionné correctement, le standby se connectera au primaire après avoir rejoué tous les fichiers WAL disponibles dans l'archive. Si la connexion est établie avec succès, vous verrez un processus walreceiver dans le standby, et un processus walsender correspondant sur le primaire.

25.2.5.1. Authentification

Il est très important que les privilèges d'accès pour la réplications soient paramétrés pour que seuls les utilisateurs de confiance puissent lire le flux WAL, parce qu'il est facile d'en extraire des informations privilégiées. Les serveurs de standby doivent s'authentifier sur le primaire avec un compte doté de l'attribut REPLICATION. Par conséquent, un rôle avec les attributs REPLICATION et LOGIN doit être créé sur le primaire.

[Note]

Note

Il est recommandé d'utiliser un compte utilisateur spécifique pour la réplication. Bien que l'attribut REPLICATION soit accordé aux comptes superutilisateurs par défaut, il n'est pas recommandé d'utiliser un compte superutilisateur pour la réplication. Même si l'attribut REPLICATION laisse beaucoup de liberté à un utilisateur, il ne l'autorise pas à modifier les données sur le primaire, alors que l'attribut SUPERUSER le permet.

L'authentification cliente pour la réplication est contrôlée par un enregistrement de pg_hba.conf spécifiant replication dans le champ database. Par exemple, si le standby s'exécute sur un hôte d'IP 192.168.1.100 et que le nom de l'utilisateur pour la réplication est foo, l'administrateur peut ajouter la ligne suivante au fichier pg_hba.conf sur le primaire:

# Autoriser l'utilisateur "foo" de l'hôte 192.168.1.100 à se connecter au primaire
# en tant que standby de replication si le mot de passe de l'utilisateur est correctement fourni
#
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    replication     foo             192.168.1.100/32        md5

Le nom d'hôte et le numéro de port du primaire, le nom d'utilisateur de la connexion, et le mot de passe sont spécifiés dans le fichier recovery.conf. Le mot de passe peut aussi être enregistré dans le fichier ~/.pgpass sur le serveur en attente (en précisant replication dans le champ database). Par exemple, si le primaire s'exécute sur l'hôte d'IP 192.168.1.50, port 5432, que le nom de l'utilisateur pour la réplication est foo, et que le mot de passe est foopass, l'administrateur peut ajouter la ligne suivante au fichier recovery.conf sur le standby:

# Le standby se connecte au primaire qui s'exécute sur l'hôte 192.168.1.50
# et port 5432 en tant qu'utilisateur "foo" dont le mot de passe est "foopass"
primary_conninfo = 'host=192.168.1.50 port=5432 user=foo password=foopass'

25.2.5.2. Supervision

Un important indicateur de santé de la streaming replication est le nombre d'enregistrements générés sur le primaire, mais pas encore appliqués sur le standby. Vous pouvez calculer ce retard en comparant le point d'avancement des écritures du WAL sur le primaire avec le dernier point d'avancement reçu par le standby. Ils peuvent être récupérés en utilisant pg_current_xlog_location sur le primaire et pg_last_xlog_receive_location sur le standby, respectivement (voir Tableau 9.56, « Fonctions de contrôle de la sauvegarde » et Tableau 9.57, « Fonctions d'information sur la restauration » pour plus de détails). Le point d'avancement de la réception dans le standby est aussi affiché dans le statut du processus de réception des WAL (wal receiver), affiché par la commande ps (voyez Section 27.1, « Outils Unix standard » pour plus de détails).

Vous pouvez obtenir la liste des processus émetteurs de WAL au moyen de la vue pg_stat_replication D'importantes différences entre les champs pg_current_xlog_location et sent_location peuvent indiquer que le serveur maître est en surcharge, tandis que des différences entre sent_location et pg_last_xlog_receive_location sur le standby peuvent soit indiquer une latence réseau importante, soit que le standby est surchargé.

25.2.6. Réplication synchrone

La streaming réplication mise en œuvre par PostgreSQL™ est asynchrone par défaut. Si le serveur primaire est hors-service, les transactions produites alors peuvent ne pas avoir été répliquées sur le serveur de standby, impliquant une perte de données. La quantité de données perdues est proportionnelle au délai de réplication au moment de la bascule.

La réplication synchrone permet de confirmer que tous les changements effectués par une transaction ont bien été transférées à un serveur de standby synchrone. Cette propriété étend le niveau de robustesse standard offert par un commit. En science informatique, ce niveau de protection est appelé 2-safe replication.

Lorsque la réplication synchrone est utilisée, chaque validation portant sur une écriture va nécessiter d'attendre la confirmation de l'écriture de cette validation sur les journaux de transaction des disques du serveur primaire et des serveurs en standby. Le seul moyen possible pour que des données soient perdues est que les serveur primaire et de standby soient hors service au même moment. Ce mécanisme permet d'assurer un niveau plus élevé de robustesse, en admettant que l'administrateur système ait pris garde à l'emplacement et à la gestion de ces deux serveurs. Attendre après la confirmation de l'écriture augmente la confiance que l'utilisateur pourra avoir sur la conservation des modifications dans le cas où un serveur serait hors service mais il augmente aussi en conséquence le temps de réponse à chaque requête. Le temps minimum d'attente est celui de l'aller-retour entre les serveurs primaire et de standby.

Les transactions où seule une lecture est effectuée ou qui consistent à annuler une transaction ne nécessitent pas d'attendre les serveurs de standby. Les validations concernant les transactions imbriquées ne nécessitent pas non plus d'attendre la réponse des serveurs de standby, cela n'affecte en fait que les validations principales. De longues opérations comme le chargement de données ou la création d'index n'attendent pas le commit final pour synchroniser les données. Toutes les actions de validation en deux étapes nécessitent d'attendre la validation du standby, incluant autant l'opération de préparation que l'opération de validation.

25.2.6.1. Configuration de base

Une fois la streaming replication configurée, la configuration de la réplication synchrone ne demande qu'une unique étape de configuration supplémentaire : la variable synchronous_standby_names doit être définie à une valeur non vide. La variable synchronous_commit doit aussi être définie à on, mais comme il s'agit d'une valeur par défaut, il n'est pas nécessaire de la modifier. Cette configuration va entraîner l'attente de la confirmation de l'écriture permanente de chaque validation sur le serveur de standby, même si cette écriture peut s'avérer être longue. La variable synchronous_commit peut être définie soit par des utilisateurs, soit par le fichier de configuration pour des utilisateurs ou des bases de données fixées, soit dynamiquement par des applications, pour contrôler la robustesse des échanges transactionnels.

Suite à l'enregistrement sur disque d'une validation sur le serveur primaire, l'enregistrement WAL est envoyé au serveur de standby. Le serveur de standby retourne une réponse à chaque fois qu'un nouveau lot de données WAL est écrit sur disque, à moins que la variable wal_receiver_status_interval soit définie à zéro sur le serveur de standby. Lorsque le premier serveur de standby est sollicité, tel que spécifié dans la variable synchronous_standby_names sur le serveur primaire, la réponse de ce serveur de standby sera utilisée pour prévenir les utilisateurs en attente de confirmation de l'enregistrement du commit. Ces paramètres permettent à l'administrateur de spécifier quels serveurs de standby suivront un comportement synchrone. Remarquez ici que la configuration de la réplication synchrone se situe sur le serveur maître.

Habituellement, un signal d'arrêt rapide (fast shutdown) annule les transactions en cours sur tous les processus serveur. Cependant, dans le cas de la réplication asynchrone, le serveur n'effectuera pas un arrêt complet avant que chaque enregistrement WAL ne soit transféré aux serveurs de standby connectés.

25.2.6.2. S'organiser pour obtenir de bonnes performances

La réplication synchrone nécessite souvent d'organiser avec une grande attention les serveurs de standby pour apporter un bon niveau de performances aux applications. Les phases d'attente d'écriture n'utilisent pas les ressources systèmes, mais les verrous transactionnels restent positionnés jusqu'à ce que le transfert vers les serveurs de standby soit confirmé. En conséquence, une utilisation non avertie de la réplication synchrone aura pour impact une baisse des performances de la base de donnée d'une application due à l'augmentation des temps de réponses et à un moins bon support de la charge.

PostgreSQL™ permet aux développeurs d'application de spécifier le niveau de robustesse à employer pour la réplication. Cela peut être spécifié pour le système entier, mais aussi pour des utilisateurs ou des connexions spécifiques, ou encore pour des transactions individuelles.

Par exemple, une répartition du travail pour une application pourrait être constituée de : 10 % de modifications concernant des articles de clients importants, et 90 % de modifications de moindre importance et qui ne devraient pas avoir d'impact sur le métier si elles venaient à être perdues, comme des dialogues de messagerie entre utilisateurs.

Les options de réplication synchrone spécifiées par une application (sur le serveur primaire) permettent de n'utiliser la réplication synchrone que pour les modifications les plus importantes, sans affecter les performances sur la plus grosse partie des traitements. Les options modifiables par les applications sont un outil important permettant d'apporter les bénéfices de la réplication synchrone aux applications nécessitant de la haute performance.

Il est conseillé de disposer d'une bande passante réseau supérieure à la quantité de données WAL générées.

25.2.6.3. S'organiser pour la haute disponibilité

Les opérations de validation effectuées avec la variable synchronous_commit définie à on nécessiteront d'attendre la réponse du serveur de standby. Cette réponse pourrait ne jamais arriver si le seul ou le dernier serveur de standby venait à être hors service.

La meilleure solution pour éviter la perte de données est de s'assurer de ne jamais perdre le dernier serveur de standby. Cette politique peut être mise en oeuvre en définissant plusieurs serveurs de standby via la variable synchronous_standby_names. Le premier serveur de standby nommé dans cette variable sera utilisé comme serveur de standby synchrone. Les serveurs suivants prendront le rôle de serveur de standby synchrone si le premier venait à être hors service.

Au moment où le premier serveur de standby s'attache au serveur primaire, il est possible qu'il ne soit pas exactement synchronisé. Cet état est appelé le mode catchup. Une fois la différence entre le serveur de standby et le serveur primaire ramenée à zéro, le mode streaming est atteint. La durée du mode catchup peut être longue surtout juste après la création du serveur de standby. Si le serveur de standby est arrêté sur cette période, alors la durée du mode CATCHUP sera d'autant plus longue. Le serveur de standby ne peut devenir un serveur de standby synchrone que lorsque le mode streaming est atteint.

Si le serveur primaire redémarre alors que des opérations de commit étaient en attente de confirmation, les transactions en attente ne seront réellement enregistrées qu'au moment où la base de donnée du serveur primaire sera redémarrée. Il n'y a aucun moyen de savoir si tous les serveurs de standby ont reçu toutes les données WAL nécessaires au moment où le serveur primaire est déclaré hors-service. Des transactions pourraient ne pas être considérées comme sauvegardées sur le serveur de standby, même si elles l'étaient sur le serveur primaire. La seule garantie offerte dans ce cadre est que l'application ne recevra pas de confirmation explicite de la réussite d'une opération de validation avant qu'il soit sûr que les données WAL sont reçues proprement par le serveur de standby.

Si le dernier serveur de standby est perdu, il est conseillé de désactiver la variable synchronous_standby_names et de recharger le fichier de configuration sur le serveur primaire.

Si le serveur primaire n'est pas accessible par les serveurs de standby restants, il est conseillé de basculer vers le meilleur candidat possible parmi ces serveurs de standby.

S'il est nécessaire de recréer un serveur de standby alors que des transactions sont en attente de confirmation, prenez garde à ce que les commandes pg_start_backup() et pg_stop_backup() soient exécutées dans un contexte où la variable synchronous_commit vaut off car, dans le cas contraire, ces requêtes attendront indéfiniment l'apparition de ce serveur de standby.