Control activity feeds on facebook

Controlling activity feeds on facebook

Facebook has a setting which lets you chose amongst your friends and pages, whose updates you want to see and whose you want to skip. Here it is how

Next to Most Recent, there is a drop down button. Click on that to gain access to control options. The drop down menu comes only when you are viewing the Most Recent news feeds only. So if you don’t see an option, just click on Most Recent. The page will refresh itself and the menu options will come.

image

Select Edit Options in the drop down menu

image

Now conveniently you can choose to show news from

  1. Those friends and pages you interact with most or
  2. All friends and pages.

The default setting is “friends and pages” you interact with most. You can also edit friends whose feeds have been blocked in the 

Recursively traversing Python dictionary and removing keys

Code Snippet to recursively traverse a dictionary and remove certain key/value pair

In case of complex dictionaries like

my_blog = { '_created' : datetime.datetime (2007,01,03),
'_updated' : datetime.datetime (2011,06,11),
'name' : 'MyBLive',
'latest_post' : { '_created' : datetime.datetime (2007,06,9),
'_updated' : datetime.datetime (2007,069),
'name' : 'Hello World !' }

The problem with this is that datetime.datetime entities are not JSON Serializable. One approach could be to provide a serializer for datetime.datetime entities as suggested by verte over IRC. If you want to have a datetime.datetime aware JSONSerializer, you should have a look at the django.core.serializers.json module

In my case, we were using a Google App Engine application and the JSON response need not contain these key/value pairs so it makes more sense if these are removed from the dictionary.

def rm (d, l):
"""
Removed from dictionary "d" all those key-value pairs the keys of which
are defined as a list in "l"
"""
if not l: return d
if reduce ( lambda x,y: x or y, [x in d.keys () for x in l]):
[d.pop (x, None) for x in l]
[rm (x, l) for x in d.values () if isinstance (x, dict)]
return d

Versioned Entity Caching for high read/write on Google App Engine Python

The usage of memcache for lowering the load on App Engine Datastore is well known. Here is an approach to versioned caching of datastore entities in App Engine. The Model is deigned primarily for purposes where entity is updated very frequently. The idea for these kind of entity generated while working on the Multiuser Chat room for App Engine using Channel APIs.

Expectations

The Model is designed with the following factors in mind

  • Very frequent read/write access to entity
  • Effective usage of memcache to reduce load on datastore operations
  • Strong consistency between datastore and memcache

Design Plan

The Entities have an internal version number. This version number is increased everytime there is an update in the entity.

from google.appengine.ext import db


# The maximum difference in revisions acceptable at any instant
# between memcached values and the datastore values. The higher it
# is, the greater catastrophe when memcache goes down, but lesser
# datastore usage. The lesser it is, the more consistent your datastore
# and memcache are, and higher datastore operations. 4~6
FAULT_TOLERANCE = 4

class GlobalVersionedCachingModel(db.Model):
"""
The Model uses internal versioning of information with prime focus on very
high read/writes and consistency.
Every entity has a datastore's version number information and the version
number from memcache. When the entity is updated, it happens in memcache
only and the memcached version number increases. If this number is greater
than the datastore version number by a certain amount called
"fault tolerance", then the datastor entity is sync'd with the memcache
entity.
"""

_db_version = db.IntegerProperty (default=0, required=True)
_cache_version = db.IntegerProperty (default=0, required=True)
_fault_tolerance = db.IntegerProperty(default = FAULT_TOLERANCE)

Approach #1

Initially, both the versions are set to 0 when the entity is created. _fault_tolerance is the maximum allowed difference between the cache version and the datastore version. Since the entity is primarily read from and written to memcache, and later updated to datastore, the datastore version number can be behind the memcache version number. When the difference between the two exceeds fault tolerance, then the datastore is updated with the memcache details.

The downside of this approach is that when the memcache goes out, then the datastore entity is fetched. This entity, in worst case scenario, could be lagging from the actual entity by a maximum of _fault_tolerance factor. Fault Tolerance can be decreased to improve the consistency between memcached entity and the datastore entity but that will result in higher datastore read/write operations.

Approach #2

In this approach, instead of directly writing into the datastore, we initiate a task queue and that gets the task done for us. The good part of this approach is that we can keep smaller values of _fault_tolerance and still expect faster processing. This approach is ideal where strong consistency is required between the entities and latency shall also be minimal

Methods

def get2 (keys, **kwargs):
keys, multiple = datastore.NormalizeAndTypeCheckKeys (keys)
getted_cache = memcache.get_multi (map (str, keys))
ret = map (deserialize_entities, getted_cache.values ())
keys_to_fetch = [key for key in keys if getted_cache.get(key, None) is not None]
getted_db = db.get(keys_to_fetch)
memcache_to_set = dict ((k,v) for k,v in zip (map (str,keys_to_fetch),
map (serialize_entities, getted_db)))
ret.extend(getted_db)
memcache.set_multi (memcache_to_set)
if multiple:
return ret
if len (ret) > 0:
return ret[0]

class GlobalVersionedCachingModel(db.Model):
"""
The Model uses internal versioning of information with prime focus on very
high read/writes and consistency.
Every entity has a datastore's version number information and the version
number from memcache. When the entity is updated, it happens in memcache
only and the memcached version number increases. If this number is greater
than the datastore version number by a certain amount called
"fault tolerance", then the datastor entity is sync'd with the memcache
entity.
"""

_db_version = db.IntegerProperty (default=0, required=True)
_cache_version = db.IntegerProperty (default=0, required=True)
_fault_tolerance = db.IntegerProperty(default = FAULT_TOLERANCE)
created = db.DateTimeProperty (auto_now_add=True)
updated = db.DateTimeProperty (auto_now=True)

@property
def keyname (self):
return str (self.key ())

def remove_from_cache (self, update_db=False):
"""
Removes the cached instance of the entity. If update_db is True,
then updates the datastore before removing from cache so that no data
is lost.
"""
if update_db:
self.update_to_db()
memcache.delete(self.keyname)

def update_to_db (self):
"""
Updates the current state of the entity from memcache to the datastore
"""
self._db_version = self._cache_version
logging.info('About to write into db. Key: %s' %self.keyname)
self.update_cache ()
return super (GlobalVersionedCachingModel, self).put ()

def update_cache (self):
"""
Updates the memacahe for this entity
"""
memcache.set (self.keyname, serialize_entities (self))

def put (self):
self._cache_version += 1
memcache.set (self.keyname, serialize_entities (self))
if self._cache_version - self._db_version >= self._fault_tolerance or
self._cache_version == 1:
self.update_to_db ()

def delete (self):
self.remove_from_cache()
return super (GlobalVersionedCachingModel, self).delete ()

@classmethod
def get_by_key_name (cls, key_names, parent=None, **kwargs):
try:
parent = db._coerce_to_key (parent)
except db.BadKeyError, e:
raise db.BadArgumentError (str (e))
rpc = datastore.GetRpcFromKwargs (kwargs)
key_names, multiple = datastore.NormalizeAndTypeCheck (key_names, basestring)
logging.info(key_names)
keys = [datastore.Key.from_path (cls.kind (), name, parent=parent) for name in key_names]
if multiple:
return get2 (keys)
else:
return get2 (keys[0], rpc=rpc)




Similar Readings

Dear Google, Take from Facebook what you have build over the years – the web

Me, Myself and My Google

I remember the first time I was plugged to internet back in 1997. I created my email id. It was on rediff.com. Then I surfed some porn sites. Then I saw yahoo.com. Almost everyone I knew was on rediff. Very soon, almost everyone was on Yahoo. And the most exciting thing to do was to do private and public chats on Yahoo Messenger. Then came the days when Yahoo chatrooms started to flood with nonsense messages, porn websites became popup machines and soon the internet became a wide wide shit. Those were the days when suddenly everything (I used) across the internet became too obstructive, complex and confusing.

image

In the midst of all those came google.com. A neat and clean interface for searching the web, which was perhaps the biggest problem at that time. Google took the challenge to organize the world’s information and make it universally accessible and useful. And they did a great job with their search engine. People started liking google and used Yahoo, Rediff and MSN for their emails. Then came gmail.com and suddenly out of nowhere everyone had a gmail.com account. It was again neat and clean. And then again, all of a sudden, the voice chats, buzzes, beautiful big smileys, public chatrooms, multiple screen names, erotic talks on Yahoo chat came to and end simple non rich text formatted, non fancy UIed chat windows. Somehow people liked it. Probably they were bored of all the glittering stuff, or maybe some principle of UX design came into play. Ever since, Google became a part of everyone’s life with products like Docs, PicasaWeb, Blogger, News, Scholar, Books, Knol and many more. People had faith in Google, their ideas and their values. Off lately there are various slanders on Google about the way they use data to create relevant text ads, but despite all these, a vast majority of people still love Google.

What The Facebook?

Social Networking was something that was not so popular in India. Not in 2004, the year I went for my Bachelors in Engineering, the year Facebook was founded and the year Orkut was founded. Sooner, everyone I knew was on Orkut and it was kinda fun. We used to meet up, post scraps to each others, and had communities where we discussed the C++ puzzles. Life was easy and fun. This probably continued till early 2008s (in India) when people were not accepting facebook because they found it too complex to use. Probably people just could not visualize what is “writing on wall”. And then facebook kept evolving, changing its UX to make it more resemble the real world interactions. That is where Orkut was left behind. Orkut was good for novice users who expected barely scraps and messages and communities for interactions. But as it goes in the real world, you appreciate, like, comment on your friends’ activities. All of a sudden, all this was possible on Facebook. One other problem with Orkut was of privacy. I remember there were communities where pictures of real girls from their profile was hotlinked and posted. This obviously irritated a lot of people and they started to remove their content from Orkut. Almost at the same time, as I precisely remember, the Indian community was starting to look towards facebook because it was social, it was fun and it was secure. Orkut was still being used by those who were new to social networking and then they gradually graduated to use facebook.

image

Soon, everyone I knew was on facebook, even my mom, my dad and my 8 year old cousin. Life became easy. Google released OpenSocial in late 2007 and people started to develop games and apps for Orkut. It could have been a big hit if people had been using Orkut or they had interest. Orkut failed to attract new users and even continue old users. Orkut started to die, and pictures of Orkut – R.I.P became popular on the internet.

You like it

Sure you will ask me, what’s the big deal with that. Google is in search and ads, and facebook is in social networking. Facebook already beat Google’s Orkut, so do they still compete? The answer is – more now than ever.

Sometimes in last year or maybe last to last year “Like Button” was introduced, and very soon every webpage on the planet started to have a “Like This” button. (Even this page has one, please like/recommend it. Thank you 🙂 The concept is pretty neat. Your friends see that you like/recommend Bellicose beliefs, and they will probably come and read what is it. If they “like” it, it goes on virally. Alternatively, if you come to Bellicose Beliefs two years later and you see that one of your friends like it, you will be more interested in reading the content. This is a win win situation for the web admins/ bloggers/ any other website and probably that is the reason for it becoming so popular, so soon. Now looking at the other aspect, facebook has a record of all the pages you like and your friends like and their friends like. So, they are more close to figuring out your taste, your work, your life and provide you better ads/recommendations. Everything tailored, just for you.

Now let’s see how google tracks internet usage – “Ads by Google”. Most of the ads you see over the internet are served by Google’s Ad server. Every ad stores a cookie onto your PC and this helps track the click, the content. All this is done on a very broad scale and the results are generated so that they are fit for a majority of people who fall under the same category. This is where difference sneaks in. Google does not track individuals, it just track the pattern formed by many users and uses that pattern to server ads, which might not be suited for you, but are suited for a majority of population. It works.

What’s the need for a search engine?

Probably you’ve started to notice it. Every single link you share, every single web content you “like/recommend” comes in your facebook search box. So, if you are looking for something that you probably have liked in past, just go to search box and start typing it’s name. You will see it coming there as in the diagram below.

image

For more details, you can go to “Show All results” and alongwith the things that are related to facebook, you see results from Bing (which as per Google, copies their search result). If this continues to grow, fewer and fewer people will go to Google for searching something, and that will be done only when facebook fails to deliver. So the search for which Google is known, shall be taken by facebook and Google will get only facebook’s leftovers.

Does facebook pose a serious threat to Google’s main source of revenue?

In order to understand this, we first need to figure out what is Google’s main source of revenue. Google’s main source of revenue is Adwords which accounted for USD 23b in 2009 (http://investor.google.com/fin_data.html).

Facebook’s revenues for 2009 were as

  1. $125 million from brand ads
  2. $150 million from Facebook’s ad deal with Microsoft
  3. $75 million from virtual goods
  4. $200 million from self-service ads. (http://www.businessinsider.com/breaking-down-facebooks-revenues-2009-7)

This total amounts to USD 550m or USD 0.55b. Facebook’s total revenue in 2009 is 46 times smaller than the revenue of Google Adwords.’Looking at this figure it is clear that Facebook is not posing a serious threat to Google’s revenue.
Now here is the present scenario. Facebook Like Button on various sites, Facebook places, Facebook questions – Facebook knows which content you like/read, where you visit and what are your doubts. Using this data, Facebook can possibly come up with an Ad Sever that has if not more, equal powers as the Google Ad Servers. If they come up with this, and you start seeing “Ads by Facebook” instead of “Ads by Google”, then the war will get more interesting.

The social web – by Google

In this particular text, I will try to figure out, how the web could be more social with the help of Google. Google already has, and in much better form what facebook is offering – social interaction, videos, emails, news, web search but in separated forms. Things are are at different URLs and one must remember/click/bookmark each location. Let’s say Google comes up with a social web – a portal that is sufficient for all your needs and is social, I would call it GoSocial. It would be a mashup of Google Search, Gmail, Buzz, Picasaweb, Docs, Music Search, YouTube, Blogger, Knol, Groups, Latitude and Maps. So you can upload pictures which will be stored in your picasaweb albums. You can blog and your blogsppot.com address shall be linked to your GoSocial account. You can upload videos on GoSocial and they will be stored and served by YouTube. You can create documents and share them with Google Docs running in the backend. You can interact with groups and can create questions and answers with the Google Groups engine in background. You can create community pages with the help of Knol. You can use Latitude to implement a feature similar to facebook places, even better in some aspects. Maybe, I need to think more so that I can come up with a possible layout of this social web. But this is my general idea. I am a die hard Google fan. Everytime I visit their numerous services, I can picture different modules of a system which is highly social, real time and most important of all is not evil, lying around. In my personal opinion it’s time to come up with a version of web, which is more social and Google, you can do it.

Over the decade, Google has helped build the web, make it safe, clean and present the data in an organized manner to all the inhabitants on the planet. Why let someone else harness it when it essentially belongs to its creator.

There are many more things in my mind to write about, so keep watching for a second part of this post

Multiuser Chatroom with App Engine Channel API – Part 2


The Part-1 of designing multiuser chat room with app engine deals with providing a basic information of how to create a simple chatroom/gameroom with App Engine Channel API. That article was more based on the tic-tac-toe example provided by the Google App Engine team.

In this post, I will talk about the ways in which this can be optimized using memcache.

But before that, let’s look at the complications with the previous one

  1. The Channel ID is a function of only userid. This means that one user can not login from multiple clients and can expect consistency. There will always be an inconsistency and improper outgoing messages on the channels.
  2. Too much datastore operations. For every action, there is too much datastore operations going on. As the number of players keep on increasing, this problem becomes more intense. A developer reported that he had to wait several seconds for things to happen while doing it with ~30 players. This is an optimized version and will have much better serving time
  3. Coding style. Well frankly, I was not satisfied with the coding style in prev version, so in the hope of creating better and beautiful code, I decided to rewrite it and I came up with this new file tournament.py, which is pretty neat.

First of all, in order to get an insight of effective memcaching in datastore entities, go through this post on Nick’s blog. This has been completely used.

EfficientModel

In order to do good optimizations, I have a base class EfficientModel from which my datastore classes will be derived. Entities of kind EfficientModel are designed to be in memcache most of the time they are required. All the operations on these entities take place in memcache only. After the memcached entity has been updated “certain” number of times, the change is replicated in the datastore. The EfficientModel has an attribute mc_version, which stores the version number of this entity in memcache. The concept is that every entity has a revision number associated. Whenever there is any change in one of the attributes/property of the entity it’s version number increases by one. mc_version stored the version number of entity in memcache. db_version stores the version number of entity in the datastore. The difference between memcache version and datastore version is called as FAULT NUMBER. When fault_number goes beyond a certain number known as fault_tolerance, then the memcache entity and the datastore entity are sync’d.

class EfficientModel(db.Model):
mc_version = db.IntegerProperty(default = 0)
db_version = db.IntegerProperty(default = 0)
created = db.DateTimeProperty(auto_now_add = True)
updated = db.DateTimeProperty(auto_now = True)

@property
def keyname(self):
return self.key().name()

@property
def memcache_key(self):
raise NotImplementedError

@classmethod
def from_id(cls, id):
existing_entity_mc = deserialize_entities(memcache.get(id))
if existing_entity_mc is None:
existing_entity_db = cls.get_by_key_name(id, parent = cls.find_parent(id))
if existing_entity_db is None:
return cls(key_name = unicode(id))
return existing_entity_db
return existing_entity_mc

@classmethod
def fetch_from_id(cls, id):
return cls(key_name = unicode(id))._get()

def _from_memcache(self):
return deserialize_entities(memcache.get(self.memcache_key))

def _get(self):
memcached_entity = self._from_memcache()
if memcached_entity is not None:
return memcached_entity
return self.get_or_insert(key_name = self.keyname)

def _store(self, force = False):
self.mc_version += 1
memcache.set(self.keyname, serialize_entities(self))
if self.mc_version – self.db_version >= FAULT_TOLERANCE or force:
logging.info(‘sync_to_db started for %s. mc_version: %d, db_version: %d’%(
self.keyname, self.mc_version, self.db_version))
self.sync_to_db()
elif self.mc_version < self.db_version:
logging.info('sync_from_db started')
self.sync_from_db()

def sync_to_db(self):
self.db_version = self.mc_version
db.put(self)
self._store()

def sync_from_db(self):
from_ds = db.get(self.keyname)
memcache.set(self.keyname, serialize_entities(from_ds))
self._store()

@classmethod
def find_parent(cls, id):
raise NotImplementedError

Properties, Classmethods and Functions

  • keyname, property. : Returns the key name of the entity. Equivalent to
    .key().name()
    
  • memcache_key, property : The derived class is expected to define this property. This is supposed to return the key which will be used in memcache while storing and retrieving this entity. It is assumed at some places the memcache_key shall be same as keyname.
  • from_id(id), classmethod : This classmethod returns the entity based on the id passed. It first tries to fetch the entity from memcache. If not found, it attempts to fetch the entity from datastore. If that is not found, then it creates an entity with the key_name as the id passed and returns that.
  • _from_memcache, private function : Returns the memcached snapshot of the entity
  • _get, private function : Attempts to get self from memcache. If not found, goes through a get_or_insert call.
  • fetch_from_id(id), classmethod : The difference between fetch_from_id and from_is is that, fetch_from_id always creates an entity in the datastore if it does not already exists. While in case of from_id a dummy object (one which is not in the datastore, yet) is returned.
  • sync_from_db, function : Sync the entities from datastore to memcache. Essentially, the entity is copied to memcache from datastore and the versions are updated
  • sync_to_db, function : Sync the entities from memcache to data store. Essentially, the entity is copied from memcahe to datastore and the versions are updated
  • _store, private function : Stores the entity in memcache and updates the mc_version. If the fault becomes more than fault tolerance, it syncs the entity across datastore and memcache.
  • find_parent(id), classmethod : This is not implemented and the derived class is expected to work on this. This is supposed to return the key name of the parent entity of this entity.

Channels

The channel ids created are a function of userid as well as the time.time(). Whenever a channel id is created, it is stored as a property in the Player model as well as the Game model. Duplicacy of data, helps do better reads.

def gen_channel(userid):
seed = userid + str(int(time.time()))
return md5.md5(seed).hexdigest()

Player Class

The Player Class has more or less the same functions, but their writing style became little different due to change in coding style.

class Player(EfficientModel):
“””
Stores information about which player is added into which game
room. This of course assumes that a player can be in only one
game room at a time.
“””
assets = db.TextProperty(default = ”)
name = db.StringProperty()
channels = db.StringListProperty()

@property
def memcache_key(self):
return self.key().name()

@property
def gameroom(self):
if self.parent():
return self.parent().key().name()

def get_channels(self):
return self._get().channels

def create_channel(self):
new_channel = gen_channel(self.keyname)
self.channels.append(new_channel)
self._store()
return new_channel

def die(self):
raise NotImplementedError

def do_action(self, action, **kwargs):
raise NotImplementedError

@classmethod
def find_parent(cls, id):
return Game.all(keys_only = True).filter(‘players = ‘, id).get()

def chat(self, message):
if self.gameroom is not None:
self._store()
Game.from_id(self.gameroom).update_chat(self, message)

def leave_tournament(self):
if self.gameroom is not None:
Game.from_id(self.gameroom).expel(self)

Game Class

The important parts of the class are shown here. For a more detailed version, have a look at the source code.

class Game(EfficientModel):
“””
The Model to store the details of a particular
game room.
“””
players = db.StringListProperty()
chat = db.TextProperty()
active = db.BooleanProperty(default = True)
channels = db.StringListProperty()

deltas = {}

@property
def memcache_key(self):
return ‘tournament_’ + self.key().name()

@classmethod
def join_latest_or_new(cls, player):
latest_roomkey = memcache.get(LATEST_GAMEROOM)
if latest_roomkey:
cls.from_id(latest_roomkey).add_player(player)
return latest_roomkey
return cls().new(player)

def can_add_player(self):
if MAX_PARTICIPANTS == -1: return True
return len(self.players) < MAX_PARTICIPANTS

def add_player(self, player):
if self.can_add_player():
logging.info('Adding to existing game')
if player.keyname not in self.players:
self.players.append(player.keyname)
self._store()
p = Player.get_or_insert(key_name = player.keyname,
parent = self)
memcache.set(player.keyname, serialize_entities(player))
self.deltas.update({'new_player' : 1,
'name' : player.keyname,})
self.update_channels(player)
return self.send_updates()
else:
return self.new(player)

@classmethod
def new(cls, player):
if not player: return
logging.info('Creating new gameroom…')
new_room = cls(key_name = str(time.time()),
players = [player.keyname])
sim_player = Player(key_name = player.keyname,
parent = new_room,
channels = player.channels)
db.put([new_room, sim_player])
memcache.set_multi({LATEST_GAMEROOM : new_room.key().name(),
new_room.memcache_key: serialize_entities(new_room),
sim_player.keyname : serialize_entities(sim_player),
})
new_room.update_channels(sim_player)
return new_room

@classmethod
def continue_tournament(cls, player):
room = cls.from_id(player.gameroom)
room.update_channels(player)

def update_channels(self, player):
new_channels = player.get_channels()
if isinstance(new_channels, list):
self.channels.extend(new_channels)
if isinstance(new_channels, str):
self.channels.append(new_channels)
self.channels = list(set(self.channels))
self._store(True)

def send_updates(self):
message = simplejson.dumps(self.deltas)
for channel_id in self.channels:
channel.send_message(channel_id, message)

@classmethod
def find_parent(cls, id):
return None

def update_chat(self, player, message):
self.deltas['chat'] = message
if self.chat:
self.chat += '
' + self.deltas.get('chat')
else:
self.chat = self.deltas.get('chat')
self._store()
self.send_updates()

def end(self):
"End the game"
player_keys = [db.Key.from_path('Game', self.keyname, 'Player', x) for x in self.players]
db_keys = [db.key()]
db_keys.extend(player_keys)
memcache_keys = self.players
memcache_keys.append(self.memcache_key)
memcache.delete_multi(memcache_keys)
db.delete(db_keys)

def expel(self, player):
"Expel player from the game"
memcache.delete(player.memcache_key)

def txn(game_obj, player_key, player_id):
db.delete(player_key)
game_obj.players.pop(player_id)
game_obj.put()

db.run_in_transaction(txn, self, db.Key(player.keyname), player.keyname)
self.deltas.update({'expel': 1,
'name' : player.keyname})
self.send_updates()

This is a better optimized version. I am still working on it on a bigger and wider scale and will keep updating about the scaling issues of this approach and how to fix them.

Resources

  1. Multiuser Chatroom with App Engine Channel API – Part 1: http://blog.myblive.com/2010/12/multiuser-chatroom-with-app-engine.html
  2. Source Code: http://code.google.com/p/pranav/source/browse/chat-channel
  3. Channel API: http://code.google.com/appengine/docs/python/channel
  4. Discuss: Google Groups Discussion
  5. Tic Tac Toe App: http://code.google.com/p/channel-tac-toe