Escalando Immich: Cómo rediseñamos la geocodificación inversa para bibliotecas de fotos masivas

PixelUnion Team
7 min read
Escalando Immich: Cómo rediseñamos la geocodificación inversa para bibliotecas de fotos masivas

Introducción: La magia de saber “Dónde”

Para los entusiastas del home lab y los defensores de la privacidad de datos, Immich se ha convertido en una solución autoalojada de primer nivel para la gestión de fotos y videos. Ofrece una alternativa poderosa y privada a los servicios basados en la nube. Una de sus características más convincentes es un poco de magia en segundo plano: la geocodificación inversa. Este proceso analiza automáticamente las coordenadas GPS incrustadas en los datos EXIF de una foto y la enriquece con contexto de ubicación legible por humanos—la ciudad, el estado y el país donde se capturó la imagen.

Como se señala en la documentación oficial de Immich, esta capacidad poderosa está impulsada por la base de datos geográfica integral GeoNames. Esto permite a Immich transformar un simple conjunto de coordenadas en información significativa que puede usar para buscar y organizar sus recuerdos.

Pero ¿qué sucede cuando esta característica elegante y autónoma necesita funcionar no solo para un solo usuario, sino a través de una plataforma multi-tenant masiva? Este fue el desafío de escalabilidad que enfrentamos en PixelUnion, y nos impulsó a reimaginar cómo se entrega esta magia.

El desafío: La geocodificación predeterminada de Immich a escala

Para diseñar una mejor solución, primero tuvimos que apreciar el diseño estratégico de la arquitectura predeterminada de Immich. El enfoque estándar es brillante para su audiencia objetivo de usuarios individuales, pero sus decisiones de diseño centrales crean cuellos de botella predecibles cuando se despliega para una gran base de usuarios con miles de bibliotecas de fotos.

Cómo funciona la geocodificación estándar de Immich

En una configuración estándar, Immich descarga el conjunto de datos GeoNames y lo carga directamente en una tabla de base de datos PostgreSQL local. Según la documentación, este proceso de importación se activa durante cada actualización de versión menor, asegurando que los datos de ubicación permanezcan actualizados.

El beneficio principal de esta arquitectura es claro: mantiene todo el procesamiento de datos completamente autónomo en el servidor del usuario. Esto mejora la privacidad y la simplicidad operativa, ya que no hay dependencias externas para esta característica central. Para un autoalojador típico que gestiona su biblioteca personal, una tabla de base de datos de alrededor de 100 MB es una solución perfectamente razonable y eficiente para las búsquedas locales rápidas que impulsan tanto la visualización de ubicación como la funcionalidad “Smart Search” de Immich.

El cuello de botella en despliegues grandes

Este modelo autónomo comienza a desmoronarse en entornos multi-usuario a gran escala como el nuestro en PixelUnion. El problema central es la hinchazón de la base de datos. Cuando esa única tabla de geodatos de ~100 MB se replica en cientos o miles de instancias de base de datos individuales, la sobrecarga de almacenamiento acumulativa se vuelve inmensa. Esto presentó desafíos operativos significativos, aumentando dramáticamente la dificultad y el costo asociados con escalar nuestra infraestructura Postgres muy grande.

Un problema secundario era la sobrecarga repetitiva del trabajo de importación en sí. El proceso de descargar y cargar el conjunto de datos, que se ejecuta después de cada actualización de Immich, se convierte en una tarea altamente ineficiente e intensiva en recursos cuando se multiplica en todo nuestro despliegue. Necesitábamos una forma de centralizar esta función sin comprometer su rendimiento.

Nuestra solución: Desacoplar la geocodificación con un microservicio

Nuestro objetivo estratégico era desacoplar la función de geocodificación de la base de datos principal de Immich. Este cambio arquitectónico fue diseñado para ofrecer mayor flexibilidad, eficiencia y escalabilidad sin alterar las características centrales orientadas al usuario de Immich. Nuestro objetivo era resolver el problema de escalabilidad a nivel de infraestructura mientras mantenemos el comportamiento de la aplicación consistente.

El cambio arquitectónico de tabla local a API central

Nuestra solución fue reemplazar la consulta directa de base de datos con una simple llamada API a un microservicio dedicado. En lugar de que cada instancia de Immich mantenga su propia copia de los datos GeoNames, ahora ejecutamos un único microservicio de geocodificación centralizado dentro de nuestro clúster. Este servicio contiene los datos GeoNames y expone endpoints API para manejar tanto solicitudes de geocodificación inversa como búsquedas de nombres de lugares para todas las instancias de Immich.

Crucialmente, el microservicio está diseñado para devolver datos en exactamente el mismo formato que Immich espera de su consulta de base de datos interna. Esto hace que el cambio arquitectónico sea completamente transparente para la lógica de la aplicación, requiriendo solo una modificación menor para dirigir las solicitudes a la nueva API en lugar de la tabla local.

Una mirada técnica al código

Para implementar esto, introdujimos una nueva variable de entorno, GEODATA_API_URL, como se ve en nuestro commit a la base de código de Immich. Esta variable actúa como una bandera de característica que le dice a una instancia de Immich si usar el método de base de datos tradicional o nuestra nueva API externa para las funciones de geocodificación y búsqueda.

La lógica central, visible en los cambios a map.repository.ts y search.repository.ts, sigue una verificación condicional simple para cada función:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// In map.repository.ts
IF GEODATA_API_URL is configured:
   call reverseGeocodeViaApi(point, GEODATA_API_URL)
ELSE:
   call reverseGeocodeViaDatabase(point)

// In search.repository.ts
IF GEODATA_API_URL is configured:
   call searchPlacesViaApi(placeName, GEODATA_API_URL)
ELSE:
   call searchPlacesViaDatabase(placeName)

Este cambio elegante es poderoso por dos razones. Primero, hace que la nueva característica sea una mejora opt-in, preservando la compatibilidad hacia atrás perfecta para todos los usuarios existentes de Immich. Segundo, proporciona un camino limpio y configurable para despliegues escalados como el nuestro para descargar toda la carga de trabajo de geodatos sin requerir un fork complejo o invasivo de la aplicación principal.

El impacto: Ganancias medibles y un Immich más escalable

Estos cambios arquitectónicos no fueron solo mejoras teóricas; resultaron en mejoras significativas y medibles en la eficiencia, rentabilidad y mantenibilidad de nuestra plataforma.

Beneficios clave realizados

  • Dieta drástica de la base de datos: La victoria más inmediata fue una reducción dramática en el tamaño de cada instancia de base de datos de Immich. Al eliminar la tabla de geodatos de ~100 MB de la base de datos de cada usuario, recuperamos una cantidad masiva de almacenamiento en todo nuestro clúster.
  • Actualizaciones simplificadas: Nuestro cambio elimina completamente el trabajo de importación de geodatos que Immich ejecutaba anteriormente después de cada actualización. Esto ahorra considerable tiempo y recursos de computación durante nuestros ciclos de actualización en toda la plataforma. Esto se confirma por la nueva lógica en el código, que explícitamente registra que está “omitiendo la importación local de geodatos” cuando nuestra API externa está configurada.
  • Escalabilidad mejorada: Con una huella de base de datos más pequeña, todo el despliegue de Immich es más fácil de gestionar, respaldar y escalar. Además, centralizar la lógica mejora el rendimiento y la consistencia tanto del enriquecimiento de datos de ubicación como de las consultas de búsqueda en toda la plataforma, permitiéndonos soportar más usuarios de manera más eficiente y rentable.

Abierto para todos

Creemos en el poder del código abierto y en devolver a las comunidades que construyen herramientas increíbles como Immich. Esta solución ha sido contribuida a través de nuestro fork de código abierto público. Puede inspeccionar la implementación exacta, desde la nueva variable de entorno hasta las llamadas API condicionales, en el commit enlazado a continuación.

Ver el commit completo en GitHub

Reflexiones finales

Nuestro viaje desde identificar un cuello de botella de escalabilidad crítico hasta implementar una solución de microservicio robusta y desacoplada destaca un desafío común en la ingeniería de software: una característica que es perfecta para una escala puede convertirse en un obstáculo en otra. Al analizar cuidadosamente el problema e implementar una solución flexible y compatible hacia atrás, pudimos mejorar la arquitectura de Immich para nuestras necesidades mientras contribuimos una opción valiosa a la comunidad en general. En PixelUnion, seguimos comprometidos con el código abierto y alentamos a todos en las comunidades de home lab y autoalojamiento a explorar, adaptar y construir sobre este tipo de soluciones.