All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nimbusds.infinispan.persistence.sql.ExpiredEntryPagedReaper Maven / Gradle / Ivy

package com.nimbusds.infinispan.persistence.sql;


import com.codahale.metrics.Timer;
import com.nimbusds.infinispan.persistence.common.InfinispanEntry;
import net.jcip.annotations.ThreadSafe;
import org.infinispan.metadata.InternalMetadata;
import org.infinispan.persistence.spi.AdvancedCacheExpirationWriter;
import org.infinispan.persistence.spi.MarshallableEntryFactory;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.impl.DSL;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

import static org.jooq.impl.DSL.table;


/**
 * Expired entry reaper, utilises a paged key set seek strategy.
 */
@ThreadSafe
class ExpiredEntryPagedReaper extends ExpiredEntryReaper {


	/**
	 * The page limit.
	 */
	private final int pageLimit;


	/**
	 * Creates a new paged reaper for expired entries.
	 *
	 * @param mEntryFactory     The Infinispan marshallable entry factory.
	 * @param dsl               The DSL context.
	 * @param recordTransformer The SQL record transformer.
	 * @param recordWrapper     The SQL record wrapper to use.
	 * @param pageLimit         The SQL select query page limit.
	 * @param deleteTimer       The SQL delete timer to use.
	 */
	public ExpiredEntryPagedReaper(final MarshallableEntryFactory mEntryFactory,
				       final DSLContext dsl,
				       final SQLRecordTransformer recordTransformer,
				       final Function recordWrapper,
				       final int pageLimit,
				       final Timer deleteTimer) {

		super(mEntryFactory, dsl, recordTransformer, recordWrapper, deleteTimer);

		assert pageLimit > 0;
		this.pageLimit = pageLimit;
	}
	
	
	@Override
	public void purgeWithEntryListener(final AdvancedCacheExpirationWriter.ExpirationPurgeListener purgeListener) {

		// The entries for deletion
		Queue> forDeletion = new LinkedList<>();

		var numDeleted = new AtomicLong();

		var lastRetrievedEntry = new AtomicReference>();

		var numRetrieved = new AtomicLong();

		// First SELECT with ORDER BY and LIMIT
		dsl.select()
			.from(table(recordTransformer.getTableName()))
			.where(resolveExpiredCondition())
			.orderBy(recordTransformer.getKeyColumnsForExpiredEntryReaper())
			.limit(DSL.inline(pageLimit))
			.stream()
			.forEach(record -> {
				
				RetrievedSQLRecord retrievedRecord = recordWrapper.apply(record);
				numRetrieved.incrementAndGet();

				InfinispanEntry infinispanEntry;
				try {
					infinispanEntry = recordTransformer.toInfinispanEntry(retrievedRecord);
				} catch (Exception e) {
					logIllegalRecordError(retrievedRecord);
					return;
				}

				lastRetrievedEntry.set(infinispanEntry);

				InternalMetadata metadata = infinispanEntry.getMetadata();

				if (metadata != null && metadata.isExpired(System.currentTimeMillis())) {
					// Add record for deletion
					forDeletion.offer(infinispanEntry);
				}
			});


		numDeleted.set(delete(forDeletion, purgeListener));

		if (numRetrieved.get() < pageLimit) {
			return;
		}

		// Continue with SELECTs using seek by key set
		var numRetrievedInPage = new AtomicLong();
		do {
			numRetrievedInPage.set(0L);

			final long now = System.currentTimeMillis();

			dsl.select()
				.from(table(recordTransformer.getTableName()))
				.where(resolveExpiredCondition())
				.orderBy(recordTransformer.getKeyColumnsForExpiredEntryReaper())
				.seek(toVarArg(recordTransformer.getKeyValuesForExpiredEntryReaper(lastRetrievedEntry.get().getKey())))
				.limit(DSL.inline(pageLimit))
				.stream()
				.forEach(record -> {

					RetrievedSQLRecord retrievedRecord = recordWrapper.apply(record);
					numRetrieved.incrementAndGet();
					numRetrievedInPage.incrementAndGet();

					InfinispanEntry infinispanEntry;
					try {
						infinispanEntry = recordTransformer.toInfinispanEntry(retrievedRecord);
					} catch (Exception e) {
						logIllegalRecordError(retrievedRecord);
						return;
					}

					lastRetrievedEntry.set(infinispanEntry);

					InternalMetadata metadata = infinispanEntry.getMetadata();

					if (metadata != null && metadata.isExpired(now)) {
						// Add record for deletion
						forDeletion.offer(infinispanEntry);
					}
				});

			numDeleted.addAndGet(delete(forDeletion, purgeListener));

		} while (numRetrievedInPage.get() >= pageLimit);

		Loggers.SQL_LOG.debug("[IS0128] SQL store: Purged {} expired out of {} {} cache entries with paging",
			numDeleted, numRetrieved, recordTransformer.getTableName());
	}


	private long delete(final Queue> entries,
			    final AdvancedCacheExpirationWriter.ExpirationPurgeListener purgeListener) {

		return delete(entries, en -> purgeListener.marshalledEntryPurged(toMarshallableEntry(en)));
	}


	private static Object[] toVarArg(final List args) {

		return args.toArray();
	}
}