/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * Licensed 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 com.linkedin.pinot.core.data.manager.offline; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.linkedin.pinot.common.metrics.ServerGauge; import com.linkedin.pinot.common.metrics.ServerMeter; import com.linkedin.pinot.common.metrics.ServerMetrics; import com.linkedin.pinot.core.data.manager.config.TableDataManagerConfig; import com.linkedin.pinot.core.indexsegment.IndexSegment; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; // TODO: pass a reference to Helix property store during initialization. Both OFFLINE and REALTIME use case need it. public abstract class AbstractTableDataManager implements TableDataManager { protected final List<String> _activeSegments = new ArrayList<String>(); protected final List<String> _loadingSegments = new ArrayList<String>(); // This read-write lock protects the _segmentsMap and SegmentDataManager.refCnt protected final ReadWriteLock _rwLock = new ReentrantReadWriteLock(); @VisibleForTesting protected final Map<String, SegmentDataManager> _segmentsMap = new HashMap<String, SegmentDataManager>(); protected Logger LOGGER = LoggerFactory.getLogger(AbstractTableDataManager.class); protected volatile boolean _isStarted = false; protected String _tableName; protected TableDataManagerConfig _tableDataManagerConfig; protected String _tableDataDir; protected File _indexDir; protected ServerMetrics _serverMetrics; protected String _serverInstance; protected AbstractTableDataManager() { } @Override public void init(@Nonnull TableDataManagerConfig tableDataManagerConfig, @Nonnull ServerMetrics serverMetrics, @Nullable String serverInstance) { _serverInstance = serverInstance; _tableDataManagerConfig = tableDataManagerConfig; _serverMetrics = serverMetrics; _tableName = _tableDataManagerConfig.getTableName(); doInit(); _tableDataDir = _tableDataManagerConfig.getDataDir(); _indexDir = new File(_tableDataDir); if (!_indexDir.exists()) { _indexDir.mkdirs(); } LOGGER.info("Initialized table: {} with data directory: {}", _tableName, _tableDataDir); } protected abstract void doInit(); @Override public void start() { LOGGER.info("Trying to start table : " + _tableName); if (_isStarted) { LOGGER.warn("TableDataManager for table {} is already started.", _tableName); return; } if (_tableDataManagerConfig == null) { LOGGER.warn("TableDataManager for table {} is not initialized.", _tableName); return; } _isStarted = true; } @Override public void shutDown() { LOGGER.info("Trying to shutdown table : " + _tableName); doShutdown(); if (_isStarted) { _tableDataManagerConfig = null; _isStarted = false; } else { LOGGER.warn("Already shutDown table : " + _tableName); } } protected abstract void doShutdown(); /** * Add a segment (or replace it, if one exists with the same name). * <p> * Ensures that reference count of the old segment (if replaced) is reduced by 1, so that the * last user of the old segment (or the calling thread, if there are none) remove the segment. * The new segment is added with a refcnt of 1, so that is never removed until a drop command * comes through. * * @param indexSegmentToAdd new segment to add/replace. */ public void addSegment(@Nonnull IndexSegment indexSegmentToAdd) { final String segmentName = indexSegmentToAdd.getSegmentName(); LOGGER.info("Trying to add a new segment {} of table {} with OfflineSegmentDataManager", segmentName, _tableName); OfflineSegmentDataManager newSegmentManager = new OfflineSegmentDataManager(indexSegmentToAdd); final int newNumDocs = indexSegmentToAdd.getSegmentMetadata().getTotalRawDocs(); SegmentDataManager oldSegmentManager; int refCnt = -1; try { _rwLock.writeLock().lock(); oldSegmentManager = _segmentsMap.put(segmentName, newSegmentManager); if (oldSegmentManager != null) { refCnt = oldSegmentManager.decrementRefCnt(); } } finally { _rwLock.writeLock().unlock(); } if (oldSegmentManager == null) { LOGGER.info("Added new segment {} for table {}", segmentName, _tableName); } else { LOGGER.info("Replaced segment {}(refCnt {}) with new segment for table {}", segmentName, refCnt, _tableName); } if (refCnt == 0) { // oldSegmentManager must be non-null. closeSegment(oldSegmentManager); } _serverMetrics.addValueToTableGauge(_tableName, ServerGauge.DOCUMENT_COUNT, newNumDocs); _serverMetrics.addValueToTableGauge(_tableName, ServerGauge.SEGMENT_COUNT, 1L); } /** * Called when we get a helix transition to go to offline or dropped state. * We need to remove it safely, keeping in mind that there may be queries that are * using the segment, * @param segmentName name of the segment to remove. */ @Override public void removeSegment(String segmentName) { if (!_isStarted) { LOGGER.warn("Could not remove segment {}, Tracker is stopped", segmentName); return; } SegmentDataManager segmentDataManager; int refCnt = -1; try { _rwLock.writeLock().lock(); segmentDataManager = _segmentsMap.remove(segmentName); if (segmentDataManager != null) { refCnt = segmentDataManager.decrementRefCnt(); } } finally { _rwLock.writeLock().unlock(); } if (refCnt == 0) { // segmentDataManager must be non-null. closeSegment(segmentDataManager); } } protected void closeSegment(SegmentDataManager segmentDataManager) { final String segmentName = segmentDataManager.getSegmentName(); LOGGER.info("Closing segment {} for table {}", segmentName, _tableName); _serverMetrics.addValueToTableGauge(_tableName, ServerGauge.SEGMENT_COUNT, -1L); _serverMetrics.addMeteredTableValue(_tableName, ServerMeter.DELETED_SEGMENT_COUNT, 1L); _serverMetrics.addValueToTableGauge(_tableName, ServerGauge.DOCUMENT_COUNT, -segmentDataManager.getSegment().getSegmentMetadata().getTotalRawDocs()); segmentDataManager.destroy(); LOGGER.info("Segment {} for table {} has been closed", segmentName, _tableName); } @Override public boolean isStarted() { return _isStarted; } @Nonnull @Override public ImmutableList<SegmentDataManager> acquireAllSegments() { ImmutableList.Builder<SegmentDataManager> segmentListBuilder = ImmutableList.builder(); try { _rwLock.readLock().lock(); for (Map.Entry<String, SegmentDataManager> segmentEntry : _segmentsMap.entrySet()) { SegmentDataManager segmentDataManager = segmentEntry.getValue(); segmentDataManager.incrementRefCnt(); segmentListBuilder.add(segmentDataManager); } } finally { _rwLock.readLock().unlock(); } return segmentListBuilder.build(); } @Override public List<SegmentDataManager> acquireSegments(List<String> segmentList) { List<SegmentDataManager> ret = new ArrayList<SegmentDataManager>(); try { _rwLock.readLock().lock(); for (String segName : segmentList) { SegmentDataManager segmentDataManager; segmentDataManager = _segmentsMap.get(segName); if (segmentDataManager != null) { segmentDataManager.incrementRefCnt(); ret.add(segmentDataManager); } } } finally { _rwLock.readLock().unlock(); } return ret; } @Override public SegmentDataManager acquireSegment(String segmentName) { try { _rwLock.readLock().lock(); SegmentDataManager segmentDataManager = _segmentsMap.get(segmentName); if (segmentDataManager != null) { segmentDataManager.incrementRefCnt(); } return segmentDataManager; } finally { _rwLock.readLock().unlock(); } } @Override public void releaseSegment(SegmentDataManager segmentDataManager) { if (segmentDataManager == null) { return; } int refCnt = segmentDataManager.decrementRefCnt(); // Exactly one thread should find this to be zero, so we can safely drop it. // We never remove it from the map here, so no need to synchronize. if (refCnt == 0) { closeSegment(segmentDataManager); } } @Override public String getTableName() { return _tableName; } }