real - Tratando con muchos datos en Firebase para un sistema de recomendación
listas de datos firebase (3)
Aunque indicó que su algoritmo necesita todas las películas y todos los atributos, no significa que los procese a todos a la vez. Cualquier unidad de cálculo tiene sus límites, y dentro de tu algoritmo, probablemente agrupas los datos en partes más pequeñas que tu unidad de cómputo pueda manejar.
Habiendo dicho eso, si desea acelerar las cosas, puede modificar su algoritmo para paralelizar la obtención y el procesamiento de los datos / películas:
| fetch | -> |process | -> | fetch | ...
|chunk(1)| |chunk(1)| |chunk(3)|
(in parallel) | fetch | -> |process | ...
|chunk(2)| |chunk(2)|
Con este enfoque, puede ahorrar casi todo el tiempo de procesamiento (pero el último fragmento) si el procesamiento es realmente más rápido que la obtención (pero no ha dicho qué tan "relativamente rápido" se ejecuta su algoritmo, en comparación con recuperar todas las películas)
Este enfoque de "alto nivel" de su problema es probablemente su mejor oportunidad si la búsqueda de películas es muy lenta, aunque requiere más trabajo que simplemente activar un botón hipotético de "aceleración" de una Biblioteca. Aunque es un enfoque sólido cuando se trata de una gran cantidad de datos.
Estoy construyendo un sistema de recomendación en el que utilizo Firebase para almacenar y recuperar datos sobre películas y preferencias del usuario .
Cada película puede tener varios atributos, y los datos se ven de la siguiente manera:
{
"titanic":
{"1997": 1, "english": 1, "dicaprio": 1, "romance": 1, "drama": 1 },
"inception":
{ "2010": 1, "english": 1, "dicaprio": 1, "adventure": 1, "scifi": 1}
...
}
Para hacer las recomendaciones, mi algoritmo requiere como entrada todos los datos (películas) y se compara con un perfil de usuario.
Sin embargo, en el modo de producción, necesito recuperar más de> 10,000 películas. Si bien el algoritmo puede manejar esto relativamente rápido, lleva mucho tiempo cargar estos datos desde Firebase.
Recupero los datos de la siguiente manera:
firebase.database().ref(moviesRef).on(''value'', function(snapshot) {
// snapshot.val();
}, function(error){
console.log(error)
});
Estoy allí preguntándome si tienes alguna idea sobre cómo acelerar las cosas. ¿Hay algún complemento o técnica conocida para resolver esto?
Soy consciente de que la desnormalización podría ayudar a dividir los datos, pero el problema es que realmente necesito TODAS las películas y TODOS los atributos correspondientes.
Mi sugerencia sería usar Cloud Functions para manejar esto.
Solución 1 (Idealmente)
Si puede calcular sugerencias cada hora / día / semana
Puede usar un Cron de funciones en la nube para arrancar diariamente / semanalmente y calcular recomendaciones por usuarios cada semana / día. De esta forma puedes lograr un resultado más o menos similar al que hace Spotify con sus listas de reproducción / recomendaciones semanales.
La principal ventaja de esto es que sus usuarios no tendrían que esperar que se descargaran las 10,000 películas, ya que esto sucedería en una función de nube, todos los domingos por la noche, compilar una lista de 25 recomendaciones y guardar en el nodo de datos de su usuario , que puedes descargar cuando el usuario accede a su perfil.
Su código de funciones en la nube se vería así:
var movies, allUsers;
exports.weekly_job = functions.pubsub.topic(''weekly-tick'').onPublish((event) => {
getMoviesAndUsers();
});
function getMoviesAndUsers () {
firebase.database().ref(moviesRef).on(''value'', function(snapshot) {
movies = snapshot.val();
firebase.database().ref(allUsersRef).on(''value'', function(snapshot) {
allUsers = snapshot.val();
createRecommendations();
});
});
}
function createRecommendations () {
// do something magical with movies and allUsers here.
// then write the recommendations to each user''s profiles kind of like
userRef.update({"userRecommendations" : {"reco1" : "Her", "reco2", "Black Mirror"}});
// etc.
}
Perdona el pseudo-código. Espero que esto dé una idea sin embargo.
Luego, en su interfaz, debería obtener solo las userRecommendations
de usuario para cada usuario. De esta forma, puede cambiar el ancho de banda y la informática del dispositivo de los usuarios a una función en la nube. Y en términos de eficiencia, sin saber cómo se calculan las recomendaciones, no puedo hacer ninguna sugerencia.
Solución 2
Si no puede calcular sugerencias cada hora / día / semana, y debe hacerlo cada vez que el usuario acceda a su panel de recomendaciones
Luego puede activar una función en la nube cada vez que el usuario visita su página de recomendaciones. Una solución rápida de trucos que uso para esto es escribir un valor en el perfil del usuario como: {getRecommendations:true}
, una vez en la carga de la página, y luego en las funciones de la nube, escuchar los cambios en getRecommendations
. Mientras tengas una estructura como esta:
ID de usuario> getRecommendations: true
Y si tiene las reglas de seguridad adecuadas para que cada usuario solo pueda escribir en su ruta, este método también le proporcionará el ID de usuario correcto que realiza la solicitud. Entonces sabrá para qué usuario se deben hacer recomendaciones. Una función en la nube probablemente podría obtener 10.000 registros más rápido y ahorrar el ancho de banda del usuario, y finalmente escribiría solo las recomendaciones para el perfil del usuario. (similar a la Solución 1 anterior) Su configuración le gustaría esto:
[ Código Frontend ]
//on pageload
userProfileRef.update({"getRecommendations" : true});
userRecommendationsRef.on(''value'', function(snapshot) { gotUserRecos(snapshot.val()); });
[ Funciones de la nube (código de back-end) ]
exports.userRequestedRecommendations = functions.database.ref(''/users/{uid}/getRecommendations'').onWrite(event => {
const uid = event.params.uid;
firebase.database().ref(moviesRef).on(''value'', function(snapshot) {
movies = snapshot.val();
firebase.database().ref(userRefFromUID).on(''value'', function(snapshot) {
usersMovieTasteInformation = snapshot.val();
// do something magical with movies and user''s preferences here.
// then
return userRecommendationsRef.update({"getRecommendations" : {"reco1" : "Her", "reco2", "Black Mirror"}});
});
});
});
Como su interfaz estará escuchando los cambios en userRecommendationsRef
, tan pronto como su función en la nube userRecommendationsRef
, su usuario verá los resultados. Esto puede demorar unos segundos, así que considere usar un indicador de carga.
PD 1: terminé usando más pseudocódigo de lo que pretendía originalmente, y eliminé el manejo de errores, etc. con la esperanza de que, en general, este punto sea claro. Si hay algo poco claro, comente y estaré encantado de aclararlo.
PD 2: estoy usando un flujo muy similar para un mini servicio interno que construí para uno de mis clientes, y ha estado felizmente operativo por más de un mes.
La mejor práctica de la estructura Firebase NoSQL JSON es "Evitar anidar datos", pero usted dijo que no desea cambiar sus datos. Entonces, para su condición, puede tener una llamada REST a cualquier nodo particular (nodo de cada película) de la base de fuego.
Solución 1) Puede crear un número fijo de Threads a través de ThreadPoolExecutors. Desde cada subproceso de trabajo, puede hacer HTTP (solicitud de llamada REST) como se muestra a continuación. Según el rendimiento de su dispositivo y la capacidad de memoria, puede decidir cuántos hilos de trabajo desea manipular a través de ThreadPoolExecutors. Puedes tener un fragmento de código como a continuación:
/* creates threads on demand */
ThreadFactory threadFactory = Executors.defaultThreadFactory();
/* Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available */
ExecutorService threadPoolExecutor = Executors.newFixedThreadPool(10); /* you have 10 different worker threads */
for(int i = 0; i<100; i++) { /* you can load first 100 movies */
/* you can use your 10 different threads to read first 10 movies */
threadPoolExecutor.execute(() -> {
/* OkHttp Reqeust */
/* urlStr can be something like "https://earthquakesenotifications.firebaseio.com/movies?print=pretty" */
Request request = new Request.Builder().url(urlStr+"/i").build();
/* Note: Firebase, by default, store index for every array.
Since you are storing all your movies in movies JSON array,
it would be easier, you read first (0) from the first worker thread,
second (1) from the second worker thread and so on. */
try {
Response response = new OkHttpClient().newCall(request).execute();
/* OkHttpClient is HTTP client to request */
String str = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
return myStr;
});
}
threadPoolExecutor.shutdown();
Solución 2) La Solución 1 no se basa en el patrón Listener-Observer. En realidad, Firebase tiene tecnología PUSH. Significa que cada vez que un nodo particular cambie en Firebase NoSQL JSON, el cliente correspondiente, que tiene el oyente de conexión para un nodo particular de JSON, obtendrá datos nuevos a través de onDataChange(DataSnapshot dataSnapshot) { }
. Para esto, puede crear una matriz de DatabaseReferences como a continuación:
Iterable<DataSnapshot> databaseReferenceList = FirebaseDatabase.getInstance().getReference().getRoot().child("movies").getChildren();
for(DataSnapshot o : databaseReferenceList) {
@Override
public void onDataChange(DataSnapshot o) {
/* show your ith movie in ListView. But even you use RecyclerView, showing each Movie in your RecyclerView''s item is still show. */
/* so you can store movie in Movies ArrayList. When everything completes, then you can update RecyclerView */
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
}