22:11 0 0
Caso de Éxito Mejorando la Interoperabilidad entre JavaScript y CSHARP en Blazor para Optimizar Consultas Masivas

Caso de Éxito Mejorando la Interoperabilidad entre JavaScript y CSHARP en Blazor para Optimizar Consultas Masivas

  DrUalcman |  agosto 162024

La verdad es que tener NuGets en producción es un trabajo bastante más estresante de lo que parecía a simple vista. Cuando otras personas comienzan a utilizarlo, empiezan a reportar problemas o a solicitar nuevas funcionalidades. Además, yo mismo utilizo estos paquetes casi a diario para mis otros proyectos, ya sean personales o de la empresa en la que esté trabajando.

En mi NuGet de manejo de IndexedDB desde Razor, pensado principalmente para ser utilizado en aplicaciones Blazor WebAssembly, me encontré con un desafío en un proyecto personal: una página web para mostrar mi colección de películas https://movies.sergiortizgomez.com/. Quería almacenar en mi base de datos local todas las relaciones entre películas, actores y directores. Esto generó una tabla con más de 27,237 registros solo para la relación entre películas y actores, y otros 489 registros entre películas y directores.

Para la tabla de películas y directores, la recuperación de datos era bastante rápida, tomando poco más de un segundo. Sin embargo, para la tabla de películas y actores, la web se bloqueaba durante 29 segundos antes de que se pudiera interactuar nuevamente. ¡Un tiempo inaceptable!

Buscando el origen del problema

Lo primero que hice fue medir el tiempo que tomaba ejecutar el código usando el objeto Stopwatch. Así, descubrí que el problema estaba en la recuperación de datos con mi NuGet DrUalcman-BlazorIndexedDb, que tardaba algo más de 26 segundos en devolverme la lista con los datos.

Definiendo el posible origen del error

Mi primera sospecha fue que el problema podría estar en el lado de JavaScript, un lenguaje que me encanta (con cierto sarcasmo). Así que intenté optimizar el código JavaScript, mientras seguía midiendo los resultados desde C#. Aunque logré reducir un poco los tiempos, seguía siendo demasiado lento.

Entonces, decidí medir directamente el tiempo que JavaScript tardaba en leer los datos desde IndexedDB. Para mi sorpresa, descubrí que la lectura de los 27,237 registros tomaba menos de un segundo, en algunos casos ni siquiera llegaba a medio segundo. Esto descartó a JavaScript como la causa del problema.

Comenzando a buscar soluciones

Ya sabiendo que JavaScript no era el culpable, me enfoqué en cómo se enviaban los datos a C#.

Primero, cambié mi enfoque en C#. En lugar de intentar recibir directamente el objeto:

List<TModel> data = awaitjsRuntime.GetJsonResultTModel>>("MyDb.Select", Setup.Tables.GetTable<TModel>(), Setup.DBName, Setup.Version, Setup.ModelsAsJson);

Comencé a solicitar los datos en formato JsonElement. Esto redujo el tiempo de 26 a 14 segundos, lo que indicaba que el problema estaba más en el manejo de datos en C# que en JavaScript. Sin embargo, convertir de JsonElement al objeto deseado volvía a incrementar el tiempo a 25 segundos, lo cual seguía siendo inaceptable.

Entonces pensé: en lugar de enviar un objeto JSON desde JavaScript, ¿por qué no enviar solo texto y luego deserializarlo desde texto en C#? Esta modificación redujo el tiempo a 20 segundos, pero todavía no era suficiente.

Se me ocurrió comprimir los datos en JavaScript para reducir el volumen de información, pero esta solución requería implementar mucho código adicional en JavaScript y C#, algo que no era ideal.

La solución al problema

Finalmente, se me ocurrió una idea: ¿por qué no enviar bytes en lugar de texto desde JavaScript?

async function JsonToBytes(data) {
const jsonString = JSON.stringify(data);
const encoder = new TextEncoder();
return encoder.encode(jsonString);
}

Y al leer los datos desde IndexedDB:

request.onsuccess = async () => {
db.close();
const data = await JsonToBytes(request.result);
resolve(data);
}

Esto me permitió hacer lo siguiente en C#:

byte[] dataBytes = await jsRuntime.InvokeAsync<byte[]>(
"MyDb.Select",
Setup.Tables.GetTable(),
Setup.DBName,
Setup.Version,
Setup.ModelsAsJson);

List data = JsonSerializer.Deserialize>(
dataBytes,
new JsonSerializerOptions {
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,
ReadCommentHandling = JsonCommentHandling.Skip
});

Las opciones adicionales de JsonSerializerOptions también ayudaron a mejorar el rendimiento de la deserialización. Como resultado, los tiempos mejoraron notablemente:

  • La lectura de datos desde JavaScript no excedía de 1 segundo, incluso para los 27,237 registros.
  • Convertir los datos a la lista deseada tomó menos de 9 segundos. Aunque este tiempo podría mejorarse por parte de Microsoft, estoy satisfecho con el resultado.

Las otras consultas, que antes tardaban un segundo o más, ahora se ejecutan en menos de medio segundo. ¡Realmente fue una gran mejora!

Conclusiones

Así fue como realicé mi investigación y optimización del código, que ya está disponible en la nueva versión de mi NuGet.

Las lecciones aprendidas son:

  • Es mejor enviar bytes desde JavaScript a C# en lugar de un objeto JSON.
  • Deserializar desde byte[] en C# es mucho más rápido que desde una cadena de texto (string).

Estos puntos son especialmente relevantes si necesitas mejorar la velocidad de respuesta o minimizar el bloqueo de la interfaz de usuario mientras trabajas entre JavaScript y C#. No he mostrado mucho código aquí porque esto es solo una experiencia personal. Si deseas ver las mejoras en detalle, puedes revisar el código antes y después en este commit del repositorio.

0 Comentarios

 
 
 

Archivo