Prueba técnica - SSE

Wei-Yu

Hace un tiempo hice un proceso para una empresa que a duras penas entra dentro de big tech (si entra siquiera teniendo en cuenta lo difuso que es el concepto). Me gustó bastante lo que vi; el proceso, las expectativas del puesto y el nivel de exigencia. Parece que no sólo no me han cogido si no que me han ghosteado, sin feedback ni nada sobre el proceso (oof). Algo feo porque era la última entrevista pero tampoco me voy a calentar por ello.

La revisión de la prueba era la última entrevista en un proceso que incluyó un cultural fit con el hiring manager (con una pequeña conversación sobre mi trasfondo técnico). Una conversación técnica entrando en detalle respecto a unas cuantas cosas y esta última prueba en la que tuve que redactar una propuesta de solución a un problema que me dieron.

La posición era 'senior software engineer'. Así muy genérico :grin:. Yo tengo 4 años exp por si sirve de referencia.

Lo pongo por aquí por si alguien tiene curiosidad y, sobre todo, por si cuela y alguien tiene feedback o algo que comentar (redacción, approach, fallos de diseño, etc), que sé que por aquí hay gente bastante crack y la verdad que aún no he tenido un solo referente técnico en mi carrera así que a veces cuesta poner las cosas en perspectiva.

NSFW

Además de los puntos flacos que se mencionan al principio, añadir que donde tampoco me sentí muy cómodo fue en la parte de infraestructura, puesto que tampoco sabía bien sobre qué hacer énfasis o con qué granularidad tratarlo.

edit: la parte de 'service description' es un resumen mío del enunciado/doc que me pasaron, así que tiene su sesgo supongo.

1
wdaoajw

Así a bote pronto, la solución que propones no escala.

No puedes mantener datos en memoria porque si no no escalas horizontalmente ya que cada réplica de la app no tiene la misma información y te quedas limitado a una única instancia.

Para mejorar esto, usa una DB tipo Redis para almacenar esos datos que te permita acceder a ellos lo suficientemente rápido y además escalar tu servicio horizontalmente hasta el infinito, abriéndote la posibilidad de hacer updates basándote en diferentes tipos (A/B, canary, etc).

Además mejora tu availability dado que si un nodo cae el resto siguen funcionando

PD: he leído un poco en diagonal, si he dicho alguna burrada por ello, mil perdones xD

1 1 respuesta
Wei-Yu

#2 fallo mío, una de las constraints que imponían era no depender de stores externos para no incrementar la latencia con roundtrips de red adicionales. Si no se lo habría tirado a una caché distribuida instantáneo.

Con esa constraint se me complicó bastante gestionar la API stateful.

1
wdaoajw

En ese caso podrías probar a manejar la blacklist en un fichero almacenado en un NFS, de forma que puedas montar ese mismo volumen y compartir el fichero entre diferentes instancias.

Únicamente deberías leerlo al hacer boot, hacer un update y periódicamente (u on demand) para actualizar la info que mantienes en memoria.

De esta forma te permite escalar, aunque tengas datos en modo eventually consistent

1 1 respuesta
Wei-Yu

#4 Pero eso seguiría implicando un roundtrip de red y en el enunciado eran bastante explícitos con evitarlos (voy a añadir eso al notion). Creo recordar que un roundtrip dentro del mismo datacenter era 1000 veces más lento que una lectura en memoria.

Pensé en hacerlo en la misma máquina y también en navegar los ficheros a pelo en vez de serializarlos a una estructura (como el "range" tree) porque algunos son suficientemente pequeños como para resolverlo con un binary search, pero con las subnets se hacía muy complejo y al final tienes el bottleneck de los sockets del OS que creo recordar que no pasaba de 50k req/s así a ojímetro (aunque fuera el cuádruple sigue siendo una solución algo guarra).

Algo que no hice fue "presionar" y preguntar sobre esas constraints e igual lo vieron como que no exploraba los porqués de los requisitos. Me lo voy a apuntar.

3 2 respuestas
wdaoajw

#5 si claro, al final todo implica algún elemento de red pero si el availability es importante es el precio a pagar.

Si quitas eso del medio, pues si, ir directo a memoria y a campeonar, pero de alguna forma deberías de serializar esa info por si hay algún reinicio

1 respuesta
ignasi_

Como empezais a aprender sobre todo esto? Yo de back sé lo justo (REST) pero todo esto me queda lejos. Teneis algun recurso?

Gracias por compartir por cierto :)

1 respuesta
Wei-Yu

#6 eso se lo come el worker replicando el repo externo dentro de la infra propia y haciendo push async del diff. No me acabó gustando mucho la idea, me pareció algo ñapas pero la verdad que lo poco que me decían era a cuentagotas.

#7 Cada uno funciona distinto supongo. Yo lo que más noto para aprender es simplemente ir curioseando leyendo a gente hablar en inet o leer artículos y cosas que me voy encontrando. Si algo de lo que leo no me suena me meto en un rabbit hole. Habrá días que hago una lectura muy superficial y otros más a fondo pero en el agregado a lo largo de los años creo que he ido aprendiendo bastante así. De libros ahora estoy con designing data intensive applications y creo que es de los pocos que recomendaría de forma sincera.

Algo útil es seguir a gente buena en redes sociales que hable de su curro. Normalmente huyo de los gurús del XP porque parecen una secta y están en bucle repitiendo lo mismo.

1
kidandcat

Siempre puede escalar horizontalmente haciendo sharding y manteniendo las listas en memoria.

Kaledros

Tal como yo lo veo, tu problema es que tienes dos fuentes de la verdad: el fichero y la memoria de tu servicio. En un único servicio pues tira que te vas, pero si empiezas a replicarlo hasta N servicios tendrás N+1 fuentes de verdad que empezarán a divergir malamente (en cuanto se levante n2 vas a perder lo que tengas en memoria en n1 y tendrás falsos positivos). Sí o sí, necesitas reducirlo a una sola y que no implique roundtrips de red, si no he entendido mal. Es decir, que debería estar en memoria.

Lo único que se me ocurre es tener un servicio como fuente de verdad. El worker alimenta la memoria de ese servicio (fuente) y el resto de servicios (checkers) le hacen llamadas para comprobar IPs. De esa manera ya puedes escalar todos los checkers que quieras sin comprometer la integridad de los datos. Si el servicio fuente tuviese que escalar, dado que el intervalo de refresco es de un minuto deberías poder levantar hasta un Windows entero si hiciera falta entre pool y pool para que la memoria de tus servicios no tengan discrepancias. Entendiendo que parte del levantado del servicio fuente es meter la lista en memoria, claro.

Todo esto suponiendo que añadir una llamada HTTP más no es problema. Esto es, no se considera roundtrip, no pasa nada que sea bloqueante, etc.

aren-pulid0

Ojo que aveces no te tiran por el solo hecho de la solución, preguntar también es importante como por ejemplo si el límite de no poder hacer llamadas en red se podría sobrepasar para solventar el tema de la escalabilidad

Almenos a mi como entrevistador me aporta mucho valor y saber hacer de las personas.
En mi empresa han sacado del proceso a más de una persona por cosas así

Edit: Acabo de leer que lo has puesto por arriba en #5

2 respuestas
Kaledros

#11 Eso es lo deseable, pero a mí me tiraron una vez por, y cito, "no ceñirme a la descripción del problema y querer buscar atajos". Lo único que hice fue preguntar por qué unas constraints del problema eran como eran y cuál era el motivo XD

1
Wei-Yu

Esto es cp del enunciado

NSFW

Una alternativa que se me ocurrió fue cachear con un TTL relacionado a la tasa de "refresco" (1 min en el supuesto) pero no te garantiza que las instancias del orquestador mantengan sus datos bien, como mucho si mantienes esa tasa de refresco en intervalos fijos e inicializas las instancias con un TTL calculado (ej al inicializar el pod/instancia llamas para preguntar el tiempo al siguiente "checkpoint" de caducidad y seteas el ttl al diff con el tiempo actual). Esta la descarté por algo que no recuerdo ahora mismo (xd)

#11 no, si que me hayan tirado me da igual, pero tampoco he tenido nunca feedback en mi curro para aprender y ya que esta prueba me resultó interesante y el constraint del datastore me buscó algo las cosquillas pues lo pongo por aquí.

Soy bastante activo y planifico preguntas y cosas para la entrevista, a ese respecto poco puedo mejorar más allá de que un día random esté cansado o algo así. Al margen de que al ser esto un hobby más me apetece hablar y suelo tener conversaciones distendidas.

Lecherito

Pues yo esto lo veo mas de sharding.

Aunque sinceramente, esto no meteria ni redis ni mierdas, un set con o(1) en el buscar y a chutar. Con una maquina para el failover como mucho, en las dns y au.

1 respuesta
JuAn4k4

Yo les hubiera dicho, si queréis allow list y deny list, pues metes WAF delante del ALB y a tomar por culo.

Yo metería cache del tipo “remember me”, al final calculas cada IP una vez, así disminuye mucho el rollo de filtrar.

Luego un reconciliation loop, creo que algo así tienes que te sincroniza las masks en memoria. Esto lo haría almacenando los datos en algún store externo y leyendo cada X del mismo, no afecta a la latencia por que se have en background.

El árbol es la mejor forma para subnet masks, no se si se contempla también CIDRs que es lo usual para bloquear IPs, en cuyo caso creó que también se puede hacer con un árbol.

No veo ninguna cosa así chocante, lo que no entiendo muy bien es lo del worker, queues y tal, no se por que está separado de lo que es el nodo principal.

Para hacerlo más escalable puedes usar sharding y tener divididas las IPs que gestiona cada nodo de tu cluster, de forma que para una IP le aplicas un mask, calculas un consistent hash y te da el nodo que tiene el shard de ese rango (es decir la sharding key es parte de la IP). Pero vamos que tampoco merece la pena liarse tanto

1 2 respuestas
RedSpirit

Este hilo es genial para darme cuenta de que no tengo ni zorra XD

10
wdaoajw

#14 sin data store solo te queda sharding y persistencia a un fichero.

Teniendo sharding ni falta que te hace el failover, lo único que necesitas es algún tipo de service discovery entre instancias si las vas a escalar automáticamente y quieres distribuir la carga. Si no, pues DNS con failover a secundaria y apañaos.

De todas formas en el 99.9% de casos implementar sharding en lugar de caché distribuida es innecesario

Lecherito

Claro, con sharding basicamente se divide el problema en N, y algo tan sencillo como los 4 primeros bits de la IP baja muchisimo la cantidad de datos que se han de almacenar. Pero eso lleva al problema de como cargar los datos de una manera efectiva y que hacer cuando se desbalancea los datos. Para cargarlos de manera efectiva supongo que deberia de haber algo que divida los datos primero y los deje listos para los shards, cosa que veo trivial.

Lo que no se de normal es lo bien que se ve poner un redis/dynamodb como backend de algo y quedarse tan pancho en una entrevista que se supone que evaluan el conocimiento. Nano si hasta el mas palurdo te mete una mierda del estilo distribuida sin pensar y ale, tienes tu escalado horizontal de free sin tener que poseer mas de 3 neuronas. No se si es lo que buscaban pero vamos xddd

Wei-Yu

con sharding os referís a segmentar por el primer bloque de la ip por ejemplo no? que ando con unas cuantas birras de más y no me entero bien

eso no sé si lo refleje en el documento pero sé que lo mencioné, aunque más como herramiwnta para rebajar la latencia manteniendo réplicas en el edge que escalar en sí. Lo que vi xon el tema de las IPs es que hay ee varios tipos (ej a, b, c) y quizás puedas segmentar por la topología ñero no me convenció del todo; de qué gorma haríais sharding?

#15

No veo ninguna cosa así chocante, lo que no entiendo muy bien es lo del worker, queues y tal, no se por que está separado de lo que es el nodo principal.

Con esto te refieres a tener el componente de la api en el mismo "pod" que el worker? eso lo barajé porque sé que se usa para cosas de ultra low latency (como por ejemplo algunas situaciones de obserbabilidad me suena haber leído) pero me pareció complicar demasiado el diseño

1 respuesta
desu
#15JuAn4k4:

Yo les hubiera dicho, si queréis allow list y deny list, pues metes WAF delante del ALB y a tomar por culo.

La solucion es esta.

Querer meter un "ip bouncer service" no tiene ningun sentido. No tiene ningun sentido querer meter un "servicio" en una capa tan lejana a cuando estableces la conexion... Boberia. Si sabes que la conexion es invalida porque transmites a otro servicio?

No me acuerdo como se hacia pero esto se daba en networking en la uni... no se hacia modoficando unas tablas como ip routing? Y diria que falla al tratar de establecer la conexion. En el paso de leer los headers.

Ademas hacerlo en network layer mueve la computacion de user land a kernel. De hecho estoy seguro que hay hardware especializado para estas cosas... No tengo ni puta idea pero no lo dudo, la CDN de netflix tiene cosas asi... Aunque estas optimizaciones a nivel de OS aun no esta disponible en linux vs bsd... Seguro que por hardware lo resuelven.

1 respuesta
JuAn4k4

#20 iptables o un firewall/security groups que hace lo mismo.

#19 Si teniendo en cuenta que el objetivo del ejercicio es mostrar cómo vas a hacer un servicio cualquiera, como te dicen la mejor solución al problema como tal es no hacerlo en un servicio sino como un filtro de network.

1 respuesta
desu

#21 si pensar en "security groups" es una buena manera de avanzar porque seguro que hay doc o blogs de los proveedores cloud tipicos... googleo y al primer resultado ya tengo la respuesta al ejercicio:

https://www.reddit.com/r/aws/comments/sozupa/security_groups_under_the_hood/

AWS para estas cosas sobre ec2 tiene sus chips dedicados (los nitro? o algo asi, no domino de aws). Y google/microsoft, uno de los dos, ha comprado este año una startup de chips para hacer lo mismo.

Seguro que si googleas docu de CISCO e IBM encontraras mucha docu de como montar estas cosas a varios niveles y los trade offs. hardware switches/routers => hardware network => hyper visors => kernel => user

para los requerimentos de monitorear, no se que info se queda al descarte, pero puedes enrutar a un sandbox y hacer lo que quieras ahi.

al final, la solucion mas sencilla y simple, siempre es la mejor.

wdaoajw

Pero en los requisitos le piden que sea a nivel de aplicación en un path concreto, osea capa 7.

Que entiendo que la idea es explicar cómo harías eso sin tener en cuenta soluciónes tipo WAF, security groups, ni iptables ni nada. Puro http devolviendo un 401/403 si la IP del requester esta blacklisted o no.

1 1 respuesta
desu

#23 Creo que te lias con el enunciado. Si quiero filtrar IPs como pide la descripcion del problema sera en todo el sistema. Que sentido tiene solo el path interno de autorizar? XY problem, huevo o gallina. El problema es filtrar IPs / spam y que sea rapido. No bloquear ips random en /auth. O es que habra casos que /auth hay que bloquear pero otros no? Fijate que si ese fuese el problema me lo explicarias de otra manera...

Ahora, como nota, si de verdad el enunciado es ese. Y me piden hacer un servicio HTTP. Mi respuesta es que no quiero trabajar en esa empresa. Porque no tiene sentido lo que piden. Demuestran no tener ni idea. O demuestran no tener las capacidades tecnicas para implementar la solucion buena.

Y para terminar, la solucion propuesta no es hacer WAF o security groups... Al menos la mia. Como he dicho arriba la solucion propuesta es "aplicar el bloqueo" en la primera capa posible. Sin complicarme mucho sigo pudiendo hacer todo esto en el kernel de mis instancias sin necesidad de tener un servicio http. Ejemplos malos:

  • No tiene ningun sentido tener un servicio para esto. Es como tener un loadbalancer o un reverse proxy que consulta a otro servidor por HTTP donde tiene que balancear... No el loadbalancer ya tiene las rutas en el binario... Y si puedes mover al kernel el trabajo mejor.

  • O para los FPeros, tengo un CRUD que consulta a otro servicio HTTP para saber a que DB tiene que escribir. Son niveles de indireccion inecesarios. Si tengo un CRUD para guardar vehiculos en mi db de vehiculos, no voy a crear un servicio para devolverle al servicio CRUD de vehiculos donde tiene que guardar los vehiculos ...

El ultimo ejemplo suena absurdo verdad? Asi de absurdo es el enunciado si de verdad me pide eso. Y estas cosas por desgracia las he visto muchas veces, las leo en blog posts y hasta hay gente que te vende libros con boberias asi. Asi que no me pareceria raro que fuese lo que de verdad te piden.

Estoy ruteando trafico de IPs por varios niveles, pero al llegar al final de todo, quiero implementar un servicio que lo bloquee... Despues de dar 10 saltos en mi sistema. Y para hacer este bloqueo, voy a crear un servicio HTTP extra para dar otros 4 saltos. Si de verdad esto no te parece absurdo... vamonos. La gente que no tiene ni puta idea se hace la picha un lio por dos chorradas y te hace un sistema distribuido que no tiene sentido porque potato.

1
Wei-Yu

En el spec que me dieron se habla de http api pero tampoco matizan mucho con nada más allá de que tienes esa forma de interactuar con el proceso. Sólo dan un set de requisitos que son los que tengo yo plasmados en la suma de service description+requirements.

Tiene que poder actualizarse y devolver metadatos (ej la lista origen).

No entiendo @desu tu comentario de mover de userland a kernel space. Si no tiene syscalls y ese es el driving factor del ROI que puedas tener hasta donde tengo entendido. Dejando de lado la abstracción de concurrencia que tenga el runtime que escojas, pero eso no debería ser algo de lo que te tuvieras que preocupar.

Sé que es mucho curro pero puedes hacer un draft tosco de cómo gestionar los metadatos de las IPs, mantener frescos los datos (updates+deletes) y cómo mantenerlo observable y escalable con tu alternativa?

Por cierto, al margen de todo esto si alguien también quiere añadir comentarios sobre estructura del texto o patrones que usé (como el C4 para los diagramas) se agradecería un montón.

1 respuesta
JuAn4k4

A ver, hay que tener en cuenta que es un ejercicio donde quieren normalmente ver cómo implementas algo, y que cosas tienes en cuenta para hacerlo en un servicio http. La lógica de dentro la suelen poner que tenga algo de miga implementar, royo esto o un rate limit o lo que sea y que no te dejen usar lo que ya está hecho y más que estudiado.

A no ser que el puesto sea más de infra y te pidan buscar soluciones para el problema real de filtrar allow_list /deny_list por CIDRs, en cuyo caso el enunciado sería algo diferente.

1
desu
#25Wei-Yu:

Por cierto, al margen de todo esto si alguien también quiere añadir comentarios sobre estructura del texto o patrones que usé (como el C4 para los diagramas) se agradecería un montón.

Todo el mundo lo hace a su manera. Yo lo que veo:

  • Si es una pregunta de system design. La respuesta no es de system design. Entra demasiado en las implementaciones y todas las implementaciones son del mismo tipo. Una propuesta deberia usar que hemos dicho juan y yo, otra podria ser usar un servicio http rest como el que proponeis... ya tienes dos lo suficientemente claras como para empezar a decidir.
  • Las alternativas de implementacion son demasiado opinion y poco hecho. Me importa poco (nada) tu opinion. Si no sabes decir si algo es bueno o malo o como implementarlo no puedes diseñarlo. Si te faltan datos para determinarlo deberias decirlo cuando comentas la alternativa.
  • Comparar las alternativas. PRO/CONS en una tabla y summary.
  • Mini roadmap de desarrollo. Este es un punto muy importante como de facil es tener un MVP para shippear. Te has centrado demasiado en intentar tener una solucion perfecta cuando aun no has resuleto el problema.
  • Coste $
  • Muchas cosas son 2 parrafos y deberian ser 2 palabras. En un sistema distribuido no hace falta comentar porque existe eventual consistency... es obvio.
  • El tema metricas en sistem design siempre esta relacionado con el caso de uso a resolver. Si estas bloqueando IPs es porque el usuario tiene problemas sistema tiene problemas. Que metrica quieres tocar? Cual es el impacto $ de tocar esta variable? No se habla nunca de que metricas tecnicas porque se dan por supuesto. Es como los logs...

Luego considera que esto es un documento donde al final lo comentareis entre todos... aunque alguien lo lidere (tu). Si alguien tiene dudas ya añadira un comentario al doc... Y al final lo mas importante es que no importa cual es la mejor propuesta. A nadie le importa hacer las cosas bien. He visto implementaciones que harian llorar al niño jesus que menos a mi a todo el mundo le parecia bien.

El system design y la implementacion no le importan a nadie hasta pasados 3 años que alguien tiene que re escribirlo todo porque (1) no se entiende una mierda, (2) no escala/falla/tiene bugs o bien (3) es mucho dinero. Y para cuando eso ocurra, nadie del equipo que lo diseño estara en el equipo que lo arregle. Entonces te acuerdas en la puta madre que quiso hacer un sistema event driven, DDD en Scala.

A mi tu doc ya me parece bien. Yo te aprovaba.

1
12 días después
B

Senior software engineer? Desde la ignorancia parece una tarea de devops. Qué eres realmente o cómo te catalogas?

1 respuesta
Wei-Yu

#28 es una prueba técnica de system design normal y corriente. Si fuera algo de devops entraría más al detalle de la infra, quizás otros usuarios que estén por ese campo puedan comentar cómo sería una prueba de devops análoga.

Algunas cosas que no me espero que sepa un devops serían:

  1. Cómo estructurar la información y manejar el ciclo de vida de esta
  2. Todo el análisis derivado de los componentes necesarios (porque son cosas a desarrollar ad hoc, no es recurir a prefabs).
  3. La escalabilidad de los componentes en sí más allá de lo que sepan a nivel de proceso.
  4. El propio dominio de la aplicación (ej las métricas útiles, los riesgos a nivel de negocio, etc).
  5. Race conditions y ramificaciones del modelo escogido de despliegue y balanceo de carga, puesto que todo esto es dependiente de qué hacen los componentes y cómo interactúan entre ellos.
1 respuesta
B

#29 pues yo leo #1 y de developer 0 pelotero.

Usuarios habituales