PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/astyanax-contrib/src/main/java/com/netflix/astyanax/contrib/dualwrites/DualWritesKeyspace.java

http://github.com/Netflix/astyanax
Java | 529 lines | 422 code | 79 blank | 28 comment | 21 complexity | b5579fbc98bcf688273225185c6badf0 MD5 | raw file
  1. /**
  2. * Copyright 2013 Netflix, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.netflix.astyanax.contrib.dualwrites;
  17. import java.util.Collections;
  18. import java.util.List;
  19. import java.util.Map;
  20. import java.util.Properties;
  21. import java.util.concurrent.atomic.AtomicBoolean;
  22. import java.util.concurrent.atomic.AtomicReference;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;
  25. import com.google.common.util.concurrent.ListenableFuture;
  26. import com.netflix.astyanax.AstyanaxConfiguration;
  27. import com.netflix.astyanax.ColumnMutation;
  28. import com.netflix.astyanax.Execution;
  29. import com.netflix.astyanax.Keyspace;
  30. import com.netflix.astyanax.MutationBatch;
  31. import com.netflix.astyanax.SerializerPackage;
  32. import com.netflix.astyanax.connectionpool.ConnectionPool;
  33. import com.netflix.astyanax.connectionpool.Operation;
  34. import com.netflix.astyanax.connectionpool.OperationResult;
  35. import com.netflix.astyanax.connectionpool.TokenRange;
  36. import com.netflix.astyanax.connectionpool.exceptions.ConnectionException;
  37. import com.netflix.astyanax.connectionpool.exceptions.OperationException;
  38. import com.netflix.astyanax.cql.CqlStatement;
  39. import com.netflix.astyanax.ddl.KeyspaceDefinition;
  40. import com.netflix.astyanax.ddl.SchemaChangeResult;
  41. import com.netflix.astyanax.model.ColumnFamily;
  42. import com.netflix.astyanax.partitioner.Partitioner;
  43. import com.netflix.astyanax.query.ColumnFamilyQuery;
  44. import com.netflix.astyanax.retry.RetryPolicy;
  45. import com.netflix.astyanax.serializers.UnknownComparatorException;
  46. /**
  47. * Main class that orchistrates all the dual writes. It wraps the 2 keyspaces (source and destination)
  48. * and appropriately forwards reads and writes to it as dictated by updates using the {@link DualWritesUpdateListener}
  49. *
  50. * Note that if dual writes are enabled then the writes are sent to the {@link DualWritesMutationBatch} or {@link DualWritesColumnMutation}
  51. * classes appropriately.
  52. *
  53. * The reads are always served from the primary data source.
  54. *
  55. * @author poberai
  56. *
  57. */
  58. public class DualWritesKeyspace implements Keyspace, DualWritesUpdateListener {
  59. private static final Logger Logger = LoggerFactory.getLogger(DualWritesKeyspace.class);
  60. private final AtomicReference<KeyspacePair> ksPair = new AtomicReference<KeyspacePair>(null);
  61. private final AtomicBoolean dualWritesEnabled = new AtomicBoolean(false);
  62. private final DualWritesStrategy executionStrategy;
  63. public DualWritesKeyspace(DualKeyspaceMetadata dualKeyspaceSetup,
  64. Keyspace primaryKS, Keyspace secondaryKS,
  65. DualWritesStrategy execStrategy) {
  66. ksPair.set(new KeyspacePair(dualKeyspaceSetup, primaryKS, secondaryKS));
  67. executionStrategy = execStrategy;
  68. }
  69. private Keyspace getPrimaryKS() {
  70. return ksPair.get().getPrimaryKS();
  71. }
  72. public DualKeyspaceMetadata getDualKeyspaceMetadata() {
  73. return ksPair.get().getDualKSMetadata();
  74. }
  75. @Override
  76. public AstyanaxConfiguration getConfig() {
  77. return getPrimaryKS().getConfig();
  78. }
  79. @Override
  80. public String getKeyspaceName() {
  81. return getPrimaryKS().getKeyspaceName();
  82. }
  83. @Override
  84. public Partitioner getPartitioner() throws ConnectionException {
  85. return getPrimaryKS().getPartitioner();
  86. }
  87. @Override
  88. public String describePartitioner() throws ConnectionException {
  89. return getPrimaryKS().describePartitioner();
  90. }
  91. @Override
  92. public List<TokenRange> describeRing() throws ConnectionException {
  93. return getPrimaryKS().describeRing();
  94. }
  95. @Override
  96. public List<TokenRange> describeRing(String dc) throws ConnectionException {
  97. return getPrimaryKS().describeRing(dc);
  98. }
  99. @Override
  100. public List<TokenRange> describeRing(String dc, String rack) throws ConnectionException {
  101. return getPrimaryKS().describeRing(dc, rack);
  102. }
  103. @Override
  104. public List<TokenRange> describeRing(boolean cached) throws ConnectionException {
  105. return getPrimaryKS().describeRing(cached);
  106. }
  107. @Override
  108. public KeyspaceDefinition describeKeyspace() throws ConnectionException {
  109. return getPrimaryKS().describeKeyspace();
  110. }
  111. @Override
  112. public Properties getKeyspaceProperties() throws ConnectionException {
  113. return getPrimaryKS().getKeyspaceProperties();
  114. }
  115. @Override
  116. public Properties getColumnFamilyProperties(String columnFamily) throws ConnectionException {
  117. return getPrimaryKS().getColumnFamilyProperties(columnFamily);
  118. }
  119. @Override
  120. public SerializerPackage getSerializerPackage(String cfName, boolean ignoreErrors) throws ConnectionException, UnknownComparatorException {
  121. return getPrimaryKS().getSerializerPackage(cfName, ignoreErrors);
  122. }
  123. @Override
  124. public MutationBatch prepareMutationBatch() {
  125. if (dualWritesEnabled.get()) {
  126. KeyspacePair pair = ksPair.get();
  127. return new DualWritesMutationBatch(
  128. pair.getDualKSMetadata(),
  129. pair.getPrimaryKS().prepareMutationBatch(),
  130. pair.getSecondaryKS().prepareMutationBatch(),
  131. executionStrategy);
  132. } else {
  133. return getPrimaryKS().prepareMutationBatch();
  134. }
  135. }
  136. @Override
  137. public <K, C> ColumnFamilyQuery<K, C> prepareQuery(ColumnFamily<K, C> cf) {
  138. return getPrimaryKS().prepareQuery(cf);
  139. }
  140. @Override
  141. public <K, C> ColumnMutation prepareColumnMutation(ColumnFamily<K, C> columnFamily, K rowKey, C column) {
  142. KeyspacePair pair = ksPair.get();
  143. if (dualWritesEnabled.get()) {
  144. WriteMetadata md = new WriteMetadata(pair.getDualKSMetadata(), columnFamily.getName(), rowKey.toString());
  145. return new DualWritesColumnMutation(md,
  146. pair.getPrimaryKS() .prepareColumnMutation(columnFamily, rowKey, column),
  147. pair.getSecondaryKS().prepareColumnMutation(columnFamily, rowKey, column),
  148. executionStrategy);
  149. } else {
  150. return pair.getPrimaryKS().prepareColumnMutation(columnFamily, rowKey, column);
  151. }
  152. }
  153. @Override
  154. public <K, C> OperationResult<Void> truncateColumnFamily(final ColumnFamily<K, C> columnFamily) throws OperationException, ConnectionException {
  155. return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
  156. @Override
  157. public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
  158. return ks.truncateColumnFamily(columnFamily);
  159. }
  160. });
  161. }
  162. @Override
  163. public OperationResult<Void> truncateColumnFamily(final String columnFamily) throws ConnectionException {
  164. return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
  165. @Override
  166. public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
  167. return ks.truncateColumnFamily(columnFamily);
  168. }
  169. });
  170. }
  171. @Override
  172. public OperationResult<Void> testOperation(final Operation<?, ?> operation) throws ConnectionException {
  173. return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
  174. @Override
  175. public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
  176. return ks.testOperation(operation);
  177. }
  178. });
  179. }
  180. @Override
  181. public OperationResult<Void> testOperation(final Operation<?, ?> operation, RetryPolicy retry) throws ConnectionException {
  182. return execDualKeyspaceOperation(new KeyspaceOperation<Void>() {
  183. @Override
  184. public OperationResult<Void> exec(Keyspace ks) throws ConnectionException {
  185. return ks.testOperation(operation);
  186. }
  187. });
  188. }
  189. @Override
  190. public <K, C> OperationResult<SchemaChangeResult> createColumnFamily(final ColumnFamily<K, C> columnFamily, final Map<String, Object> options) throws ConnectionException {
  191. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  192. @Override
  193. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  194. return ks.createColumnFamily(columnFamily, options);
  195. }
  196. });
  197. }
  198. @Override
  199. public OperationResult<SchemaChangeResult> createColumnFamily(final Properties props) throws ConnectionException {
  200. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  201. @Override
  202. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  203. return ks.createColumnFamily(props);
  204. }
  205. });
  206. }
  207. @Override
  208. public OperationResult<SchemaChangeResult> createColumnFamily(final Map<String, Object> options) throws ConnectionException {
  209. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  210. @Override
  211. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  212. return ks.createColumnFamily(options);
  213. }
  214. });
  215. }
  216. @Override
  217. public <K, C> OperationResult<SchemaChangeResult> updateColumnFamily(final ColumnFamily<K, C> columnFamily, final Map<String, Object> options) throws ConnectionException {
  218. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  219. @Override
  220. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  221. return ks.updateColumnFamily(columnFamily, options);
  222. }
  223. });
  224. }
  225. @Override
  226. public OperationResult<SchemaChangeResult> updateColumnFamily(final Properties props) throws ConnectionException {
  227. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  228. @Override
  229. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  230. return ks.updateColumnFamily(props);
  231. }
  232. });
  233. }
  234. @Override
  235. public OperationResult<SchemaChangeResult> updateColumnFamily(final Map<String, Object> options) throws ConnectionException {
  236. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  237. @Override
  238. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  239. return ks.updateColumnFamily(options);
  240. }
  241. });
  242. }
  243. @Override
  244. public OperationResult<SchemaChangeResult> dropColumnFamily(final String columnFamilyName) throws ConnectionException {
  245. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  246. @Override
  247. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  248. return ks.dropColumnFamily(columnFamilyName);
  249. }
  250. });
  251. }
  252. @Override
  253. public <K, C> OperationResult<SchemaChangeResult> dropColumnFamily(final ColumnFamily<K, C> columnFamily) throws ConnectionException {
  254. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  255. @Override
  256. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  257. return ks.dropColumnFamily(columnFamily);
  258. }
  259. });
  260. }
  261. @Override
  262. public OperationResult<SchemaChangeResult> createKeyspace(final Map<String, Object> options) throws ConnectionException {
  263. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  264. @Override
  265. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  266. return ks.createKeyspace(options);
  267. }
  268. });
  269. }
  270. @Override
  271. public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Map<String, Object> options) throws ConnectionException {
  272. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  273. @Override
  274. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  275. return ks.createKeyspaceIfNotExists(options);
  276. }
  277. });
  278. }
  279. @Override
  280. public OperationResult<SchemaChangeResult> createKeyspace(final Properties properties) throws ConnectionException {
  281. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  282. @Override
  283. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  284. return ks.createKeyspace(properties);
  285. }
  286. });
  287. }
  288. @Override
  289. public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Properties properties) throws ConnectionException {
  290. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  291. @Override
  292. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  293. return ks.createKeyspaceIfNotExists(properties);
  294. }
  295. });
  296. }
  297. @Override
  298. public OperationResult<SchemaChangeResult> createKeyspace(final Map<String, Object> options, final Map<ColumnFamily, Map<String, Object>> cfs) throws ConnectionException {
  299. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  300. @Override
  301. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  302. return ks.createKeyspace(options, cfs);
  303. }
  304. });
  305. }
  306. @Override
  307. public OperationResult<SchemaChangeResult> createKeyspaceIfNotExists(final Map<String, Object> options, final Map<ColumnFamily, Map<String, Object>> cfs) throws ConnectionException {
  308. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  309. @Override
  310. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  311. return ks.createKeyspaceIfNotExists(options, cfs);
  312. }
  313. });
  314. }
  315. @Override
  316. public OperationResult<SchemaChangeResult> updateKeyspace(final Map<String, Object> options) throws ConnectionException {
  317. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  318. @Override
  319. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  320. return ks.updateKeyspace(options);
  321. }
  322. });
  323. }
  324. @Override
  325. public OperationResult<SchemaChangeResult> updateKeyspace(final Properties props) throws ConnectionException {
  326. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  327. @Override
  328. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  329. return ks.updateKeyspace(props);
  330. }
  331. });
  332. }
  333. @Override
  334. public OperationResult<SchemaChangeResult> dropKeyspace() throws ConnectionException {
  335. return execDualKeyspaceOperation(new KeyspaceOperation<SchemaChangeResult>() {
  336. @Override
  337. public OperationResult<SchemaChangeResult> exec(Keyspace ks) throws ConnectionException {
  338. return ks.dropKeyspace();
  339. }
  340. });
  341. }
  342. @Override
  343. public Map<String, List<String>> describeSchemaVersions() throws ConnectionException {
  344. return getPrimaryKS().describeSchemaVersions();
  345. }
  346. @Override
  347. public CqlStatement prepareCqlStatement() {
  348. KeyspacePair pair = ksPair.get();
  349. CqlStatement primaryStmt = pair.getPrimaryKS().prepareCqlStatement();
  350. CqlStatement secondaryStmt = pair.getSecondaryKS().prepareCqlStatement();
  351. return new DualWritesCqlStatement(primaryStmt, secondaryStmt, executionStrategy, pair.getDualKSMetadata());
  352. }
  353. @Override
  354. public ConnectionPool<?> getConnectionPool() throws ConnectionException {
  355. return getPrimaryKS().getConnectionPool();
  356. }
  357. private class KeyspacePair {
  358. private final DualKeyspaceMetadata dualKeyspaceMetadata;
  359. private final Keyspace ksPrimary;
  360. private final Keyspace ksSecondary;
  361. private KeyspacePair(final DualKeyspaceMetadata dualKeyspaceSetup, final Keyspace pKS, final Keyspace sKS) {
  362. dualKeyspaceMetadata = dualKeyspaceSetup;
  363. ksPrimary = pKS;
  364. ksSecondary = sKS;
  365. }
  366. private Keyspace getPrimaryKS() {
  367. return ksPrimary;
  368. }
  369. private Keyspace getSecondaryKS() {
  370. return ksSecondary;
  371. }
  372. private DualKeyspaceMetadata getDualKSMetadata() {
  373. return dualKeyspaceMetadata;
  374. }
  375. @Override
  376. public int hashCode() {
  377. final int prime = 31;
  378. int result = 1;
  379. result = prime * result + ((ksPrimary == null) ? 0 : ksPrimary.hashCode());
  380. result = prime * result + ((ksSecondary == null) ? 0 : ksSecondary.hashCode());
  381. result = prime * result + ((dualKeyspaceMetadata == null) ? 0 : dualKeyspaceMetadata.hashCode());
  382. return result;
  383. }
  384. @Override
  385. public boolean equals(Object obj) {
  386. if (this == obj) return true;
  387. if (obj == null) return false;
  388. if (getClass() != obj.getClass()) return false;
  389. KeyspacePair other = (KeyspacePair) obj;
  390. boolean equals = true;
  391. equals &= (ksPrimary == null) ? (other.ksPrimary == null) : (ksPrimary.equals(other.ksPrimary));
  392. equals &= (ksSecondary == null) ? (other.ksSecondary == null) : (ksSecondary.equals(other.ksSecondary));
  393. equals &= (dualKeyspaceMetadata == null) ? (other.dualKeyspaceMetadata == null) : (dualKeyspaceMetadata.equals(other.dualKeyspaceMetadata));
  394. return equals;
  395. }
  396. }
  397. @Override
  398. public void dualWritesEnabled() {
  399. Logger.info("ENABLING dual writes for dual keyspace setup: " + ksPair.get().getDualKSMetadata());
  400. dualWritesEnabled.set(true);
  401. }
  402. @Override
  403. public void dualWritesDisabled() {
  404. Logger.info("DISABLING dual writes for dual keyspace setup: " + ksPair.get().getDualKSMetadata());
  405. dualWritesEnabled.set(false);
  406. }
  407. @Override
  408. public void flipPrimaryAndSecondary() {
  409. // Check that the expected state is actually reverse of what the destination state should be
  410. KeyspacePair currentPair = ksPair.get();
  411. DualKeyspaceMetadata currentKeyspaceSetup = currentPair.getDualKSMetadata();
  412. DualKeyspaceMetadata newDualKeyspaceSetup =
  413. new DualKeyspaceMetadata(currentKeyspaceSetup.getSecondaryCluster(), currentKeyspaceSetup.getSecondaryKeyspaceName(),
  414. currentKeyspaceSetup.getPrimaryCluster(), currentKeyspaceSetup.getPrimaryKeyspaceName());
  415. KeyspacePair newPair =
  416. new KeyspacePair(newDualKeyspaceSetup, currentPair.getSecondaryKS(), currentPair.getPrimaryKS());
  417. boolean success = ksPair.compareAndSet(currentPair, newPair);
  418. if (success) {
  419. Logger.info("Successfully flipped to new dual keyspace setup" + ksPair.get().getDualKSMetadata());
  420. } else {
  421. Logger.info("Could not flip keyspace pair: " + currentPair + " to new pair: " + newPair);
  422. }
  423. }
  424. private abstract class SimpleSyncExec<R> implements Execution<R> {
  425. @Override
  426. public ListenableFuture<OperationResult<R>> executeAsync() throws ConnectionException {
  427. throw new RuntimeException("executeAsync not implemented for SimpleSyncExec");
  428. }
  429. }
  430. private interface KeyspaceOperation<R> {
  431. OperationResult<R> exec(Keyspace ks) throws ConnectionException;
  432. }
  433. private <R> OperationResult<R> execDualKeyspaceOperation(final KeyspaceOperation<R> ksOperation) throws ConnectionException {
  434. final KeyspacePair pair = ksPair.get();
  435. final Execution<R> exec1 = new SimpleSyncExec<R>() {
  436. @Override
  437. public OperationResult<R> execute() throws ConnectionException {
  438. return ksOperation.exec(pair.getPrimaryKS());
  439. }
  440. };
  441. final Execution<R> exec2 = new SimpleSyncExec<R>() {
  442. @Override
  443. public OperationResult<R> execute() throws ConnectionException {
  444. return ksOperation.exec(pair.getSecondaryKS());
  445. }
  446. };
  447. WriteMetadata writeMd = new WriteMetadata(pair.getDualKSMetadata(), null, null);
  448. return executionStrategy.wrapExecutions(exec1, exec2, Collections.singletonList(writeMd)).execute();
  449. }
  450. }