推荐系统中Firebase中大量数据的处理

推荐系统中Firebase中大量数据的处理,firebase,database-design,firebase-realtime-database,recommendation-engine,nosql,Firebase,Database Design,Firebase Realtime Database,Recommendation Engine,Nosql,我正在构建一个推荐系统,使用Firebase存储和检索有关电影和用户偏好的数据 每个电影可以有几个属性,数据如下所示: { "titanic": {"1997": 1, "english": 1, "dicaprio": 1, "romance": 1, "drama": 1 }, "inception": { "2010": 1, "english": 1, "dicaprio": 1, "adventure": 1, "scifi": 1} ..

我正在构建一个推荐系统,使用Firebase存储和检索有关电影和用户偏好的数据

每个电影可以有几个属性,数据如下所示:

{ 
    "titanic": 
    {"1997": 1, "english": 1, "dicaprio": 1,    "romance": 1, "drama": 1 }, 
    "inception": 
    { "2010": 1, "english": 1, "dicaprio": 1, "adventure": 1, "scifi": 1}
...
}
firebase.database().ref(moviesRef).on('value', function(snapshot) {
    // snapshot.val();
}, function(error){
    console.log(error)
});
为了提出建议,我的算法需要输入所有数据(电影),并与用户配置文件匹配

然而,在制作模式下,我需要检索超过10000部电影。虽然算法可以相对较快地处理此问题,但从Firebase加载此数据需要大量时间

我检索数据如下:

{ 
    "titanic": 
    {"1997": 1, "english": 1, "dicaprio": 1,    "romance": 1, "drama": 1 }, 
    "inception": 
    { "2010": 1, "english": 1, "dicaprio": 1, "adventure": 1, "scifi": 1}
...
}
firebase.database().ref(moviesRef).on('value', function(snapshot) {
    // snapshot.val();
}, function(error){
    console.log(error)
});
我想知道你对如何加快速度有什么想法吗?有没有已知的插件或技术可以解决这个问题


我知道非规范化可以帮助分割数据,但问题是我需要所有电影和所有相应的属性。

虽然您声明您的算法需要所有电影和所有属性,但这并不意味着它一次处理所有电影和属性。任何计算单元都有其局限性,在您的算法中,您可能会将数据分为计算单元可以处理的较小部分

话虽如此,如果您想加快速度,可以修改算法以并行化数据/电影的获取和处理:

| fetch  | -> |process | -> | fetch  | ...
|chunk(1)|    |chunk(1)|    |chunk(3)|

(in parallel) | fetch  | -> |process | ...
              |chunk(2)|    |chunk(2)|
使用这种方法,如果处理速度确实比抓取快,那么您可以节省几乎所有的处理时间(但最后一个块)(但是您没有说与抓取所有电影相比,您的算法运行的“相对速度”有多快)


如果获取电影的速度真的很慢,那么这种解决问题的“高级”方法可能是更好的机会,尽管它需要比简单地激活库中假设的“加速”按钮更多的工作。尽管在处理大量数据时,这是一种合理的方法。

我的建议是使用云函数来处理这一问题

解决方案1(理想情况下)

如果您可以每小时/天/周计算建议

您可以使用a每天/每周启动,并每周/每天计算每个用户的建议。通过这种方式,您可以获得与Spotify每周播放列表/推荐内容大致相似的结果

这样做的主要优点是用户不必等待10000部电影全部下载,因为这将发生在云功能中,每个星期天晚上,编译一个包含25条建议的列表,并保存到用户的数据节点中,用户访问其个人资料时,您可以下载该节点

您的云函数代码如下所示:

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. 
}
请原谅伪代码。不过,我希望这能提供一个想法

然后在前端,您只需为每个用户获取
userRecommendations
。通过这种方式,您可以将带宽和计算从用户设备转移到云功能。在效率方面,如果不知道你是如何计算建议的,我不能提出任何建议

解决方案2

如果您不能每小时/天/周计算建议,并且您必须在用户每次访问其建议面板时计算建议

然后,您可以在用户每次访问其推荐页面时触发云功能。我使用的一个快速作弊解决方案是在用户的配置文件中写入一个值,如:
{getRecommendations:true}
,一旦进入pageload,然后在云函数中侦听
getRecommendations
中的更改。只要你有这样的结构:

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. 
}
userID>getRecommensions:true

如果您有适当的安全规则,每个用户只能写入其路径,那么此方法也会为您提供正确的请求用户ID。因此,您将知道要为哪个用户计算建议。一个云功能很可能会更快地提取10000条记录并节省用户带宽,最终只将建议写入用户配置文件。(与上面的解决方案1类似)您的设置如下:

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. 
}
[前端代码]

[云功能(后端代码)]

由于您的前端将在
userRecommendationsRef
上监听更改,因此一旦您的云功能完成,您的用户将看到结果。这可能需要几秒钟,所以考虑使用加载指示符。 p.S.1:我最终使用了比最初预期的更多的伪代码,并删除了错误处理等。希望这能让大家明白这一点。如果有任何不清楚的地方,请发表评论,我很乐意澄清


第二点:我为我的一个客户建立了一个小型内部服务,我正在使用一个非常类似的流程,它已经愉快地运行了一个多月了

Firebase NoSQL JSON结构的最佳实践是“避免嵌套数据”,但您说过,您不想更改数据。因此,对于您的情况,您可以对firebase的任何特定节点(每部电影的节点)进行REST调用

解决方案1)您可以通过ThreadPoolExecutors创建一些固定数量的线程。从每个工作线程中,您可以执行以下HTTP(REST调用请求)。根据您的设备性能和内存容量,您可以决定要通过ThreadPoolExecutors操作多少工作线程。您可以使用如下代码段:

/* 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();
      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) {
            }
}

你能更新这个问题,包括你是如何检索数据的吗?@PatNeedham补充道。基本上我只是从根本上理解它。我不知道该怎么做,因为我需要所有的数据。你为什么需要所有的数据?如果这是一个电影列表,那么它的长度将是数千,并且在UI中向用户显示10000部电影的列表可能不是最佳体验。您需要一次显示所有这些数据吗?