
io.questdb.cairo.mv.MatViewState Maven / Gradle / Ivy
Show all versions of questdb Show documentation
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2024 QuestDB
*
* 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 io.questdb.cairo.mv;
import io.questdb.cairo.file.AppendableBlock;
import io.questdb.cairo.file.BlockFileWriter;
import io.questdb.cairo.sql.RecordCursorFactory;
import io.questdb.griffin.RecordToRowCopier;
import io.questdb.std.Misc;
import io.questdb.std.Numbers;
import io.questdb.std.QuietCloseable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static io.questdb.TelemetrySystemEvent.*;
/**
* Mat view refresh state serves the purpose of synchronizing and coordinating
* {@link MatViewRefreshJob}s.
*
* Unlike {@link MatViewStateReader}, it doesn't include invalidation reason
* string as that field is not needed for refresh jobs.
*/
public class MatViewState implements QuietCloseable {
public static final String MAT_VIEW_STATE_FILE_NAME = "_mv.s";
public static final int MAT_VIEW_STATE_FORMAT_EXTRA_TS_MSG_TYPE = 1;
public static final int MAT_VIEW_STATE_FORMAT_MSG_TYPE = 0;
// used to avoid concurrent refresh runs
private final AtomicBoolean latch = new AtomicBoolean(false);
// Incremented each time an incremental/full refresh finishes.
// Used by MatViewTimerJob to avoid queueing redundant refresh tasks.
private final AtomicLong refreshSeq = new AtomicLong();
private final MatViewTelemetryFacade telemetryFacade;
private final MatViewDefinition viewDefinition;
private RecordCursorFactory cursorFactory;
private volatile boolean dropped;
private volatile boolean invalid;
private volatile long lastRefreshBaseTxn = -1;
private volatile long lastRefreshFinishTimestamp = Numbers.LONG_NULL;
private volatile long lastRefreshStartTimestamp = Numbers.LONG_NULL;
private volatile boolean pendingInvalidation;
private long recordRowCopierMetadataVersion;
private RecordToRowCopier recordToRowCopier;
public MatViewState(
@NotNull MatViewDefinition viewDefinition,
MatViewTelemetryFacade telemetryFacade
) {
this.viewDefinition = viewDefinition;
this.telemetryFacade = telemetryFacade;
}
public static void append(
long lastRefreshTimestamp,
long lastRefreshBaseTxn,
boolean invalid,
@Nullable CharSequence invalidationReason,
@NotNull BlockFileWriter writer
) {
final AppendableBlock block = writer.append();
appendState(lastRefreshBaseTxn, invalid, invalidationReason, block);
block.commit(MAT_VIEW_STATE_FORMAT_MSG_TYPE);
final AppendableBlock blockTs = writer.append();
appendTs(lastRefreshTimestamp, blockTs);
blockTs.commit(MAT_VIEW_STATE_FORMAT_EXTRA_TS_MSG_TYPE);
writer.commit();
}
// refreshState can be null, in this case "default" record will be written
public static void append(@Nullable MatViewStateReader refreshState, @NotNull BlockFileWriter writer) {
if (refreshState != null) {
append(
refreshState.getLastRefreshTimestamp(),
refreshState.getLastRefreshBaseTxn(),
refreshState.isInvalid(),
refreshState.getInvalidationReason(),
writer
);
} else {
append(
Numbers.LONG_NULL,
-1,
false,
null,
writer
);
}
}
// kept public for tests
public static void appendState(
long lastRefreshBaseTxn,
boolean invalid,
@Nullable CharSequence invalidationReason,
@NotNull AppendableBlock block
) {
block.putBool(invalid);
block.putLong(lastRefreshBaseTxn);
block.putStr(invalidationReason);
}
// kept public for tests
public static void appendTs(long lastRefreshTimestamp, @NotNull AppendableBlock block) {
block.putLong(lastRefreshTimestamp);
}
public RecordCursorFactory acquireRecordFactory() {
assert latch.get();
RecordCursorFactory factory = cursorFactory;
cursorFactory = null;
return factory;
}
@Override
public void close() {
cursorFactory = Misc.free(cursorFactory);
}
public long getLastRefreshBaseTxn() {
return lastRefreshBaseTxn;
}
public long getLastRefreshFinishTimestamp() {
return lastRefreshFinishTimestamp;
}
public long getLastRefreshStartTimestamp() {
return lastRefreshStartTimestamp;
}
public long getRecordRowCopierMetadataVersion() {
return recordRowCopierMetadataVersion;
}
public RecordToRowCopier getRecordToRowCopier() {
return recordToRowCopier;
}
public long getRefreshSeq() {
return refreshSeq.get();
}
public @NotNull MatViewDefinition getViewDefinition() {
return viewDefinition;
}
public void incrementRefreshSeq() {
refreshSeq.incrementAndGet();
}
public void init() {
telemetryFacade.store(MAT_VIEW_CREATE, viewDefinition.getMatViewToken(), Numbers.LONG_NULL, null, 0);
}
public void initFromReader(MatViewStateReader reader) {
this.invalid = reader.isInvalid();
this.lastRefreshBaseTxn = reader.getLastRefreshBaseTxn();
this.lastRefreshFinishTimestamp = reader.getLastRefreshTimestamp();
}
public boolean isDropped() {
return dropped;
}
public boolean isInvalid() {
return invalid;
}
public boolean isLocked() {
return latch.get();
}
public boolean isPendingInvalidation() {
return pendingInvalidation;
}
public void markAsDropped() {
dropped = true;
telemetryFacade.store(MAT_VIEW_DROP, viewDefinition.getMatViewToken(), Numbers.LONG_NULL, null, 0);
}
public void markAsInvalid(CharSequence invalidationReason) {
if (!invalid) {
telemetryFacade.store(MAT_VIEW_INVALIDATE, viewDefinition.getMatViewToken(), Numbers.LONG_NULL, invalidationReason, 0);
}
this.invalid = true;
}
public void markAsPendingInvalidation() {
pendingInvalidation = true;
}
public void markAsValid() {
this.invalid = false;
this.pendingInvalidation = false;
}
public void rangeRefreshSuccess(
RecordCursorFactory factory,
RecordToRowCopier copier,
long recordRowCopierMetadataVersion,
long refreshFinishedTimestamp,
long refreshTriggeredTimestamp
) {
assert latch.get();
this.cursorFactory = factory;
this.recordToRowCopier = copier;
this.recordRowCopierMetadataVersion = recordRowCopierMetadataVersion;
this.lastRefreshFinishTimestamp = refreshFinishedTimestamp;
telemetryFacade.store(
MAT_VIEW_REFRESH_SUCCESS,
viewDefinition.getMatViewToken(),
-1,
null,
refreshFinishedTimestamp - refreshTriggeredTimestamp
);
}
public void refreshFail(long refreshTimestamp, CharSequence errorMessage) {
assert latch.get();
this.lastRefreshFinishTimestamp = refreshTimestamp;
markAsInvalid(errorMessage);
telemetryFacade.store(MAT_VIEW_REFRESH_FAIL, viewDefinition.getMatViewToken(), Numbers.LONG_NULL, errorMessage, 0);
}
public void refreshSuccess(
RecordCursorFactory factory,
RecordToRowCopier copier,
long recordRowCopierMetadataVersion,
long refreshFinishedTimestamp,
long refreshTriggeredTimestamp,
long baseTableTxn
) {
assert latch.get();
this.cursorFactory = factory;
this.recordToRowCopier = copier;
this.recordRowCopierMetadataVersion = recordRowCopierMetadataVersion;
this.lastRefreshFinishTimestamp = refreshFinishedTimestamp;
this.lastRefreshBaseTxn = baseTableTxn;
telemetryFacade.store(
MAT_VIEW_REFRESH_SUCCESS,
viewDefinition.getMatViewToken(),
baseTableTxn,
null,
refreshFinishedTimestamp - refreshTriggeredTimestamp
);
}
public void setLastRefreshBaseTableTxn(long txn) {
lastRefreshBaseTxn = txn;
}
public void setLastRefreshStartTimestamp(long ts) {
lastRefreshStartTimestamp = ts;
}
public void setLastRefreshTimestamp(long ts) {
this.lastRefreshFinishTimestamp = ts;
}
public void tryCloseIfDropped() {
if (dropped && tryLock()) {
try {
close();
} finally {
unlock();
}
}
}
public boolean tryLock() {
return latch.compareAndSet(false, true);
}
public void unlock() {
if (!latch.compareAndSet(true, false)) {
throw new IllegalStateException("cannot unlock, not locked");
}
}
}