Avez-vous de l'expérience avec des systèmes distribués?
Golang
/* Il y a deux serveurs PostgreSQL : * PROD - serveur OLTP, * STATS - serveur pour des requêtes analytiques longues. Sur le serveur actuel, dans la base de données prod, il y a une grande table (10Tb) de la forme suivante : CREATE TABLE profiles( id SERIAL, data JSONB ) La table peut contenir des "trous", c'est-à-dire que certains `id` peuvent être manquants. Il faut écrire un programme pour copier la table profiles de PROD à STATS. On suppose que l'on utilisera les interfaces suivantes pour travailler avec les bases de données : type Row []interface{} type Database interface { // l'implémentation de l'interface Database peut rétablir les connexions // l'appel à SaveRows est idempotent io.Closer GetMaxID(ctx context.Context) (uint64, error) LoadRows(ctx context.Context, minID, maxID uint64) ([]Row, error) // [minID, maxID] SaveRows(ctx context.Context, rows []Row) error } func Connect(ctx context.Context, dbname string) (Database, error) // CopyTable // Si full=false, continuer le transfert de données depuis le point de l'erreur précédente // Si full=true, transférer toutes les données func CopyTable(fromName string, toName string, full bool) error { // ... votre code } Si l'option `full=false` est passée, le programme doit continuer le transfert de données depuis le point de l'erreur précédente. Si `full=true`, il doit transférer toutes les données. **Niveau de base** : - transfert séquentiel des données en un seul flux - récupération après erreur (option `full=false`) Informations supplémentaires : - si nécessaire, vous pouvez étendre l'interface en ajoutant vos propres méthodes - si nécessaire, vous pouvez utiliser directement le package **database/sql**
Une chaîne de caractères est donnée. Trouvez le nombre de paires d'indices i et j (i <= j), entre lesquelles, inclus, il n'y a pas de caractères répétés. Pour la chaîne "aba", la réponse est 5 : [0, 0] ("a") [0, 1] ("ab") [1, 1] ("b") [1, 2] ("ba") [2, 2] ("a") Pour la chaîne "abcb", la réponse est ? : aba 3 + 2 = 5 abcb 4 (a, b, c, d) + 1 (ab) + 1 (bc) + 1 (cb) + 1 (abc) = 8
/* Une chaîne de caractères est donnée. Trouver le nombre de paires d'indices i et j (i <= j), entre lesquelles il n'y a pas de caractères répétés. Pour la chaîne "aba", la réponse est 5 : peuvent ne pas être seulement ASCII [0, 0] ("a") [0, 1] ("ab") [1, 1] ("b") [1, 2] ("ba") [2, 2] ("a") */
/* Nous devons transférer des données d'une source à un consommateur. La source fournit les données en petits lots (~dix enregistrements), tandis que le consommateur fonctionne mieux avec de gros lots (~mille enregistrements). Un exemple réel est le transfert de données depuis des files d'attente de type Kafka vers une base de données Clickhouse. Source : - Pratiquement infinie. - La source ne renvoie jamais plus de MaxItems enregistrements lors d'un appel à Next. - Dans le cadre d'une "session" (un appel à la fonction Pipe), la source renvoie de nouvelles données à chaque appel à Next. - Cependant, après un redémarrage, la source reprend à la position "confirmée" précédente, indiquée par cookie. Par conséquent, *chaque* valeur de cookie renvoyée par Next, après avoir sauvegardé les données dans le récepteur, doit être confirmée avec l'appel à Commit, dans le même ordre dans lequel elles ont été renvoyées par Next. Récepteur : - Ne peut traiter plus de MaxItems à la fois. Niveau de base : Il faut implémenter la fonction func Pipe(p Producer, c Consumer) error qui lit les données de la source, les regroupe dans un buffer de taille maximale MaxItems et les stocke dans le récepteur, après quoi elle confirme la progression dans la source. Difficulté supplémentaire : Les méthodes Next, Process et Commit sont liées à des appels réseau et peuvent prendre beaucoup de temps. Pour accélérer le processus, il faut paralléliser les processus de lecture, d'écriture et de confirmation de progression. De sorte qu'au cours de Process ou Commit, la lecture de la source et la formation du nouveau buffer continuent. */ const MaxItems = 9999 type Producer interface { // Next retourne : // - un lot d'éléments à traiter // - un cookie à confirmer une fois le traitement terminé // - une erreur Next() (items []any, cookie int, err error) // Commit est utilisé pour marquer un lot de données comme traité Commit(cookie int) error } type Consumer interface { Process(items []any) error } func Pipe(p Producer, c Consumer) error { // TODO }
Explique le principe d'inversion de dépendance et pourquoi l'appel direct aux méthodes du référentiel depuis un cas d'utilisation viole le principe SOLID.
Parle-moi des bases de données — avec lesquelles tu as travaillé et avec lesquelles tu travailles actuellement?
Quelle est la différence entre les environnements local, dev, stage, prod?
Comment avez-vous effectué le test et la vérification de la correction des requêtes effectuées?
/* Il existe une application avec une architecture de microservices. Un microservice peut être abstrait via une interface Backend. Pour accéder à une instance du microservice, on peut utiliser le type BackendImpl, qui est déjà implémenté. Chaque microservice a plusieurs dizaines d'instances en cours d'exécution, chacune accessible via sa propre adresse addr. Cependant, les instances individuelles du microservice ne sont pas fiables : elles peuvent échouer, être inaccessibles ou surchargées. Par conséquent, vous devez implémenter un type Balancer, qui implémente également l'interface Backend et effectue un équilibrage de charge côté client entre les instances du microservice, en choisissant à chaque fois l'instance **la moins chargée**. */
Comment JWT vérifie-t-il son authenticité ? Comment s'assurer que le jeton n'a pas été falsifié ?
/* Il existe une application avec une architecture de microservices. Un microservice peut être abstrait via une interface Backend. Pour accéder à une instance du microservice, on peut utiliser le type BackendImpl, qui est déjà implémenté. Chaque microservice a plusieurs dizaines d'instances en cours d'exécution, chacune accessible par sa propre adresse addr. Cependant, les instances individuelles du microservice ne sont pas fiables : elles peuvent échouer, être inaccessibles ou surchargées. Par conséquent, vous devez implémenter le type Balancer, qui implémente également l'interface Backend et effectue un équilibrage de charge côté client entre les instances du microservice, en choisissant à chaque fois l'instance **la moins chargée**. */ type Request interface{} type Response interface{} type Backend interface { Invoke(ctx context.Context, req Request) (Response, error) } var _ Backend = &BackendImpl{} // addr contient ip:port d'une instance spécifique func NewBackend(addr string) *BackendImpl type Balancer struct { // TODO } var _ Backend = &Balancer{} // addrs contiennent les adresses de toutes les instances équilibrées func NewBalancer(addrs []string) *Balancer { // TODO }
Avez-vous déjà eu une expérience en gestion d'équipe?
Quelle est la limitation d'un sémaphore et d'un canal tamponné comme solutions pour limiter les requêtes parallèles?
Que est ajouté à la réponse lorsque la fenêtre devient valide?
Comment comparer les éléments dans une carte ? Comment vérifier que deux cartes sont identiques ?
Quelle est la quantité maximale de requêtes par seconde (RPS) traitée par le service le plus chargé?
Écrire un problème en langage Go — section pratique (problèmes de la vie réelle du développeur, sans algorithmes) : améliorer ou corriger un service. Durée — 1,5 heures.
Format de travail : à distance, au bureau ou hybride ?
Comment rendre la mémoire constante (supprimer la dépendance du nombre de caractères uniques) ?