self optimizing routing code (#196)

* optimize lookups with a prefix tree

* fix tests
diff --git a/Makefile b/Makefile
index 0c4b37b..209eea1 100644
--- a/Makefile
+++ b/Makefile
@@ -26,6 +26,10 @@
 	 	-e TOKEN_FACEBOOK_URL=https://graph.facebook.com/debug_token \
 		-e TOKEN_GITHUB_URL=https://api.github.com/user \
 		-e DEBUG=true \
+		-e CACHING_ENABLED=true \
+		-e CACHE_SIZE=2048 \
+		-e CACHE_TTL=180 \
+		-e OPTIMIZE=1 \
 		-d openwhisk/apigateway-profiling:latest
 
 .PHONY: test-run
diff --git a/api-gateway.conf b/api-gateway.conf
index 4c18873..3aea095 100644
--- a/api-gateway.conf
+++ b/api-gateway.conf
@@ -34,7 +34,7 @@
 env HOST;
 env PORT;
 
-
+env OPTIMIZE;
 env REDIS_RETRY_COUNT;
 env SNAPSHOTTING;
 env CACHING_ENABLED;
diff --git a/conf.d/managed_endpoints.conf b/conf.d/managed_endpoints.conf
index ba9c94e..c7c5396 100644
--- a/conf.d/managed_endpoints.conf
+++ b/conf.d/managed_endpoints.conf
@@ -79,7 +79,7 @@
 
         access_by_lua_block {
             local routing = require "routing"
-            local ds = require "lib/dataStore" 
+            local ds = require "lib/dataStore"
             routing.processCall(ds.init())
         }
 
diff --git a/scripts/lua/cors.lua b/scripts/lua/cors.lua
index 3a22af1..50333c8 100644
--- a/scripts/lua/cors.lua
+++ b/scripts/lua/cors.lua
@@ -37,20 +37,20 @@
 function _M.replaceHeaders()
   if ngx.var.cors_origins ~= nil then
     if ngx.var.cors_origins == 'true' then
-      ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']  
+      ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
       ngx.header['Access-Control-Allow-Origin'] = '*'
       ngx.header['Access-Control-Allow-Methods'] = ngx.var.cors_methods
       if ngx.var.cors_methods == nil then
         ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
       end
-    elseif ngx.var.cors_origins == 'false' then 
+    elseif ngx.var.cors_origins == 'false' then
       ngx.header['Access-Control-Allow-Origin'] = nil
       ngx.header['Access-Control-Allow-Methods'] = nil
     else
       ngx.header['Access-Control-Allow-Origin'] = ngx.var.cors_origins
       ngx.header['Access-Control-Allow-Methods'] = ngx.var.cors_methods
-      ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']  
-      if ngx.var.cors_methods == nil then 
+      ngx.header['Access-Control-Allow-Headers'] = ngx.req.get_headers()['Access-Control-Request-Headers']
+      if ngx.var.cors_methods == nil then
         ngx.header['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS'
       end
     end
diff --git a/scripts/lua/lib/dataStore.lua b/scripts/lua/lib/dataStore.lua
index 764d870..85f209c 100644
--- a/scripts/lua/lib/dataStore.lua
+++ b/scripts/lua/lib/dataStore.lua
@@ -12,7 +12,6 @@
   setmetatable(o, self)
   self.__index = self
   o.impl = require(utils.concatStrings({'lib/', DATASTORE}))
-  o.ds = o.impl.init()
   return o
 end
 
@@ -38,28 +37,43 @@
 end
 
 function DataStore:close()
-  return self.impl.close(self.ds)
+  if self.ds == nil then
+    return nil
+  else
+    return self.impl.close(self.ds)
+  end
 end
 
 function DataStore:addAPI(id, apiObj, existingAPI)
+  self:singleInit()
   self:setSnapshotId(apiObj.tenantId)
   return self.impl.addAPI(self.ds, id, apiObj, existingAPI)
 end
 
 function DataStore:getAllAPIs()
+  self:singleInit()
   return self.impl.getAllAPIs(self.ds)
 end
 
 function DataStore:getAPI(id)
-  return self.impl.getAPI(self.ds, id)
+  local result, ds =  self.impl.getAPI(self.ds, id)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
 
 function DataStore:deleteAPI(id)
+  self:singleInit()
   return self.impl.deleteAPI(self.ds, id)
 end
 
 function DataStore:resourceToApi(resource)
-  return self.impl.resourceToApi(self.ds, resource, self.snapshotId)
+  local result, ds = self.impl.resourceToApi(self.ds, resource, self.snapshotId)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
 
 function DataStore:generateResourceObj(ops, apiId, tenantObj, cors)
@@ -67,89 +81,131 @@
 end
 
 function DataStore:createResource(key, field, resourceObj)
+  self:singleInit()
   return self.impl.createResource(self.ds, key, field, resourceObj, self.snapshotId)
 end
 
 function DataStore:addResourceToIndex(index, resourceKey)
+  self:singleInit()
   return self.impl.addResourceToIndex(self.ds, index, resourceKey, self.snapshotId)
 end
 
 function DataStore:deleteResourceFromIndex(index, resourceKey)
+  self:singleInit()
   return self.impl.deleteResourceFromIndex(self.ds, index, resourceKey, self.snapshotId)
 end
 function DataStore:getResource(key, field)
+  self:singleInit()
   return self.impl.getResource(self.ds, key, field, self.snapshotId)
 end
 function DataStore:getAllResources(tenantId)
+  self:singleInit()
   return self.impl.getAllResources(self.ds, tenantId, self.snapshotId)
 end
 function DataStore:deleteResource(key, field)
+  self:singleInit()
   return self.impl.deleteResource(self.ds, key, field, self.snapshotId)
 end
 
 function DataStore:addTenant(id, tenantObj)
+  self:singleInit()
   return self.impl.addTenant(self.ds, id, tenantObj)
 end
 
 function DataStore:getAllTenants()
+  self:singleInit()
   return self.impl.getAllTenants(self.ds)
 end
 
 function DataStore:getTenant(id)
-  return self.impl.getTenant(self.ds, id)
+  local result, ds =  self.impl.getTenant(self.ds, id)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
 
 function DataStore:deleteTenant(id)
+  self:singleInit()
   return self.impl.deleteTenant(self.ds, id)
 end
 
 function DataStore:createSubscription(key)
+  self:singleInit()
   return self.impl.createSubscription(self.ds, key, self.snapshotId)
 end
 
 function DataStore:deleteSubscription(key)
+  self:singleInit()
   return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
 end
 
 function DataStore:getSubscriptions(artifactId, tenantId)
-  return self.impl.getSubscriptions(artifactId, tenantId)
+  self:singleInit()
+  return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
 end
 
 function DataStore:healthCheck()
+  self:singleInit()
   return self.impl.healthCheck(self.ds)
 end
 
 function DataStore:addSwagger(id, swagger)
+  self:singleInit()
   return self.impl.addSwagger(self.ds, id, swagger)
 end
 
 function DataStore:getSwagger(id)
+  self:singleInit()
   return self.impl.getSwagger(self.ds, id)
 end
 
 function DataStore:deleteSwagger(id)
+  self:singleInit()
   return self.impl.deleteSwagger(self.ds, id)
 end
 
 function DataStore:getOAuthToken(provider, token)
-  return self.impl.getOAuthToken(self.ds, provider, token)
+  local result, ds =  self.impl.getOAuthToken(self.ds, provider, token)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
 
 function DataStore:saveOAuthToken(provider, token, body, ttl)
+  self:singleInit()
   return self.impl.saveOAuthToken(self.ds, provider, token, body, ttl)
 end
 
 function DataStore:exists(key)
-  return self.impl.exists(self.ds, key, self.snapshotId)
+  local result, ds =  self.impl.exists(self.ds, key, self.snapshotId)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
 
 function DataStore:setRateLimit(key, value, interval, expires)
+  self:singleInit()
   return self.impl.setRateLimit(self.ds, key, value, interval, expires)
 end
+
 function DataStore:getRateLimit(key)
-  return self.impl.getRateLimit(self.ds, key)
+  local result, ds = self.impl.getRateLimit(self.ds, key)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
 end
--- o be removed in the future
+
+function DataStore:optimizedLookup(tenant, path)
+  local result, ds = self.impl.optimizedLookup(self.ds, tenant, path)
+  if ds ~= nil then
+    self.ds = ds
+  end
+  return result
+end
 
 function DataStore:deleteSubscriptionAdv(artifactId, tenantId, clientId)
   local subscriptionKey = utils.concatStrings({"subscriptions:tenant:", tenantId, ":api:", artifactId})
@@ -166,6 +222,19 @@
   return true
 end
 
+function DataStore:optimizeLookup(tenant, resourceKey, pathStr)
+  self:singleInit()
+  return self.impl.optimizeLookup(self.ds, tenant, resourceKey, pathStr)
+end
+
+
+function DataStore:singleInit()
+  if self.ds == nil then
+    self.ds = self.impl.init()
+  end
+end
+-- to be removed in the future
+
 function _M.init()
   return DataStore:init()
 end
diff --git a/scripts/lua/lib/redis.lua b/scripts/lua/lib/redis.lua
index bfc24fc..65bc38c 100644
--- a/scripts/lua/lib/redis.lua
+++ b/scripts/lua/lib/redis.lua
@@ -29,6 +29,7 @@
 local CACHE_SIZE
 local CACHE_TTL
 local c, err
+
 local REDIS_HOST = os.getenv("REDIS_HOST")
 local REDIS_PORT = os.getenv("REDIS_PORT")
 local REDIS_PASS = os.getenv("REDIS_PASS")
@@ -66,7 +67,6 @@
   local port = REDIS_PORT
   local redis = require "resty.redis"
   local red = redis:new()
-
   red:set_timeout(REDIS_TIMEOUT)
   -- Connect to Redis server
   local retryCount = REDIS_RETRY_COUNT
@@ -534,14 +534,106 @@
   return red:set(key, value, interval, expires)
 end
 
+-- rate limiting is kind of special in that I don't want to get it from the cache because the intervals are too small.
+-- eventually may consider moving it into an nginx variable instead of redis
 function _M.getRateLimit(red, key)
-  return get(red, key)
+  if red == nil then
+    red = _M.init()
+  end
+  return red:get(key), red
+end
+
+function _M.optimizedLookup(red, tenant, path)
+  if CACHING_ENABLED then
+    local cached = c:get(utils.concatStrings({'fastmap:', tenant, ':', path}))
+    if cached ~= nil then
+      return cached
+    end
+  end
+  local script = [[
+    local tenant = ']] .. tenant .. [['
+    local path = ']] .. path .. [['
+    if redis.call('EXISTS', 'resources:' .. tenant .. ':' .. path) ~= 0 then
+      return 'resources:' .. tenant .. ':' .. path
+    end
+    local currStr = 'fastmap:' .. tenant
+    path = string.match(path, '[^?]*')
+    local exp_path = string.gmatch(path, '[^/]*')
+    local path = {}
+
+    for i in exp_path do
+      if i ~= nil and i ~= '' then
+        table.insert(path, i)
+      end
+    end
+
+    for i,v in ipairs(path) do
+      if redis.call('EXISTS', currStr .. '/' .. v) == 1 then
+        currStr = currStr .. '/' .. v
+      elseif redis.call('EXISTS', currStr .. '/.*') == 1 then
+        currStr = currStr .. '/.*'
+      else
+        return 0
+      end
+    end
+    return redis.call('GET', currStr)
+  ]]
+  if red == nil then
+    red = _M.init()
+  end
+  local result = red:eval(script, 0)
+  if type(result) ~= 'string' or result == '' then
+    return nil, red
+  end
+  ngx.var.gatewayPath = result:gsub(utils.concatStrings({'resources:', tenant, ':'}), '')
+
+  if CACHING_ENABLED then
+    c:set(utils.concatStrings({'fastmap:', tenant, ':', path}), result, CACHE_TTL)
+  end
+
+  return result, red
+end
+
+function _M.optimizeLookup(red, tenant, resourceKey, pathStr)
+  local startingString = utils.concatStrings({'fastmap:', tenant})
+  if get(red, startingString) == nil then
+    set(red, startingString, '')
+  end
+  path = {}
+  key = {}
+  for p in string.gmatch(pathStr, '[^/]*') do
+    if p ~= '' then
+      table.insert(path, p)
+    end
+  end
+
+  for r in string.gmatch(resourceKey:gsub('[^:]*:[^:]*:', ''), '[^/]*') do
+    if r ~= '' then
+      table.insert(key, r)
+    end
+  end
+
+  for i = 1, table.getn(path) do
+    if path[i] == key[i] then
+      startingString = utils.concatStrings({startingString, '/', key[i]})
+      if (exists(red, startingString)) == 0 then
+        set(red, startingString, '')
+      end
+    else
+      startingString = utils.concatStrings({startingString, '/.*'})
+      if (exists(red,startingString) == 0) then
+        set(red, startingString, '')
+      end
+    end
+  end
+  set(red, startingString, resourceKey)
 end
 
 function _M.lockSnapshot(red, snapshotId)
   red:set(utils.concatStrings({'lock:snapshots:', snapshotId}), 'true')
   red:expire(utils.concatStrings({'lock:snapshots:', snapshotId}), 60)
 end
+
 -- LRU Caching methods
 
 function exists(red, key, snapshotId)
@@ -554,14 +646,20 @@
       return 1
     end
   -- if it isn't in the cache, try and load it in there
+    if red == nil then
+      red = _M.init()
+    end
     local result = red:get(key)
     if result ~= ngx.null then
       c:set(key, result, CACHE_TTL)
-      return 1
+      return 1, red
     end
     return 0
   else
-    return red:exists(key)
+    if red == nil then
+      red = _M.init()
+    end
+    return red:exists(key), red
   end
 end
 
@@ -571,11 +669,17 @@
     if cached ~= nil then
       return cached
     else
+      if red == nil then
+        red = _M.init()
+      end
       local result = red:get(key)
       c:set(key, result, CACHE_TTL)
-      return result
+      return result, red
     end
   else
+    if red == nil then
+      red = _M.init()
+    end
     return red:get(key)
   end
 end
@@ -588,20 +692,29 @@
       if cached ~= nil then
          return cached
       else
+        if red == nil then
+          red = _M.init()
+        end
         local result = red:hget(key, id)
         cachedmap:set(id, result, CACHE_TTL)
         c:set(key, cachedmap, CACHE_TTL)
-        return result
+        return result, red
       end
     else
+      if red == nil then
+        red = _M.init()
+      end
       local result = red:hget(key, id)
       local newcache = lrucache.new(CACHE_SIZE)
       newcache:set(id, result, CACHE_TTL)
       c:set(key, newcache, CACHE_TTL)
-      return result
+      return result, red
     end
   else
-    return red:hget(key, id)
+    if red == nil then
+      red = _M.init()
+    end
+    return red:hget(key, id), red
   end
 end
 
diff --git a/scripts/lua/lib/utils.lua b/scripts/lua/lib/utils.lua
index 5a55710..d99192a 100644
--- a/scripts/lua/lib/utils.lua
+++ b/scripts/lua/lib/utils.lua
@@ -110,15 +110,15 @@
 --- Takes a string and performs a SHA256 hash on its input
 -- @param str the string to input into the hash function
 -- @returns a hashed string
-function _Utils.hash(str) 
+function _Utils.hash(str)
   local resty_sha256 = require "resty.sha256"
-  local resty_str = require "resty.string" 
+  local resty_str = require "resty.string"
 
-  local sha256 = resty_sha256:new() 
+  local sha256 = resty_sha256:new()
   sha256:update(str)
   local digest = sha256:final()
   return resty_str.to_hex(digest)
-end 
+end
 
 return _Utils
 
diff --git a/scripts/lua/oauth/facebook.lua b/scripts/lua/oauth/facebook.lua
index d1a5a81..2889f3f 100644
--- a/scripts/lua/oauth/facebook.lua
+++ b/scripts/lua/oauth/facebook.lua
@@ -21,26 +21,26 @@
 local cjson = require 'cjson'
 local utils = require "lib/utils"
 
-local _M = {} 
+local _M = {}
 function _M.process(dataStore, token)
 
   local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_")
- 
+
   local facebookAppToken = ngx.var[headerName]
   if facebookAppToken == nil then
     request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header')
     return nil
   end
 
-  local result = dataStore:getOAuthToken('facebook', token) 
+  local result = dataStore:getOAuthToken('facebook', token)
   if result ~= ngx.null then
     return cjson.decode(result)
   end
   result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken}))
-  if result ~= ngx.null then 
-    return cjson.decode(result) 
+  if result ~= ngx.null then
+    return cjson.decode(result)
   end
- 
+
    return exchangeOAuthToken(dataStore, token, facebookAppToken)
 end
 
@@ -48,7 +48,7 @@
   local http = require 'resty.http'
   local request = require "lib/request"
   local httpc = http.new()
- 
+
   local request_options = {
     headers = {
       ['Accept'] = 'application/json'
diff --git a/scripts/lua/oauth/github.lua b/scripts/lua/oauth/github.lua
index 2335477..4b8f02c 100644
--- a/scripts/lua/oauth/github.lua
+++ b/scripts/lua/oauth/github.lua
@@ -21,18 +21,17 @@
 -- A Proxy for Github OAuth API
 local cjson = require "cjson"
 local http = require "resty.http"
-local redis = require "lib/redis"
 local request = require "lib/request"
 local cjson = require "cjson"
 local utils = require "lib/utils"
-local _M = {} 
+local _M = {}
 
 function _M.process(dataStore, token)
-  local result = dataStore:getOAuthToken('github', token) 
-  if result ~= ngx.null then 
+  local result = dataStore:getOAuthToken('github', token)
+  if result ~= ngx.null then
     return cjson.decode(result)
-  end 
- 
+  end
+
   local request_options = {
     headers = {
       ["Accept"] = "application/json"
@@ -52,7 +51,7 @@
     request.err(500, 'OAuth provider error.')
     return
   end
- 
+
   local json_resp = cjson.decode(res.body)
   if json_resp.id == nil then
     return nil
@@ -62,10 +61,10 @@
     return nil
   end
 
-  dataStore:saveOAuthToken('github', token) 
+  dataStore:saveOAuthToken('github', token, cjson.encode(json_resp))
   -- convert Github's response
   -- Read more about the fields at: https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo
   return json_resp
 end
 
-return _M 
+return _M
diff --git a/scripts/lua/oauth/mock.lua b/scripts/lua/oauth/mock.lua
index 2b88511..9de4a4d 100644
--- a/scripts/lua/oauth/mock.lua
+++ b/scripts/lua/oauth/mock.lua
@@ -20,7 +20,7 @@
 --   DEALINGS IN THE SOFTWARE.
 ---
 -- A fake oauth provider for testing
-local cjson = require "cjson"  
+local cjson = require "cjson"
 local _M = {}
 function _M.process (red, token)
   local result
diff --git a/scripts/lua/policies/rateLimit.lua b/scripts/lua/policies/rateLimit.lua
index 082824a..e59557d 100644
--- a/scripts/lua/policies/rateLimit.lua
+++ b/scripts/lua/policies/rateLimit.lua
@@ -26,7 +26,7 @@
 local _M = {}
 
 --- Limit a resource/api/tenant, based on the passed in rateLimit object
--- @param red redis client instance
+-- @param dataStore the datastore object
 -- @param obj rateLimit object containing interval, rate, scope, subscription fields
 -- @param apiKey optional api key to use if subscription is set to true
 function limit(dataStore, obj, apiKey)
diff --git a/scripts/lua/policies/security.lua b/scripts/lua/policies/security.lua
index 0761f34..afc714d 100644
--- a/scripts/lua/policies/security.lua
+++ b/scripts/lua/policies/security.lua
@@ -27,6 +27,7 @@
 local request = require "lib/request"
 local utils = require "lib/utils"
 --- Allow or block a request by calling a loaded security policy
+-- @param dataStore the datastore object
 -- @param securityObj an object out of the security array in a given tenant / api / resource
 function process(dataStore, securityObj)
   local ok, result = pcall(require, utils.concatStrings({'policies/security/', securityObj.type}))
diff --git a/scripts/lua/policies/security/apiKey.lua b/scripts/lua/policies/security/apiKey.lua
index f462abb..d834dfa 100644
--- a/scripts/lua/policies/security/apiKey.lua
+++ b/scripts/lua/policies/security/apiKey.lua
@@ -33,8 +33,8 @@
 
 local _M = {}
 
---- Validate that the given subscription is in redis
--- @param red a redis instance to query against
+--- Validate that the given subscription is in the datastore
+-- @param dataStore the datastore object
 -- @param tenant the namespace
 -- @param gatewayPath the gateway path to use, if scope is resource
 -- @param apiId api Id to use, if scope is api
@@ -64,7 +64,7 @@
 end
 
 --- Process the security object
--- @param red a redis instance to query against
+-- @param dataStore the datastore object
 -- @param securityObj security object from nginx conf file
 -- @param hashFunction a function that will be called to hash the string
 -- @return apiKey api key for the subscription
diff --git a/scripts/lua/policies/security/clientSecret.lua b/scripts/lua/policies/security/clientSecret.lua
index af865af..8fc221c 100644
--- a/scripts/lua/policies/security/clientSecret.lua
+++ b/scripts/lua/policies/security/clientSecret.lua
@@ -33,6 +33,7 @@
 
 --- Process function main entry point for the security block.
 --  Takes 2 headers and decides if the request should be allowed based on a hashed secret key
+--@param dataStore the datastore object
 --@param securityObj the security object loaded from nginx.conf
 --@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret
 function process(dataStore, securityObj)
@@ -41,7 +42,7 @@
 
 --- In order to properly test this functionallity, I use this function to do all of the business logic with injected dependencies
 -- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
--- @param red the redis instance to perform the lookup on
+-- @param dataStore the datastore object
 -- @param securityObj the security configuration for the tenant/resource/api we are verifying
 -- @param hashFunction the function used to perform the hash of the api secret
 function processWithHashFunction(dataStore, securityObj, hashFunction)
@@ -93,7 +94,8 @@
   return result
 end
 
---- Validate that the subscription exists in redis
+--- Validate that the subscription exists in the dataStore
+-- @param dataStore the datastore object
 -- @param tenant the tenantId we are checking for
 -- @param gatewayPath the possible resource we are checking for
 -- @param apiId if we are checking for an api
diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua
index eebf536..778e7b4 100644
--- a/scripts/lua/policies/security/oauth2.lua
+++ b/scripts/lua/policies/security/oauth2.lua
@@ -63,6 +63,7 @@
 end
 
 --- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf
+-- @param dataStore the datastore object
 -- @param token the accessToken passed in the authorization header of the routing request
 -- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook
 -- @return the json object recieved from exchanging tokens with the provider
diff --git a/scripts/lua/routing.lua b/scripts/lua/routing.lua
index 09003c0..7bdf7d1 100644
--- a/scripts/lua/routing.lua
+++ b/scripts/lua/routing.lua
@@ -31,7 +31,13 @@
 local rateLimit = require "policies/rateLimit"
 local backendRouting = require "policies/backendRouting"
 local cors = require "cors"
+local OPTIMIZE = os.getenv("OPTIMIZE")
 
+if OPTIMIZE ~= nil then
+  OPTIMIZE = tonumber(OPTIMIZE)
+else
+  OPTIMIZE = 0
+end
 
 local SNAPSHOTTING = os.getenv('SNAPSHOTTING')
 
@@ -51,9 +57,7 @@
   if ngx.req.get_headers()["x-debug-mode"] == "true" then
     setRequestLogs()
   end
-  local resourceKeys = dataStore:getAllResources(tenantId)
-  print(cjson.encode(resourceKeys))
-  local redisKey = _M.findResource(resourceKeys, tenantId, gatewayPath)
+  local redisKey = _M.findResource(dataStore, tenantId, gatewayPath)
   if redisKey == nil then
     request.err(404, 'Not found.')
   end
@@ -101,10 +105,10 @@
 end
 
 --- Find the correct redis key based on the path that's passed in
--- @param resourceKeys list of resourceKeys to search through
+-- @param dataStore the datastore object
 -- @param tenant tenantId
 -- @param path path to look for
-function _M.findResource(resourceKeys, tenant, path)
+function _M.findResource(dataStore, tenant, path)
   -- Check for exact match
   local redisKey = utils.concatStrings({"resources:", tenant, ":", path})
   local cfRedisKey
@@ -117,6 +121,33 @@
       ngx.var.analyticsUri = utils.concatStrings({ngx.var.analyticsUri, '?', u.query})
     end
   end
+
+  local result
+  if OPTIMIZE > 0 then
+    result = dataStore:optimizedLookup(tenant, path)
+  end
+  if result ~= nil then
+    ngx.var.gatewayPath = result:gsub(utils.concatStrings({'resources:', tenant, ':'}), '')
+    return result
+  end
+
+  local resourceKeys = dataStore:getAllResources(tenant)
+  local result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
+
+  if OPTIMIZE > 0 and result ~= nil then
+    dataStore:optimizeLookup(tenant, result, path)
+  end
+
+  return result
+end
+
+--- Perform a linear lookup of the api based on the apis in a tenant
+-- @param resourceKeys all of the resources under a given tenant
+-- @param tenant the tenant we are looking up for
+-- @param path the path used to call the api gateway
+-- @param redisKey a guess for a redis key based on the tenant id and path
+-- @param cfRedisKey a redis key that will exist if cloud foundry routing logic is used
+function _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
   for _, key in pairs(resourceKeys) do
     if key == redisKey or key == cfRedisKey then
       local res = {string.match(key, "([^:]+):([^:]+):([^:]+)")}
@@ -163,6 +194,7 @@
     redisKey = string.sub(redisKey, 1, index - 1)
   end
   return nil
+
 end
 
 --- Check redis if resourceKey matches path parameters
diff --git a/tests/scripts/lua/routing.lua b/tests/scripts/lua/routing.lua
index 2447d51..cb4c759 100644
--- a/tests/scripts/lua/routing.lua
+++ b/tests/scripts/lua/routing.lua
@@ -22,6 +22,8 @@
 local fakeredis = require 'fakeredis'
 local redis = require 'lib/redis'
 local routing = require 'routing'
+local cjson = require 'cjson'
+
 
 describe('Testing routing module', function()
   before_each(function()
@@ -39,6 +41,7 @@
     local field = 'resources'
     for _, key in pairs(keys) do
       red:hset(key, field, redis.generateResourceObj(operations, nil))
+      red:sadd('resources:guest:__index__', key)
     end
   end)
 
@@ -46,7 +49,8 @@
     local expected = 'resources:guest:bp1/test/hello'
     local tenant = 'guest'
     local path = 'bp1/test/hello'
-    local actual = routing.findResource(keys, tenant, path)
+    local ds = require 'lib/dataStore'
+    local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
     assert.are.same(expected, actual)
     expected = 'bp1/test/hello'
     actual = ngx.var.gatewayPath
@@ -57,8 +61,8 @@
     local expected = nil
     local tenant = 'guest'
     local path = 'bp1/bad/path'
-    local actual = routing.findResource(keys, tenant, path)
-    print(actual)
+    local ds = require 'lib/dataStore'
+    local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
     assert.are.same(expected, actual)
   end)
 
@@ -66,7 +70,8 @@
     local expected = 'resources:guest:nobp'
     local tenant = 'guest'
     local path = 'nobp'
-    local actual = routing.findResource(keys, tenant, path)
+    local ds = require 'lib/dataStore'
+    local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
     assert.are.same(expected, actual)
     expected = 'nobp'
     actual = ngx.var.gatewayPath
@@ -77,7 +82,8 @@
     local expected = 'resources:guest:noresource/'
     local tenant = 'guest'
     local path = 'noresource/'
-    local actual = routing.findResource(keys, tenant, path)
+    local ds = require 'lib/dataStore'
+    local actual = routing.findResource(ds.initWithDriver(red), tenant, path)
     assert.are.same(expected, actual)
     expected = 'noresource/'
     actual = ngx.var.gatewayPath
@@ -131,8 +137,9 @@
     local dataStore = ds.initWithDriver(red)
     dataStore:setSnapshotId('test')
     local result = dataStore:getAllResources('test')[1]
+    print ('etst')
     assert.are.same(result, 'resources:test:v1/test')
     local routing = require  'routing'
-    local result = routing.findResource(dataStore:getAllResources('test'), 'test', 'v1/test')
+    local result = routing.findResource(dataStore, dataStore:getAllResources('test'), 'test', 'v1/test')
   end)
 end)