Monday, April 18, 2011

Customer Support

Customers really don't want Customer Support, they just want working products. 

Friday, April 08, 2011

Price & Retailer Margin

Assuming price is determined by the demand and supply rules, how does one decides the retailer margin?

Let P be the price and M% be the retailers margin and C the expected number of units sold per month, then

Earnings per month for retailer = P * M * C
This number should be high enough to make a living for the retailer after deducting the cost of running the business.

Examples:

  • Mobile prices are high and number of units sold per month is good, causing margins to be low
  • Taps, shower, lights, basins, etc are sold at low volumes, prices are OK and margins are very high
  • FMCG products have very low margins
  • Kitchen chimneys and burners are high priced, volumes are low and margins are high
  • Cigarettes have high volume and hence margins are low
  • High end cosmetics have very low volumes, hence has reasonable margins
  • Books have low volume, unit price is OK, margins should be high
  • Text Books have high volume, unit price is OK, but they are sold only once in a year, very much like crackers on diwali.
  • Pet food also has low volume and hence high margins
  • Furniture again low volume, high price and hence margins ought to be reasonable.
I guess the opportunity for e-commerce is creating business efficiency for low volume products to drive their prices down by passing on the profits to customers making it impossible for some of the retailers to justify their business. The high volume products will always have lower margins and hence price is not what will drive buying on the net, but the other factors like trust, service, range, etc. It also opens up the market for products which couldn't find retail space because they couldn't fulfill this equation.

Another side effect of this equation is what will be counter intuitive for most economists. Under specific circumstances, when the demand goes low, prices don't really go down, they increase. The reason - retailer needs to meet his monthly needs. If he makes less money, the only way to go back to original income levels is increasing the price and not decreasing the price. The specific circumstances are:

  • All retailers are friends (taxi operators, unions, etc) 
  • Customer lifetime values is same as that of current transaction. For example a tourist hiring a taxi is not going to remember or call the same guy on his next visit.
  • How much time does customer has to make the decision. The lesser the time, less choice he has.
  • Customer already committed part of the money. 
  • Does competition exists? 
  • Is trust part of the equation?  

Wednesday, April 06, 2011

Old Poem

मुर्गी से आया है अंडा
या अंडे से मुर्गी आई है 
कविता ने कवी को बनाया है 
या कवी ने कविता बनाई है
दुविधा में हूँ फंसा हुआ 
क्या सच है क्या सचायी है

क्या सच की कोई परिभासा है 
क्या सच झूठ के बीच कोई रेखा है 
जो देखा है वोह सच है क्या 
क्या सच को किसी ने देखा है 

क्या वही है सच जो दीखता है 
या वोह जो आप समझते हैं
लकिन जो आप समझते है 
क्या आप उसे समझते हैं

मैंने सच को समझा नहीं 
जो समझा है समझा दे मुझे 
मैं उलझा हूँ उलझन मैं मेरी 
मुजको मुझेसे मिला दो हरे



Mean And/Or Stupid

Mean and Stupid are two labels which can be used to describe people. Just like tall, dark, handsome or generous, wealthy, shy, happy, content, etc. All labels are dangerous, but these two in-particular are particularly dangerous.

Label someone mean and it gives us the license to be mean to them.
Label someone stupid and it gives us the license to not listen to them. Someone who doesn't appreciates something intelligent said to them is by definition stupid.

Thinking someone is mean and/or stupid eventually makes us  mean and stupid and being mean and stupid anyway causes other people to be mean and stupid. It is a vicious circle.  It is fiction creating hardcore reality which is actually factitious.

Just think how many people are mean and stupid just because we think they are. You are what you think you are is alright, but you are what you think someone else is, is scary.

Thursday, March 17, 2011

Speeding up Kahlua

Earlier post Scriptable Object Cache

The first implementation of the lua based http proxy was fairly slow.  On a 1.6Gz quad core intel processor, it could proxy about 1K requests per second. The final number was about 15K messages per second with 5KB request and 5KB response.

The first problem was LuaTableImpl. It is very slow.  The problem is lua table is both a hashmap and a list with single iterator.  One of the methods in the lua table interface is:


Object next(Object key);

Hack number one is to use default java Hashmap instead of the built in LuaTableImpl. Need to take care of making sure that "array" access works and "next" works.

Problem number two is the overhead of creating the runtime. In my case every request needs a new runtime  or LuaState object which is associated with each request. This was very expensive. Here is what needs to be done to create luaState and set it up for the proxy code to execute.

String     fileName = "/luaTestCode/test.lua";
LuaState   state    = new LuaState(System.out);
File       luaFile  = new File(fileName);
LuaClosure closure  = LuaCompiler.loadis(new FileInputStream(luaFile),
    luaFile.getName(), state.getEnvironment());
state.call(closure, null);
LuaConverterManager manager = new LuaConverterManager();
LuaNumberConverter.install(manager);
LuaTableConverter.install(manager);
LuaJavaClassExposer exposer = new LuaJavaClassExposer(state, manager);
        exposer.exposeClass(LuaContext.class);
                exposer.exposeClass(LuaHttpRequest.class);
                            exposer.exposeClass(LuaHttpResponse.class);

Object proxy = state.getEnvironment().rawget("proxy_function");
Object value = state.call(proxy, null);


As you can see, we have file read, followed by compiler, followed by some Reflection code which will setup java functions in the lua runtime. All of this is expensive and needs to be done for every message. To avoid this, I used couple of hacks. First one was to avoid the compilation again and again. This one was simple. The output of compilation step is LuaClosure. I changed the code so that instead of LuaClosure I would get LuaPrototype which is the real output of compilation. This could be easily reused to create as many LuaClosure objects without paying the price of file access and compilation. The second hack was to avoid setup cost of LuaState object. Digging into code revealed that LuaState (without the stack) is nothing but the environment or a luaTable. So I just added clone method to the LuaTable implementation and used it to cheaply construct new LuaStates which had all the java classes correctly exposed.  The new code looked something like this.

public class LuaContext {
private LuaState     state;
private LuaClosure   closure;
private Object       coroutine;

private static final LuaState            BASE;
private static final LuaConverterManager MANAGER;
private static final LuaJavaClassExposer EXPOSER;
static {
BASE    = new LuaState(System.out);
MANAGER = new LuaConverterManager();
LuaNumberConverter.install(MANAGER);
LuaTableConverter.install(MANAGER);
EXPOSER = new LuaJavaClassExposer(BASE, MANAGER);
                EXPOSER.exposeClass(LuaContext.class);
EXPOSER.exposeClass(LuaHttpRequest.class);
EXPOSER.exposeClass(LuaHttpResponse.class);
}

public LuaContext(LuaPrototype proto) 
throws IOException {
this.state         = BASE.clone();
this.closure       = new LuaClosure(proto, state.getEnvironment());
this.state.call(this.closure, null);
this.coroutine     = this.state.getEnvironment().rawget("proxy_function");
}
That is it.  After adding support for storing state (lua objects), lock and some basic json, I was able to write some cool proxy features in lua itself.

Here is how I could do redirect handling at server side...

function getServerAndPath(response) 
       local newURL = response:getHeader("Location")
       local server     = nil
       local path        = nil
       if (newURL ~= nil) then
            local h1, h2 = string.find(newURL, "http://")
            local s1, s2  = string.find(newURL, "/", h2+1)
            server          = string.sub(newURL, h2+1, s1-1)
            path             = string.sub(newURL, s1)
       end
       return server, path 
end


function isRedirectResponseCode(code)
      if (code == 301) then return true end
      if (code == 302) then return true end
      if (code == 303) then return true end
      if (code == 307) then return true end
      return false
end

function proxy(context, httpRequest)
         local targetServer   = "yahoo.com"
         local path                  = httpRequest:getUri() 
         local responseCode = 301
         local httpResponse  = nil

         while (isRedirectResponseCode(responseCode)) do 
      httpRequest:setHeader("Host", targetServer)
               httpRequest:setUri(path)
               httpResponse = luaSendRequestAndGetResponse(context, httpRequest) 
               responseCode = httpResponse:getStatus()
               print ("got response with status " .. responseCode)
               targetServer, path = getServerAndPath(httpResponse) 
         end
         luaSendResponse(context, httpResponse)
end


 Here is another example...for doing least connection based server load balancing.

-- simple lb implementation 

function newLB() 
  local  object      = {}
  object.lock        = NewLuaLock()
  object.connections = {}
  return object
end

function addServer(object, serverName) 
    object.lock:lock()
    if (object.connections[serverName] == nil) then
        object.connections[serverName] = 0
    end
    object.lock:unlock()
end

function findLeastUsedServer(object)
    local min    = 64 * 1024
    local result = nil
 
    object.lock:lock()
    for k,v in pairs(object.connections) do
if (v < min) then 
             min    = v 
             result = k
        end        
    end
    object.connections[result] = min + 1
    object.lock:unlock() 
    return result
end

function decreaseServerCount(object, server)
    object.lock:lock()
    if (object.connections[server]  ~= nil) then 
if (object.connections[server] > 0) then 
           object.connections[server] = object.connections[server] -1
        end
    end
    object.lock:unlock()
end

--------------------------------------------------------------

function getLBResource() 
local objectMap    = getObjectMap()
         local lbResource   = objectMap:get("lb")

         if (lbResource == nil) then 
lbResource = newLB() 
                addServer(lbResource, "google.com")
                addServer(lbResource, "yahoo.com")
                objectMap:put("lb", lbResource)    
         end 
         return lbResource
end


function proxy(context, httpRequest)
         local lbResource = getLBResource()
         local server         = findLeastUsedServer(lbResource)
               httpRequest:setHeader("Host", server)
         local response    = luaSendRequestAndGetResponse(context, httpRequest) 
               decreaseServerCount(lbResource, server)
         luaSendResponse(context, response)
end

The final example show how to filter the twitter timeline of all the junk information and cache it for a while at the proxy.

function proxy(context, httpRequest)
         local globalCache     = getHttpResponseCache()
local twitterResponse = globalCache:get("timeline")
       
         if (twitterResponse == nil) then 
          local twitterRequest  = NewLuaHttpRequest("HTTP/1.1", "GET", "/1/statuses/public_timeline.json")
                  twitterRequest:setHeader("Host", "api.twitter.com")                
         twitterResponse = luaSendRequestAndGetResponse(context, twitterRequest)
                
                  local jsonArray       = NewLuaJSONArrayFromText(twitterResponse:getContent())    
                  local newJsonArray    = NewLuaJSONArray()        
          
                  for i=0,(jsonArray:length()-1) do 
                       local current = jsonArray:getJSONObjectAtIndex(i)
                       local text    = current:getString("text")
                       newJsonArray:putString(text)
                  end
                  twitterResponse:setContent(newJsonArray:toString())
                  -- cache for five minutes
                  globalCache:put("timeline", 300, twitterResponse)
          end
          luaSendResponse(context, twitterResponse)
end