/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cassandra.db.view; import java.util.*; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import com.google.common.util.concurrent.Striped; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.ViewMetadata; import org.apache.cassandra.db.*; import org.apache.cassandra.db.partitions.*; import org.apache.cassandra.repair.SystemDistributedKeyspace; import org.apache.cassandra.service.StorageService; /** * Manages {@link View}'s for a single {@link ColumnFamilyStore}. All of the views for that table are created when this * manager is initialized. * * The main purposes of the manager are to provide a single location for updates to be vetted to see whether they update * any views {@link #updatesAffectView(Collection, boolean)}, provide locks to prevent multiple * updates from creating incoherent updates in the view {@link #acquireLockFor(int)}, and * to affect change on the view. * * TODO: I think we can get rid of that class. For addition/removal of view by names, we could move it Keyspace. And we * not sure it's even worth keeping viewsByName as none of the related operation are performance sensitive so we could * find the view by iterating over the CFStore.viewManager directly. * For the lock, it could move to Keyspace too, but I don't remmenber why it has to be at the keyspace level and if it * can be at the table level, maybe that's where it should be. */ public class ViewManager { private static final Logger logger = LoggerFactory.getLogger(ViewManager.class); private static final Striped<Lock> LOCKS = Striped.lazyWeakLock(DatabaseDescriptor.getConcurrentViewWriters() * 1024); private static final boolean enableCoordinatorBatchlog = Boolean.getBoolean("cassandra.mv_enable_coordinator_batchlog"); private final ConcurrentMap<String, View> viewsByName = new ConcurrentHashMap<>(); private final ConcurrentMap<TableId, TableViews> viewsByBaseTable = new ConcurrentHashMap<>(); private final Keyspace keyspace; public ViewManager(Keyspace keyspace) { this.keyspace = keyspace; } public boolean updatesAffectView(Collection<? extends IMutation> mutations, boolean coordinatorBatchlog) { if (!enableCoordinatorBatchlog && coordinatorBatchlog) return false; for (IMutation mutation : mutations) { for (PartitionUpdate update : mutation.getPartitionUpdates()) { assert keyspace.getName().equals(update.metadata().keyspace); if (coordinatorBatchlog && keyspace.getReplicationStrategy().getReplicationFactor() == 1) continue; if (!forTable(update.metadata().id).updatedViews(update).isEmpty()) return true; } } return false; } private Iterable<View> allViews() { return viewsByName.values(); } public void reload() { Map<String, ViewMetadata> newViewsByName = new HashMap<>(); for (ViewMetadata definition : keyspace.getMetadata().views) { newViewsByName.put(definition.name, definition); } for (String viewName : viewsByName.keySet()) { if (!newViewsByName.containsKey(viewName)) removeView(viewName); } for (Map.Entry<String, ViewMetadata> entry : newViewsByName.entrySet()) { if (!viewsByName.containsKey(entry.getKey())) addView(entry.getValue()); } // Building views involves updating view build status in the system_distributed // keyspace and therefore it requires ring information. This check prevents builds // being submitted when Keyspaces are initialized during CassandraDaemon::setup as // that happens before StorageService & gossip are initialized. After SS has been // init'd we schedule builds for *all* views anyway, so this doesn't have any effect // on startup. It does mean however, that builds will not be triggered if gossip is // disabled via JMX or nodetool as that sets SS to an uninitialized state. if (!StorageService.instance.isInitialized()) { logger.info("Not submitting build tasks for views in keyspace {} as " + "storage service is not initialized", keyspace.getName()); return; } for (View view : allViews()) { view.build(); // We provide the new definition from the base metadata view.updateDefinition(newViewsByName.get(view.name)); } } public void addView(ViewMetadata definition) { View view = new View(definition, keyspace.getColumnFamilyStore(definition.baseTableId)); forTable(view.getDefinition().baseTableId).add(view); viewsByName.put(definition.name, view); } public void removeView(String name) { View view = viewsByName.remove(name); if (view == null) return; forTable(view.getDefinition().baseTableId).removeByName(name); SystemKeyspace.setViewRemoved(keyspace.getName(), view.name); SystemDistributedKeyspace.setViewRemoved(keyspace.getName(), view.name); } public void buildAllViews() { for (View view : allViews()) view.build(); } public TableViews forTable(TableId id) { TableViews views = viewsByBaseTable.get(id); if (views == null) { views = new TableViews(id); TableViews previous = viewsByBaseTable.putIfAbsent(id, views); if (previous != null) views = previous; } return views; } public static Lock acquireLockFor(int keyAndCfidHash) { Lock lock = LOCKS.get(keyAndCfidHash); if (lock.tryLock()) return lock; return null; } }