/**
* 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 org.apache.aurora.scheduler.storage.db;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import javax.inject.Inject;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.AbstractScheduledService;
import org.apache.aurora.common.inject.TimedInterceptor.Timed;
import org.apache.aurora.common.stats.StatsProvider;
import org.apache.aurora.scheduler.storage.Storage;
import org.apache.aurora.scheduler.storage.Storage.MutateWork.NoResult;
import org.apache.ibatis.exceptions.PersistenceException;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
* A periodic cleanup routine for unreferenced database relations.
*/
class RowGarbageCollector extends AbstractScheduledService {
private static final Logger LOG = LoggerFactory.getLogger(RowGarbageCollector.class);
// Note: these are deliberately ordered to remove 'parent' references first, but since
// this is an iterative process, it is not strictly necessary.
private static final List<Class<? extends GarbageCollectedTableMapper>> TABLES =
ImmutableList.of(TaskConfigMapper.class, JobKeyMapper.class);
private final AtomicLong deletedCount;
private final Scheduler iterationScheduler;
private final SqlSessionFactory sessionFactory;
// Note: Storage is only used to acquire the same application-level lock used by other storage
// mutations. This sidesteps the issue of DB deadlocks (e.g. AURORA-1401).
private final Storage storage;
@Inject
RowGarbageCollector(
Scheduler iterationScheduler,
SqlSessionFactory sessionFactory,
Storage storage,
StatsProvider statsProvider) {
this.iterationScheduler = requireNonNull(iterationScheduler);
this.sessionFactory = requireNonNull(sessionFactory);
this.storage = requireNonNull(storage);
this.deletedCount = statsProvider.makeCounter("row_garbage_collector_deleted");
}
@Override
protected Scheduler scheduler() {
return iterationScheduler;
}
@Timed("row_garbage_collector_run")
@VisibleForTesting
@Override
public void runOneIteration() {
LOG.info("Scanning database tables for unreferenced rows.");
long startCount = deletedCount.get();
for (Class<? extends GarbageCollectedTableMapper> tableClass : TABLES) {
storage.write((NoResult.Quiet) storeProvider -> {
try (SqlSession session = sessionFactory.openSession(true)) {
GarbageCollectedTableMapper table = session.getMapper(tableClass);
for (long rowId : table.selectAllRowIds()) {
try {
table.deleteRow(rowId);
deletedCount.incrementAndGet();
} catch (PersistenceException e) {
// Expected for rows that are still referenced.
}
}
}
});
}
LOG.info("Deleted {} unreferenced rows.", Long.valueOf(deletedCount.get() - startCount));
}
}