When JSON encoding does not play well with character encoding

There comes a time for a developer to face a bug so weird that it leaves him/her speechless and scramble to find an answer wherever possible.

After seemingly innocent deployment to production, I noticed huge spike in errors like the following:


ActionView::Template::Error: "\xEB" from ASCII-8BIT to UTF-8

Offending line was JSON encoding of geo location information provided in request header by our cache provider.

\xEB

happens to be

ß

character in ASCII, so it was probably some German city name.

Our site’s character type is UTF-8, and Ruby 2.0’s default encoding is UTF-8. I also verified that

Encoding.default_internal

and

Encoding.default_external

are both UTF-8. It was our cache provider inserting non-UTF-8 character in request header.

No problem. I will just force UTF-8 encoding on the string.

Then, I was faced with another kind of problems.


ActionView::Template::Error: partial character in source, but hit end

Something is off.

Off to the best debugging method in Ruby – print the string encoding. And it’s ISO-8859-1 (or Latin-1).

So, the final solution was to force ISO-8859-1 encoding and encode it again in UTF-8.


string.to_s.force_encoding("ISO-8859-1").encode("UTF-8")

However, I am not 100% happy with the solution. It just smells. It works for now, but I need to look for better solution when I get a chance.

For bonus, check this out – [The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)][1]

MongoDB Query Performance Gotchas

At my current job, we use mongodb as our main database and as you can imagine we query our database a lot. The followings are what I found while investigating some slow performance. Hopefully it will prevent you from making the same mistake or pinpoint the source of problem quickly. We are using Rails 4 with Mongoid.

1. Don’t use regular expression with

/i

in queries.

One of the offending query looked pretty innocent. Customer is embedded document with email attribute and email field is indexed.


def self.with_email(email)
  self.where("customer.email" => /\Aemail\z/i).count
end

When I ran

Benchmark.bmbm

where the method was repeated 100 times, it returned the following result.


       user     system      total        real
   0.090000   0.010000   0.100000 ( 27.656723)

Compare that with when you call the same without regexp.


def self.with_email(email)
  self.where("customer.email" => email).count
end

Performance result is day and night.


       user     system      total        real
   0.080000   0.000000   0.080000 (  0.122888)

So, why the difference? I couldn’t quite figure it out myself. I knew that Mongodb uses B-Tree for index and index for email was already created. What about the regular expression was making it slow? I thought perhaps the anchor tags (

\A

and

\z

) were causing it, but that wasn’t it. I tried using substring of email and anchoring the beginning of email string (using

^

), but that didn’t yield difference either.

I finally turned to Stack Overflow for assistance, and within a few minutes, someone provided exact answer I was looking for. They key to the answer was in output of

explain

method.

Look at the

nscanned

and

indexBounds

in the output of explain method for the first method. It scanned all index because the upper and lower bounds are not defined (

""

and

{}

).


{
                     "cursor" => "BtreeCursor customer.email_1",
                 "isMultiKey" => false,
                          "n" => 781,
            "nscannedObjects" => 781,
                   "nscanned" => 500000,
    "nscannedObjectsAllPlans" => 781,
           "nscannedAllPlans" => 500000,
               "scanAndOrder" => false,
                  "indexOnly" => false,
                    "nYields" => 1397,
                "nChunkSkips" => 0,
                     "millis" => 406,
            "indexBounds" => {
    "customer.email" => [
        [0] [
            [0] "",
            [1] {}
        ],
        [1] [
            [0] /test/i,
            [1] /test/i
        ]
    ]
  }
}

Compare that to the output of explain method for the second method. Exactly the same upper and lower bounds, and it only scanned extremely small number of objects. This explains the big performance difference between the two methods.


{
                     "cursor" => "BtreeCursor customer.email_1",
                 "isMultiKey" => false,
                          "n" => 230,
            "nscannedObjects" => 230,
                   "nscanned" => 230,
    "nscannedObjectsAllPlans" => 230,
           "nscannedAllPlans" => 230,
               "scanAndOrder" => false,
                  "indexOnly" => false,
                    "nYields" => 1,
                "nChunkSkips" => 0,
                     "millis" => 0,
            "indexBounds" => {
    "customer.email" => [
        [0] [
            [0] "test@email.com",
            [1] "test@email.com"
        ]
    ]
  }
}

2. Don’t convert collection to array

Another slow performance was due to the following line of code. ProductA collection had about 20 documents and ProductB collection had about 2 documents. Another seemingly innocent code, but why?


products = ProductA.all + ProductB.all

Back to measuring performance with Benchmark (this time I just did it with Benchmark.bm).


Benchmark.bm do |x|
  x.report {n.times {|n| products = ProductA.all + ProductB.all}}
end
       user     system      total        real
  99.810000   2.630000 102.440000 (114.282440)

Let’s confirm the performance of retrieval.


Benchmark.bm do |x|
  x.report {n.times {|n| ProductA.all.count}}
end
       user     system      total        real
   0.430000   0.000000   0.430000 (  0.437339)

Benchmark.bm do |x|
  x.report {n.times {|n| ProductB.all.count}}
end
       user     system      total        real
   0.440000   0.000000   0.440000 (  0.456449)

That’s pretty reasonable performance. So, it must be something in the addition of two collections. When you add two collections using Mongoid, it returns an array, so it might be something related to converting to an array. Let’s check the performance on that.


Benchmark.bm do |x|
  x.report {n.times {|n| ProductA.all.to_a}}
end
       user     system      total        real
 110.070000   1.640000 111.710000 (117.081572)

Ah-ha! Looks like we found the problem. Converting to an array is quite slow. In order to use single collection, without converting to an array, I created a parent class – ProductParent, and ProductA and ProductB would inherit from that class. In such way, one collection is used and subclasses are distinguished by just

type

field. Actually, it should have been parent/subclass structure since ProductA and ProductB are kinds of a Product, and both share many of the same attributes.

After the re-arrangement, the following is the performance.


Benchmark.bm do |x|
  x.report {n.times {|n| ProductParent.all.count}}
end
       user     system      total        real
   0.480000   0.010000   0.490000 (  0.490489)

What a difference it made!

Finding these two performance gotchas have been a fun journey, and I hope they are useful to you. Do you have any gotchas you have found? Let me know, and I will post a link here!

Importance of owning a market segment – Monopoly, market leader, and innovator’s dilemma

I am having a great time listening to lectures for Stanford CS183B – How to start a startup online. All lectures have been extremely good, and it allowed me to get a sense of how Y Combinator accepted companies into its batches and what made them successful. I am learning so many things, and I see some common underlying themes I want to write about, but two lectures really struck a chord with me.

Before the Startup by Paul Graham and Business Strategy and Monopoly Theory by Peter Thiel, in my opinion, were dead on. Many startups were able to thrive on because many of their ideas seemed bad at the time. If you have an obviously good idea, then many people will go for the same idea. That leads to competitions. For consumers, competition is good, but for business not so. And in a sense, overarching goal of a business is to become a monopoly in its market.

I find Peter and Paul’s main points similar to those of Crossing the Chasm and Innovator’s Dilemma.

In Crossing the Chasm, the author, Geoffrey Moore, argues that in order for disruptive technology company to survive, it needs to reach beyond technology pioneers and early adapters and move to early majority market. The gap between them is so great that many technology companies fail, and he named it “chasm”. What’s the strategy for crossing the chasm successfully? He coined the first market segment in early majority “beach-head market”. This is where company has to put all its resources and try to conquer. Company can get it wrong, of course, and it’s imperative that company finds this sooner than later so that it can correct its course, and hopefully new one is the right one.

One thing he emphasizes in conquering the beach-head market is to be the market leader. Who is market leader? I believe it’s synonymous to monopoly. You need to have at least 60% of the market share in the segment. Market leader has several advantages over #2 and #3 in the market. Because of its reach in the market, there will be auxiliary companies supporting the market leader, thus creating the whole eco-system. Often times, cost of changing product from leader to another is so great that it gives the leader leeway with its customers. After taking the leadership, it can expand to adjacent markets with money and time it earned being the market leader of beach-head market (he compared to going to adjacent markets as using the top pin in bowling to knock down other pins). If I remember correctly, Geoffrey Moore suggested the beach-head market be adequate enough for a startup to tackle.

Similarly, in Innovator’s Dilemma, the author, Clayton Christensen provides examples after examples, time and time again, a startup seems to come out of nowhere and take over the 900-lbs. gorilla of the market. What’s interesting is that companies in Clayton’s examples represent diverse markets – consumer, enterprise, and even to steel mills. They all had a few things in common.

They all started by attacking the market segment with much lower margin so market leader is happy to let them take it. Market leader is usually larger in size, and thus require much higher margin to operate and make profits. While market leader grows nominally by producing products for handful of larger customers, startup becomes leader in its segment and advances its products at faster rate. When the market leader notices startups’ progress, it’s often too late and even its larger customers are ready to become startup’s customers.

Those two books definitely shaped my views of startups, and Paul and Peter’s lectures seem to validate the books’ main points. There are, of course, many other things that contribute to a startup’s success or failure – co-founder dynamics, team, culture, executions, and any external events. However, I believe, one thing is sure to attribute to startup’s success.

It could be that it’s a market segment on one cares or that the idea you are working on seems to be bad to everyone else. Whatever it is, startup needs to become a leader in market segment to survive and succeed.

Become a leader or monopoly in a market segment

How to create a (simple) watch app on Pebble

It’s been a couple of months since I joined Pebble as a full-stack web developer. Most of people know Pebble by its phenomenal success in its Kickstarter campaign – raising more than $10M. Pebble is also known as the company that started the smartwatch/wearable market.

Ever since I started, I have come to like a few watch apps and faces, and I’ve always wanted to create something on my own (since I like to build things). Even though we have a hack day every other week, I wasn’t able to participate because of things that needed to get done.  But today was a special one. It was hack night, instead of hack day, and I was able to participate.

In today’s hackathon, I built a simple app that displays a short quote like Unix fortune using Pebble’s pebblejs and CloudPebble. It was super easy. Almost too easy. I don’t interact with our dev evangelist team much, I have new respect for those guys. They worked hard to make it easy for developers to develop apps on Pebble.

I have shared my code on my github account.  You should check it out, and it should be self-explanatory. Use pebblejs as reference to look up functions.

https://github.com/yangtheman/fortunes_pebble_app

Here we go.

1. Enable Developer Mode on your mobile phone. On iPhone: Settings > Pebble > Developer Mode > On

IMG_3529

IMG_3527

2. Enable Developer Mode on Pebble mobile app. On iPhone: Pebble App > Settings (where you see My Pebble) > Developer > Enabled

IMG_3528

IMG_3530

3. Log in to CloudPebble using the same username and password as one you created when you first set up Pebble.

4. Create a new project and select Pebble.js as Project Type.

Screen Shot 2014-07-17 at 10.02.49 PM

5. Click on app.js on the left column.

Screen Shot 2014-07-17 at 9.32.22 PM

6. If you want, you can leave the default code as it is. Or you can copy and paste my code in pebble-js-app.js. Don’t publish your app using my code as it is, but you are certainly welcome to try it out and tweak it!

7. When you want to see the app run on the watch, click on the white Play button with green background on the right side of the screen. This should automatically compile and install the app on your watch. Make sure you have an empty slot in the locker on Pebble mobile app. If compile/install fails, toggle developer mode on Pebble mobile app as in step 2 above and try again. If you have any debug statements using console.log, you will see the log messages in ‘View Logs’ as you interact with the app.

Screen Shot 2014-07-17 at 9.32.12 PM

8. You can change your code and compile/run until you are satisfied. Next steps are for publishing the app on Pebble App Store.

9. Get some screenshots from your watch app. From CloudPebble, click on COMPILATION link. Then click on SCREENSHOT button. It will take screenshot of what’s on the watch. Take a couple of different screenshots.

Screen Shot 2014-07-17 at 9.41.50 PM

10. Get the PBW file. In COMPILATION view, you will see a list of builds. You probably want to grab the latest one (assuming the last build is the one you want to publish).

11. Log in to Pebble Dev Portal using the same username and password as one you created when you first set up Pebble, and click on Publish a Pebble App button.

10. In our case, we are adding a watch app, so click on Add a Watchapp link on the left.

Screen Shot 2014-07-17 at 10.09.01 PM

12. Filling out the form should be pretty straight forward.

12.1. For icons, make sure they are Black with White background. Do not use transparent background. 

12.2. Use the screenshots you downloaded earlier for screenshots.

12.3. You can be as creative as your want for the header image.

13. Once you are done, hit CREATE button at the bottom of the page.

14. You are not quite done yet. Lastly, go down to Releases section and publish your first version. 

Screen Shot 2014-07-17 at 10.11.01 PM

15. After that you can make your app public by clicking on the Make Public button on the top right corner of the page.

16. Ta-Da! Your app is ready on Android platform first. It will take a day or two to appear on iOS platform. There you go. You now successfully created and published the first app!

Best way to learn is to imitate (and how it should apply to startups in emerging markets)

Recently there were interesting articles beng circulated and discussed in the valley. One is Black Swan Farming by Paul Graham and the other one is Screw the Black Swans by Dave McClure. Interestingly enough, the fireside chat with Vinod Khosla at the TechCrunch Disrupt SF 2012 was a timely interview and speaks to the different viewpoints.

I suspected, but was surprised to learn that out of all YC companies, Airbnb and Dropbox account for three quarters (3/4) in terms of about $10 Billion valuation. Paul was saying how difficult it is to pick a game changer winner. What makes it more difficult is how great ideas seem like bad ideas in the beginning. If an idea looks good, then everyone including large companies will work on it, and startups will have even less chance of succeeding. It’s when an idea seems bad and thus hard time getting funded, but at the end succeeds, it’s a game changer (homerun).

Vinod Khosla said pretty much the same. His fund looks for companies that will make tremendous impact when successful. It doesn’t matter when they fail. Founders will move on to a new project. But they don’t want to invest in companies that will make a little to no impact when successful. Again, Vinod is looking for a game changer (homerun) company.

Dave on the other hand is saying that 500 startups focus on Ichiro’s of the world (consistent hitting) rather than Barry Bond’s of the world (homerun king). He goes further into discussing the differences between YC and 500 startups (like hackers vs. hustlers), but my main takeaway was what kind of companies they were looking to fund.

These discussions made me think about how startups in emerging markets (including Korea) could do better. When babies are born, they learn mostly by imitating people around them. There is also an old saying, “Imitation is the best form of flattery”. Many amateur athletes also learn by imitating professional players (watching and learning). Korea is the only country I know best outside the US, and I always noticed the lack of virtuous seed funding cycle in Korea – successful founders seed investing in other startups. Once startup is up and running, and shows notable tractions, they can raise money from VCs and the process and valuation will be much better. However, startups often fail even before they could show traction because either they make multiple mistakes or run out of money before they can pivot. Without proper seed funding (and mentoring), they will have really hard time reaching the traction point.

At any rate, I think the best way for startups in Korea (or in emerging markets) to produce good enough winners to start the virtuous cycle is to imitate successful US startups in Korea (or in emerging markets), and after having a few successful exits, the founders can help other founders by investing and mentoring them. Furthermore, after successful exits, those founders would be in much better position to take bigger risk and attempt to make big impacts. In baseball analogy, it would be something along the line of having a few good seasons of hitting consistently, then you can swing for homeruns. No doubt those good seasons would be a confidence builder as well.

I keep hearing how there are many incubators and accelerators popping up in Korea. Someone recently expressed a concern that it’s like a startup bubble and a few failures will have devastating effect on the whole startup movements. I think this type of imitation strategy will be good at least in the beginning. After there are good number of successful and experienced founders, they would be better equipped to make sounding decisions and also to help other up-and-coming founders. It would truly strengthen startup community in Korea (or in emerging markets).

How to install Ubuntu on Chomebook (CR-48) and put parental control

I have had a couple of first-generation Chromebook (CR-48) for a while. I thought these would be pretty good portable computing devices to give to my kids. However, I wasn’t ready to do so unless there was some sort of parental control. As someone who spends a lot of time online, it’s too rough place for kids to roam around. I have known about their support for dev mode, and I decided to figure out which linux flavor would support CR-48.

First search attempt turned out Ubuntu, and since Ubuntu is known for better UI, I decided to give it a try. I also found out there is an excellent parental control guide on Ubuntu as well.

Install Ubuntu on Chromebook (CR-48)

There is no need to re-invent the wheel. Follow instruction here to put Ubuntu 11.04 on Chromebook (CR-48). It worked out like a charm.

Parental Control on Ubuntu

There is a great guide here, and I couldn’t get the Web Content Control to work. However, timekpr is pretty good. You should at least install that.

For filtering content, ProCon Latte Content Filter Firefox Add-on has been working pretty well.

So far, I have been happy with Ubuntu on CR-48. It’s slow and keypad doesn’t work too well, but I think kids are just happy to have their own laptops. :)

How to install KidsRuby on Mac OS X and Ubuntu

I have two kids, and I have been wanting to find ways to introduce my kids to programming. Especially for my son, who has asperger syndrome. I thought programming would be a good way to use his interests in logic and mathematics to good use.

I was so much in joy when I found KidsRuby. It’s an awesome project. I think Ruby is a great first language because of its elegance and object-oriented nature to the core.

Previously I had a hard time installing KidsRuby my Mac OS X, due to qt installation error. I was able to at least get it going by cloning their git repository. I had filed a bug for the qt installation error, and the author responded some time ago, but I hadn’t had time to verify it.

I finally had time to verify it, and it worked like a charm.

I also had converted two first-generation Google Chromebooks to use Ubuntu to give to my kids. And while I was familiar with Fedora, this was my first time using Ubuntu, and as new user, I had to search around the net to install Ruby and to install KidsRuby. I am close to finishing it, and I will describe it here.

Mac OS X

This one is pretty easy. Just download the installer dmg file from here, and follow the instruction. It will take a while, but it would be well worth it. You can find the KidsRuby folder in Application folder.

Ubuntu

Now, this one took a while. If you have Ruby installed already, you are good to go. If not, follow the direction below.

If Ruby isn’t installed

You can simply follow the direction from Ryan Bigg to install Ruby 1.9.3.

If Ruby is already installed

I am not sure which version of Ruby is supported, but this applies to Ruby 1.9.3.

The biggest problem I faced was the problem with ffi gem. I kept getting the following error.

Installing ffi (1.0.10) with native extensions
Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.

        /usr/bin/ruby1.9.1 extconf.rb
:29:in `require': no such file to load -- mkmf (LoadError)
	from :29:in `require'
	from extconf.rb:4:in `'

Gem files will remain installed in /usr/share/kidsruby/ffi/ruby/1.9.1/gems/ffi-1.0.10 for inspection.
Results logged to /usr/share/kidsruby/ffi/ruby/1.9.1/gems/ffi-1.0.10/ext/ffi_c/gem_make.out
An error occured while installing ffi (1.0.10), and Bundler cannot continue.
Make sure that `gem install ffi -v '1.0.10'` succeeds before bundling.

After searching the net for a while, I realized that I needed to install “-dev” package as well. Since I am using 1.9.3, I had to install ruby1.9.1-dev.

sudo apt-get install ruby1.9.1-dev

After that you can clone the git repository and install necessary packages.

git clone https://github.com/hybridgroup/kidsruby.git

cd kidsruby

sudo apt-get install libqt4-dev
sudo apt-get install cmake
gem install qtbindings
bundle install

The bundle install part will take a while.

For the lesson part, I had a surprising result – my son totally lost interest very quickly, but my daughter was more into it. :)

KidsRuby on Ubuntu

Never never give up on your life

When I first read TechCrunch’s article on Diaspora co-founder Ilya Zhitomirskiy’s death, I didn’t think much of it except that it didn’t mention anything about the cause of the death. It usually means only thing, and my suspicion was confirmed by hacker news thread.

http://news.ycombinator.com/item?id=3231531

What I particularly noticed about the thread was discussion of failure and stress of founding a startup and other suicides by very smart folks. It also reminded me of earlier tragic passing of a co-founder of a Y-Combinator-funded company and the article I read on WIRED magazine about two AI scientists committing suicides almost in identical ways.

I would never know why these guys did what they did. But for me, I have one thing that would prevent me from thinking about it. It’s my kids and my family to an extent. It’s a double-edged sword. On one side, it’s the reason for not being able to take huge risk, but on the other side, it’s the reason for my sanity no matter how shitty my life is at any given moment (and my life right now isn’t all that spectacular).

Also, we should also keep things in perspective. In grand scheme of this universe, we are just small part of green/blue spec called Earth. You shouldn’t care about and be afraid of failures/rejections. Who cares? People will forget and move on. I think it’s courageous and commendable to just try. Regardless of outcome, having tried something sets you ahead of many others.

Just remember the following quotes.

“Regret for the things we did can be tempered by time; it is regret for the things we did not do that is inconsolable.”

Also, especially this one.

“It is not the critic who counts; not the man who points out how the strong man stumbles, or where the doer of deeds could have done them better. The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood; who strives valiantly; who errs, who comes short again and again, because there is no effort without error and shortcoming; but who does actually strive to do the deeds; who knows great enthusiasms, the great devotions; who spends himself in a worthy cause; who at the best knows in the end the triumph of high achievement, and who at the worst, if he fails, at least fails while daring greatly, so that his place shall never be with those cold and timid souls who neither know victory nor defeat.”

(org.mongodb.mogod) Exited with exit code: 100

For some reason, I couldn’t run mongo shell and the console was overrun by the same error message like the following.

8/1/11 6:49:15 PM com.apple.launchd[1] (org.mongodb.mongod) Throttling respawn: Will start in 10 seconds
8/1/11 6:49:25 PM com.apple.launchd[1] (org.mongodb.mongod[394]) Exited with exit code: 100
8/1/11 6:49:15 PM com.apple.launchd[1] (org.mongodb.mongod) Throttling respawn: Will start in 10 seconds
8/1/11 6:49:25 PM com.apple.launchd[1] (org.mongodb.mongod[394]) Exited with exit code: 100

And searching on Google didn’t turn up any interesting results.

IT PAYS TO LOOK AT YOUR LOG FILE! Well, this shouldn’t be new, but it somehow skipped my mind until I saw the config file.

In the log file was the following helpful error message.

**************
old lock file: /usr/local/mongodb_data/mongod.lock.  probably means unclean shutdown
recommend removing file and running --repair
see: http://dochub.mongodb.org/core/repair for more information
*************

Mon Aug  1 18:49:35 [initandlisten] exception in initAndListen std::exception: old lock file, terminatingMon Aug  1 18:49:35 dbexit:
Mon Aug  1 18:49:35 [initandlisten] shutdown: going to close listening sockets...
Mon Aug  1 18:49:35 [initandlisten] shutdown: going to flush diaglog...
Mon Aug  1 18:49:35 [initandlisten] shutdown: going to close sockets...
Mon Aug  1 18:49:35 [initandlisten] shutdown: waiting for fs preallocator...
Mon Aug  1 18:49:35 [initandlisten] shutdown: closing all files...
Mon Aug  1 18:49:35 closeAllFiles() finished
Mon Aug  1 18:49:35 dbexit: really exiting now
Well, after deleting the lock file, everything was back to normal.

When in doubt, always look in your log file!

Relevance in current Internet

I just finished reading TechCrunch article, How Facebook Can Put Google Out of Business. This also reminded me of Paul Adam’s articles, The Real Life Social Network and How Your Customers’ Social Circles Influence What They Buy, What They Do and Where They Go. And also this good analogy of Facebook, Google and other hot startups.

I was also asked a lot of questions about problem of current social media and how I could make it better as part of interview questions. One common answer I provide is the relevance. How important are these news, status updates, tweets, etc. to me? I agree that the biggest difference between Facebook and Google is the core of each company – people (social) or links (web pages).

Many companies are trying to figure out who you are as a person. For example, another favorite startup of mine, Hunch, is trying to figure out who you are and what you would like based on what you already like. Netflix tries to figure out what movies you might like based on your ratings of movies. Amazon has been doing it for a while with features like “you might also like…” Facebook is obviously in good position because of the social interaction data they have – what you shared, who you have interacted with, what you liked, etc. in addition to social graph. Twitter can also certainly figure out by analyzing followers, tweets and especially retweets, but right now I feel that noise-to-signal ratio on Twitter is too high. You control tweet relevance by carefully choosing who you follow.

We are bound to create more data. The amount of data we generate will never decrease. In the sea of data, it would be harder and harder to find information that is relevant to you, but to others. In this sense, Facebook is definitely sitting on a gold mine. It’s kind of creepy, but the more interactions you have on Facebook, the more Facebook knows you, and it provides good targeting data to advertisers. Can Google build significant social product? Nothing is impossible, but I think it would be very hard. Facebook is just too big, and I just don’t see why I would use similar feature on another platform. Once you have your social network established on one platform, it is extremely hard to create the similar network on a different platform. In social network, the winner takes all, unless the winner screws up big time. It could happen (as has happened a couple of times already with Friendster and MySpace).

We are living in an interesting time, indeed.