fallback.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # Copyright 2012 Pinterest.com
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """
  15. A client for falling back to older memcached servers when performing reads.
  16. It is sometimes necessary to deploy memcached on new servers, or with a
  17. different configuration. In these cases, it is undesirable to start up an
  18. empty memcached server and point traffic to it, since the cache will be cold,
  19. and the backing store will have a large increase in traffic.
  20. This class attempts to solve that problem by providing an interface identical
  21. to the Client interface, but which can fall back to older memcached servers
  22. when reads to the primary server fail. The approach for upgrading memcached
  23. servers or configuration then becomes:
  24. 1. Deploy a new host (or fleet) with memcached, possibly with a new
  25. configuration.
  26. 2. From your application servers, use FallbackClient to write and read from
  27. the new cluster, and to read from the old cluster when there is a miss in
  28. the new cluster.
  29. 3. Wait until the new cache is warm enough to support the load.
  30. 4. Switch from FallbackClient to a regular Client library for doing all
  31. reads and writes to the new cluster.
  32. 5. Take down the old cluster.
  33. Best Practices:
  34. ---------------
  35. - Make sure that the old client has "ignore_exc" set to True, so that it
  36. treats failures like cache misses. That will allow you to take down the
  37. old cluster before you switch away from FallbackClient.
  38. """
  39. class FallbackClient(object):
  40. def __init__(self, caches):
  41. assert len(caches) > 0
  42. self.caches = caches
  43. def close(self):
  44. "Close each of the memcached clients"
  45. for cache in self.caches:
  46. cache.close()
  47. def set(self, key, value, expire=0, noreply=True):
  48. self.caches[0].set(key, value, expire, noreply)
  49. def add(self, key, value, expire=0, noreply=True):
  50. self.caches[0].add(key, value, expire, noreply)
  51. def replace(self, key, value, expire=0, noreply=True):
  52. self.caches[0].replace(key, value, expire, noreply)
  53. def append(self, key, value, expire=0, noreply=True):
  54. self.caches[0].append(key, value, expire, noreply)
  55. def prepend(self, key, value, expire=0, noreply=True):
  56. self.caches[0].prepend(key, value, expire, noreply)
  57. def cas(self, key, value, cas, expire=0, noreply=True):
  58. self.caches[0].cas(key, value, cas, expire, noreply)
  59. def get(self, key):
  60. for cache in self.caches:
  61. result = cache.get(key)
  62. if result is not None:
  63. return result
  64. return None
  65. def get_many(self, keys):
  66. for cache in self.caches:
  67. result = cache.get_many(keys)
  68. if result:
  69. return result
  70. return []
  71. def gets(self, key):
  72. for cache in self.caches:
  73. result = cache.gets(key)
  74. if result is not None:
  75. return result
  76. return None
  77. def gets_many(self, keys):
  78. for cache in self.caches:
  79. result = cache.gets_many(keys)
  80. if result:
  81. return result
  82. return []
  83. def delete(self, key, noreply=True):
  84. self.caches[0].delete(key, noreply)
  85. def incr(self, key, value, noreply=True):
  86. self.caches[0].incr(key, value, noreply)
  87. def decr(self, key, value, noreply=True):
  88. self.caches[0].decr(key, value, noreply)
  89. def touch(self, key, expire=0, noreply=True):
  90. self.caches[0].touch(key, expire, noreply)
  91. def stats(self):
  92. # TODO: ??
  93. pass
  94. def flush_all(self, delay=0, noreply=True):
  95. self.caches[0].flush_all(delay, noreply)
  96. def quit(self):
  97. # TODO: ??
  98. pass