2 puntos por GN⁺ 2023-08-12 | 1 comentarios | Compartir por WhatsApp
  • El cuello de botella de Postgres de una única aplicación SaaS se agravó, con uso de CPU entre 60% y 80%, y en una ocasión llegó al 100%, provocando una breve caída
  • La escalabilidad vertical mediante una instancia de BD más grande ya había llegado a su límite, por lo que era difícil absorber más crecimiento de carga con el mismo enfoque
  • El sharding de escrituras y los microservicios pueden aumentar la capacidad y la resiliencia operativa, pero siguen dejando costos de complejidad en backups, monitoreo, migraciones, ORM e incluso topología de red
  • En la práctica, durante 3 meses se eliminaron consultas pesadas, se optimizó código Rails, se ajustó Postgres y algunas consultas caras de solo lectura se separaron hacia una BD de réplica
  • Como resultado, el uso máximo semanal de CPU de la BD bajó de 90% a 30%, abriendo margen para aprovechar el sistema actual por más tiempo antes de pasar a una arquitectura de próxima generación

Cuello de botella de Postgres y el fin de la escalabilidad vertical

  • A medida que aumentó la carga de una única aplicación SaaS, el rendimiento de Postgres se convirtió en el cuello de botella principal
    • El uso de CPU se mantenía en el rango de 60% a 80%
    • Al menos una vez se disparó hasta el 100%, causando una breve caída
  • Antes, cada vez que la BD se saturaba, ganaban tiempo cambiándola por una instancia más grande
    • Gracias a ese enfoque, podían enfocarse en otras tareas, como el desarrollo de funcionalidades
    • En ese momento ya estaban usando la instancia más grande, así que ya no era posible seguir con la escalabilidad vertical

Arquitecturas de próxima generación: atractivas, pero caras

  • Las principales opciones discutidas fueron el sharding de escrituras y los microservicios
  • El sharding de escrituras consiste en tener clústeres de BD independientes y escribir los datos en una BD específica según una estrategia de particionamiento
    • Potencialmente puede aumentar la capacidad en dos o tres órdenes de magnitud
  • Los microservicios dividen el monolito en varios servicios, y cada servicio tiene su propio almacén de datos
    • Permiten elegir el almacén de datos según la carga de trabajo de cada servicio
  • Ambos enfoques amplían las opciones en términos de tolerancia a fallas y recuperación operativa, pero el objetivo inmediato era devolver el rendimiento de la BD a un estado controlable

La complejidad, una vez introducida, sigue generando costos

  • El aumento de complejidad va más allá del costo de implementar la nueva estructura y se convierte luego en un costo de atención
  • Si se elige el sharding de BD, cada decisión técnica futura tendrá que lidiar también con esa nueva complejidad
    • Backups
    • Monitoreo
    • Migraciones
    • ORM
    • Topología de red
  • Los microservicios generan una carga similar, y mantener arquitectura adicional puede retrasar o incluso hacer que se abandonen entregas de funcionalidades

Primero buscar margen en el sistema actual

  • Incluso cuando parece necesaria una gran transición arquitectónica, muchas veces queda margen adicional en el sistema existente
  • Ajustar la carga de trabajo, hacer tuning de rendimiento y sumar sistemas auxiliares puede comprar meses o años de tiempo
  • Si estas opciones son viables, vale la pena intentarlas antes de construir desde cero un sistema de próxima generación

Optimizaciones realizadas en la práctica

  • En el primer trabajo, dos ingenieros se dedicaron principalmente a problemas de rendimiento de BD durante unos 3 meses
    • No hubo una solución milagrosa única
    • Usaron telemetría para encontrar consultas pesadas
    • Identificaron en qué puntos del codebase Rails se generaban las consultas
    • Optimizaron o eliminaron consultas
    • Ajustaron varias configuraciones de Postgres
  • En el segundo trabajo, otros dos ingenieros modificaron el codebase para que algunas consultas caras de solo lectura se ejecutaran en una BD de réplica
    • Separaron de la BD principal las consultas SELECT más frecuentes provocadas por el polling del cliente web

Resultados y principios operativos

  • Combinando ambos trabajos, el uso máximo semanal de CPU de la BD bajó de 90% a 30%
    • Uso máximo semanal de CPU: {l:90,30}
  • Se generó mucho margen de CPU y también aumentó la capacidad de quitar carga de la BD principal
  • Al tocar varias partes del codebase y colaborar entre varios desarrolladores, también se acumuló conocimiento distribuido sobre el sistema existente
  • La complejidad no siempre es mala, y algún día habrá que pasar a una estructura más compleja antes de llegar a los límites fundamentales de la arquitectura de BD
  • Hasta entonces, exprimir primero el sistema actual permite trabajar durante más tiempo con un sistema aburrido y simple en la medida de lo posible, y también resulta ventajoso en costo y practicidad

1 comentarios

 
GN⁺ 2023-08-12
Opiniones de Hacker News
  • Al ver problemas de rendimiento en bases de datos, mi idea más fuerte para proyectos nuevos es diseñar el hot path de la aplicación para que no use joins en absoluto.
    El almacenamiento es barato, así que se puede desnormalizar todo y actualizarlo todo dentro de una transacción. Es realmente sorprendente cuánto más rápido se vuelve todo al eliminar los joins. Las consultas analíticas ad hoc se pueden replicar a otra base de datos para análisis.
    Tengo sentimientos encontrados sobre DynamoDB de Amazon, pero la idea de que, para usarlo bien, primero hay que planear los patrones de uso y definir el esquema después es algo que vale la pena llevar también a las bases de datos relacionales. Hoy en día, incluso diría que, salvo para fines analíticos, los joins son innecesarios. Las principales bases de datos tienen propiedades ACID y el almacenamiento es absurdamente barato, así que basta con desnormalizar.
    Para evitar particiones calientes, conviene usar algo más parecido a un UUID en lugar de enteros. No es una solución mágica y tiene desventajas, pero uno puede acostumbrarse a un rendimiento “siempre aceptable” y escalable horizontalmente, en vez del excelente rendimiento de los enteros que algún día se va a romper.
    Una idea todavía más polémica sería indexar todas las columnas, pero ese es tema para otro día.

    • Sinceramente, no puedo estar más en desacuerdo. Mientras construía una startup, durante los años 1 a 5 casi no nos preocupamos por el rendimiento, y en el año 6 apareció un problema de rendimiento en una tabla específica que optimizamos con varios meses de trabajo de ingeniería.
      Desde el punto de vista de la deuda técnica, habría sido mucho más caro intentar que todo fuera rápido desde el principio. La velocidad de desarrollo se habría reducido mucho y probablemente habríamos fracasado en varias crisis.
      En cambio, pagamos unos cuantos miles de dólares más al mes en costos de máquinas de lo que realmente necesitábamos, y nos ahorramos meses de trabajo de personal en una etapa en la que no podíamos contratar suficientes ingenieros y el costo de oportunidad de desarrollar funcionalidades era alto. Como no podíamos saber de antemano dónde estaría el cuello de botella, hacer que todo fuera rápido desde el principio habría requerido entre 10 y 20 veces más trabajo. Algunos cuellos de botella fueron inesperados.
      Los joins pueden ser dañinos a gran escala, pero la mayoría de las startups, al menos al principio, no tienen problemas de escala. La desnormalización puede ser una buena optimización, pero se paga el costo de mantener sincronizadas todas las copias cada vez que hay cambios. Alguien acabará introduciendo un bug que no actualiza un campo desnormalizado que no es la fuente de verdad, y mostrará datos obsoletos a los usuarios. En general, usar joins y optimizar más adelante con un caché read-aside u otras técnicas suele ser más barato en costo total que retorcer el esquema.
    • Si el join está bien indexado y ocurre dentro del mismo servidor, no debería tener un gran impacto en el rendimiento. Decir que “eliminar los joins lo vuelve mucho más rápido” no es cierto si se usan correctamente los joins.
      En cambio, escribir un mismo dato en 20 lugares en vez de 1 es mucho más lento en términos de rendimiento, y las consultas se vuelven enormemente complejas y propensas a bugs. Terminas actualizando 18 lugares y olvidando 2.
      Se suele mencionar el almacenamiento barato como ventaja de la desnormalización, pero aquí el almacenamiento es el menor de los problemas. El verdadero problema es una superficie de bugs mucho mayor y un mal rendimiento de escritura, y ese rendimiento de escritura puede comerse fácilmente también el rendimiento de lectura.
    • Hay aplicaciones “altas” (tall) y aplicaciones “anchas” (wide). Casi todos los consejos sobre diseño y optimización de bases de datos son para aplicaciones altas.
      Una aplicación alta básicamente hace una sola cosa, y lo demás existe para apoyar esa cosa. La mayoría de las grandes tecnológicas que se te ocurran caen en esta categoría. En su modelo de datos hay solo unos pocos conceptos impulsores realmente importantes.
      Facebook básicamente tiene personas, publicaciones y anuncios. Netflix tiene cuentas y series/películas. En Amazon, el producto gira alrededor de vendedores, compradores y artículos, con quizá algunas piezas adicionales de logística detrás.
      Hay muchas aplicaciones altas porque son fáciles. Son mucho más fáciles que las aplicaciones anchas, a las que normalmente se llama “enterprise”. El software enterprise es malo porque es difícil, y ahí está el territorio menos explorado y una enorme oportunidad. Los jugadores establecidos como Oracle lo hacen pésimo ahí, y si entras con una mentalidad de aplicación alta, tú también lo harás.
      Consejos como “nunca uses joins” o “diseña en torno a una sola tabla” tienen sentido para aplicaciones altas, pero son consejos terribles para aplicaciones anchas. A menudo se ve a empresas de aplicaciones muy altas fracasar estrepitosamente al intentar hacer cosas fuera de su competencia principal, porque están llenas de personas que tratan estos consejos como sagrados.
      Ese consejo es para empresas que ya tuvieron éxito, hacen algo fácil y ya recogieron toda la fruta al alcance de la mano. Incluso una aplicación alta que aún no se ha convertido en víctima de su propio éxito no necesita destrozar su modelo de datos por rendimiento. Solo las empresas que ya tuvieron un éxito gigantesco y están exprimiendo hasta la última gota de rendimiento se preocupan por eso, y son precisamente las que menos necesitan consejos. Los consejos centrados en aplicaciones altas, como “si FAANG lo hace, tú también deberías” o “¿y qué vas a hacer si tienes mil millones de usuarios?”, contaminan la cabeza de quienes quieren hacer cosas más interesantes que mostrar anuncios a miles de millones de personas.
    • En los últimos años he visto más sufrimiento causado por desarrolladores apoyándose en la desnormalización que por problemas de rendimiento al hacer joins en tablas grandes.
      Los joins grandes tienen formas de corregirse en rendimiento, como moverlos más adelante a vistas materializadas o hacer ETL hacia un almacén columnar. Pero si alguien copia una columna subtotal_cents en los modelos Order, Invoice, Payment, NotificationEmail y UserProfileRecentOrders, y la hace ser referenciada o actualizada en 296 lugares, el camino de vuelta a un estado sano se vuelve largo.
    • Casi nunca he visto que los joins en sí sean el cuello de botella de rendimiento. Si hay un principio único que he observado, es: “no hagas tablas demasiado grandes”.
      Por lo general, los problemas aparecen en tablas con naturaleza de registros históricos. Los datos necesarios para la operación diaria son solo una fracción minúscula de la tabla real, pero trabajar sobre una tabla enorme se vuelve lento sin importar qué índices tenga. El solo hecho de agregar más índices también se convierte en un problema.
      Al menos en las bases de datos relacionales tradicionales, no basta con indexar todas las columnas: hay que definir los índices de combinación de columnas correctos que puedan ser utilizados. DynamoDB puede ser distinto.
  • Exprime al máximo lo que ya tienes y, después de hacerlo durante un tiempo, cambia la forma de ver el problema: si exprimes aquí, aquí y también aquí, de pronto te das cuenta de que queda mucho por aprovechar
    Optimizamos un monolito enorme durante unos dos meses: pasó de menos de 2 mil RPS, donde el PM y el equipo creían que ya no quedaba nada por exprimir, a menos de 3200 RPS con un cambio de hardware; luego, con unos días de ajustes, a 4 mil RPS; con un poco más de esfuerzo, a 10 mil RPS; y alrededor de una semana después, a 40 mil RPS
    Se empezó a decir “con esto basta, no hace falta ir más lejos”, pero después de cambiar bastantes cosas, saltó a más de 2 millones de RPS en una sola máquina, y un mes después ya procesaba de forma estable más de 40 millones de RPS con baja latencia. Todavía hay margen para empujar un poco más
    Hoy no usamos ni el 5% de la capacidad que se le puede sacar. Ese cambio vino simplemente de cambiar la forma de pensar el problema. Migrar de un servidor viejo a uno nuevo solo lo llevó de 1800 RPS a un poco más de 3000 RPS. Agregar hardware no solucionó el problema de fondo, y agregar complejidad solo postergó el problema. Al cambiar la forma de pensar el problema, cambiaron tanto el problema como la respuesta

    • Me da mucha curiosidad qué estaba haciendo tan mal ese monolito como para poder sacarle semejante rendimiento. Si las consultas eran pésimas, si había demasiadas consultas, si no había nada de caché, o si estaba haciendo de forma sincrónica cosas que podían procesarse en paralelo
    • Me pregunto si podrías compartir cuál era el contenido o el protocolo de esas solicitudes, y cuál era aproximadamente el tamaño del payload promedio de cada solicitud y respuesta
  • Para complementar eso de “dividir el monolito en varios servicios interconectados, y que cada servicio tenga su propio almacén de datos escalable según sus condiciones”, en este punto no hace falta separar todos los microservicios posibles. Basta con preguntar: “¿qué separación tendría el mayor impacto?”
    En mi caso, separé algunos datos de series temporales de Mongo hacia Cassandra. La estructura de tablas de Cassandra encajaba mucho mejor. Ese dataset tenía un esquema bien definido, y Cassandra podía empaquetar los datos de forma mucho más eficiente. En esa parte no necesitábamos la flexibilidad de los documentos JSON
    Como esos datos eran la mayor parte del total, después de eso Mongo quedó en un estado bastante satisfactorio. Solo hizo falta una separación. Técnicamente, antes y después seguía siendo un monolito; simplemente el mismo servicio pasó a escribir en dos bases de datos
    Irónicamente, más adelante un arquitecto de los que diseñan desde el escritorio quiso unificar todos los datos en un almacén de documentos JSON, y tuvimos varias conversaciones del estilo “ya recorrimos ese camino y sabemos dónde termina”

    • Es interesante cómo la idea de los microservicios se lanza como una “solución” demasiado obvia. No lo es
      El escalado vertical debería ser la primera solución obvia. Es algo que mucha gente pasa por alto y que el artículo también toca en parte, pero el escalado vertical se parece al escalado horizontal sin romper la consistencia de la base de datos
      Hay mucho margen por exprimir, y rara vez hace falta recurrir tan fácilmente a antipatrones como ignorar joins o validación de datos solo porque apareció un problema de rendimiento
    • En cierto sentido, el artículo también hizo una separación, porque movió ciertas consultas SELECT pesadas a una réplica
    • Curiosamente, yo suelo encontrarme con el problema opuesto. Tengo que explicar una y otra vez por qué Cassandra no encaja con datos de vida relativamente corta y actualizados con frecuencia. Es por los tombstones
    • También es importante que un solo servidor pueda leer de dos almacenes de datos. Igual que con la réplica de lectura mencionada en un comentario hermano, no hay nada que impida leer Postgres y Redis juntos dentro del mismo proceso
  • Si los desarrolladores saben leer los resultados de EXPLAIN/ANALYZE y hacer indexación correcta y optimización de consultas, pueden evitar muchísimas decisiones de sobrediseño
    Basta con registrar las consultas, filtrar las que se ejecutan muy a menudo o tardan mucho, cachear las que se ejecutan con frecuencia y optimizar las pesadas. Si se hace de forma sistemática, el sistema se vuelve más saludable
    En mi experiencia, lo que ayuda mucho son APM, logs de consultas lentas, réplicas de lectura/escritura de base de datos, particionamiento y sharding

    • Leer la salida de explain también puede ser todo un desafío. Las bases de datos son un mundo completamente aparte, sobre todo si casi no escribes SQL directamente
      Herramientas como https://explainmysql.com muestran con más claridad qué hay que optimizar en realidad, y ofrecen un marco más accesible para desarrolladores que saben lo suficiente como para configurar una base de datos, pero no tanto como para entender cómo se usa internamente
      Me imagino que alguien ya estará construyendo un sistema de IA que, al pasarle el esquema y los logs, ejecute mágicamente el SQL necesario para mejorar todo. No sé si confiaría en eso, pero muchas empresas preferirían usar algo así antes que contratar a un DBA dedicado
    • Me gustaría saber si hay buenos recursos para entender los planes de ejecución de SQL. En el proyecto actual estamos teniendo muchos problemas de rendimiento con consultas de MS SQL Server
      No sé si debería especificar siempre hints de índice para cada consulta. A veces parece que, aunque el índice existe, la consulta no lo usa. Creo que usar planes de ejecución de SQL me ayudaría a entender mejor este problema
    • No puedo contar cuántas sesiones de “arquitectura de próxima generación” he visto, y la mayoría podrían haberse reemplazado por una due diligence de la implementación actual
      No puedes arreglar código malo con una arquitectura nueva. Solo postergas el problema por un tiempo
  • Me viene a la mente una de mis frases favoritas: “Uno va a la guerra con el ejército que tiene, no con el ejército que quiere o que le gustaría tener más adelante”.
    Tal vez quieras ignorar el hecho de que la frase viene de Donald Rumsfeld. Aun así, dejó algunas expresiones excelentes, como “unknown unknowns”.
    Cuando trabajo en equipo, pienso mucho en esa frase. No todos están perfectamente de acuerdo ni tienen la misma comprensión o los mismos objetivos compartidos. Puede que alguien trabaje de una manera ineficiente o que yo no prefiera. Pero como es mejor tener un equipo que no tenerlo, hay que encontrar la mejor forma de lograr el objetivo con el equipo que se tiene.
    También aplica bien a los sistemas.

    • Rumsfeld tiene bastantes citas buenas. La mayoría salieron en el contexto de explicar cómo la guerra de Irak terminó siendo semejante desastre, y esa situación precisamente necesitaba con urgencia el tipo de liderazgo que, viendo solo sus citas, parecía que él podía aportar.
    • Cada vez que escucho el nombre Rumsfeld, recuerdo aquella escena en la que durante más de 10 minutos se negó a negar que fuera un lagarto.
      https://www.youtube.com/watch?v=XH_34tqxAjA
    • “Ningún plan de batalla sobrevive al contacto con el enemigo”.
      https://www.google.com/search?q=no+battle+plan+survives
    • La frase de Mattis, “el enemigo también tiene voto”, también es un buen recordatorio de la realidad. Aunque a la gente le molesta mucho, es útil en contextos como seguridad, privacidad y DRM.
    • También me gusta una frase similar de Steven Pressfield: “El atleta sabe que nunca llegará un día en que despierte sin dolor. Tiene que competir lesionado”.
      Esto aplica más a nosotros mismos que a los sistemas.
  • En mi experiencia, en apps web construidas sobre un ORM, cuando la carga de la base de datos se vuelve un problema, hay muchísima fruta al alcance de la mano que se puede obtener optimizando consultas.
    Más allá de lo básico, como “¿hay un problema N+1?”, los ORM a veces no generan la consulta óptima. No querría construir una app web de producción compleja sin un ORM, pero de vez en cuando hay que poder escaparse del ORM.
    Hay que perfilar las consultas que realmente se ejecutan en producción y consumen más recursos. Hay que mirar esas consultas y entender la forma de las tablas objetivo. A veces el ORM usa joins cuando en realidad se necesita una subconsulta, y a veces es al revés. También puede ser necesario preagrupar parte de los resultados o ajustar condiciones WHERE en joins complejos.
    He visto casos en los que una consulta generada por el ORM, que se ejecutaba con cierta frecuencia, estaba matando la base de datos, y con unos pocos cambios pequeños pasó de tardar más de 20 segundos a menos de 1 segundo.

    • Ahora estoy trabajando con SQLAlchemy, un ORM de Python, y hacer que use RETURNING en INSERT no es trivial. Hay que configurar la opción poco intuitiva expire_on_commit=False.
      Esa opción tampoco garantiza el uso de RETURNING: lo usa si el driver de la base de datos y la base de datos lo soportan, y si el ORM lo soporta para esa combinación de driver/base de datos. El SQL generado aparece en los logs, pero no hay una API para inspeccionarlo realmente, así que no hay forma de forzar el uso de RETURNING en la suite de pruebas salvo capturar y raspar nuestros propios logs. Por suerte, dentro del framework Pytest eso es bastante fácil.
      Me gustan los ORM, pero cosas como esta son frustrantemente complejas en varios niveles. También entiendo que SQLAlchemy es una biblioteca enorme y que no todo puede ser fácil. Aun así, este caso muestra bien los compromisos de usar un ORM.
      Sé que si se usa insert() directamente en Core se obtiene lo que uno quiere. Aquí hablo del caso de hacer .add() de un objeto ORM a un AsyncSession.
    • El gran problema de los ORM es que la orientación a objetos no es una buena forma de organizar software o datos en la mayoría de los dominios.
      La mayor parte de la lógica de negocio se expresa mejor en el lenguaje del álgebra relacional y algunas extensiones que con OOP.
  • “El costo real, y a menudo mucho mayor, del aumento de complejidad es la atención” también equivale a carga cognitiva.
    Estoy cansado de trabajar en sistemas de microservicios que igual tienen downtime y donde nadie sabe cómo funciona todo el conjunto. La mayoría en realidad son monolitos distribuidos: los cambios atraviesan varios servicios y hay que desplegarlos en orden. Los datos hay que replicarlos, los trabajos hay que sincronizarlos y el estado hay que compartirlo.
    https://www.youtube.com/watch?v=y8OnoxKotPQ

    • Los microservicios en los que “nadie sabe cómo funciona todo el conjunto” son un olor arquitectónico muy común. La idea original es que nadie pueda ni tenga que conocerlo todo.
      Pero si para arreglar o modificar el sistema alguien tiene que entenderlo completo, es una señal fuerte de que se rompieron reglas como la responsabilidad única o la abstracción correcta mediante APIs. Aun así, en mi experiencia es muy común. Para depurar un pipeline de N microservicios, a menudo hay que ejecutar y compilar localmente los N servicios.
      En sentido estricto, eso es un monolito con particiones de red y variabilidad infinita de build/deploy añadidas. Al final, lo veo como un entorno de trabajo extremadamente difícil que supera la capacidad de cualquier programador humano.
  • Me gusta este artículo. Durante los últimos meses intenté transmitirle el mismo mensaje a mi gerente, pero sin mucho éxito.
    Nuestro clúster de Redshift sobrecargado se cayó varias veces, y como ya lo escalamos al máximo con nodos RA3, ahora estamos por iniciar una gran migración a la “infraestructura de próxima generación”. En este caso, esa infraestructura de próxima generación son tres clústeres de Redshift administrados con CDK.
    La nueva infraestructura es mucho más compleja que la configuración actual, y no estoy seguro de que vaya a ser la bala de plata que todos esperan.

  • Es una solución aburrida. Si no estás construyendo un sistema ultracrítico del que dependan vidas o la integridad física de las personas, debería ser la respuesta por defecto. El costo total de propiedad definitivamente baja mucho.
    Si no tienes los recursos para operar un gran sistema redundante, he visto demasiadas veces que la propia complejidad agregada por el sistema redundante se convierte en el problema. Es mejor enfocarse en la simplicidad.
    Si para sostener la complejidad necesitas sumar mucha más gente, pero el dinero y la evaluación de riesgos indican que no hace falta, lo más simple es mucho mejor. No es que nunca haya visto casos en los que, para seguir avanzando, terminara haciendo falta un proyecto enorme; pero a veces incluso eso resulta menor que el total de haber seguido cargando con la complejidad hasta ese punto. Depende mucho de qué estés construyendo.

  • Las soluciones que encuentran optimizaciones de rendimiento en un sistema y exprimen el rendimiento restante son realmente divertidas
    Me recuerda al libro de Richard L. Sites, Understanding_Software_Dynamics. Este libro enseña cómo medir y corregir problemas de latencia, y cuánto ahorro puede generar la reducción de la latencia a gran escala
    Medir y razonar sobre estos problemas es difícil, pero las soluciones a menudo son simples. Por ejemplo, en la página 9 dice: “[a] simple change paid for 10 years of my salary.”
    Algún día me gustaría hacer una optimización con ese nivel de impacto

    • En Google hice algo así al menos una vez. Google usa una cantidad enorme de recursos de máquinas y también cuenta con excelentes herramientas de rendimiento, así que es un entorno fértil
      https://research.google/pubs/pub36575/
      Pero también hay mucha gente inteligente alrededor, así que si encuentras una gran oportunidad, normalmente hay una razón por la que nadie más se metió. Puede ser una razón técnica o una razón organizacional
      Como ejemplo de lo segundo, Google normalmente no recompensa mucho este tipo de trabajo salvo cuando hay presión por recursos. Puede que haya recibido unos 100 dólares de bono entre colegas por alguna optimización, pero para nada fue una comisión del 10%, ni un ascenso, ni el derecho a cobrar sueldo durante 10 años sin ir a trabajar. En general, las empresas prefieren que los ingenieros trabajen en crecimiento de ingresos antes que en reducción de costos. Si esa política es correcta o no está por encima de mi nivel salarial
    • Lo que me preocupa de la solución final es que solo optimizó la consulta después de levantar la instancia con la configuración más grande posible
      Parece que, llegado ese punto, ya no podía subir más la configuración con unos cuantos clics en la consola de administración, y por eso tuvo que ponerse a pensar de verdad para resolver el problema de capacidad. Si hubiera optimizado antes esa parte específica del código, quizá nunca habría necesitado una configuración de instancia tan grande