
io.questdb.cutlass.line.tcp.LineTcpWriterJob Maven / Gradle / Ivy
/*******************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2023 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.cutlass.line.tcp;
import io.questdb.Metrics;
import io.questdb.log.Log;
import io.questdb.log.LogFactory;
import io.questdb.mp.Job;
import io.questdb.mp.RingQueue;
import io.questdb.mp.Sequence;
import io.questdb.std.Misc;
import io.questdb.std.ObjList;
import io.questdb.std.Os;
import io.questdb.std.datetime.millitime.MillisecondClock;
import io.questdb.std.str.Path;
import org.jetbrains.annotations.NotNull;
import java.io.Closeable;
class LineTcpWriterJob implements Job, Closeable {
private final static Log LOG = LogFactory.getLog(LineTcpWriterJob.class);
private final ObjList assignedTables;
private final long commitInterval;
private final Metrics metrics;
private final MillisecondClock millisecondClock;
private final Path path = new Path();
private final RingQueue queue;
private final LineTcpMeasurementScheduler scheduler;
private final Sequence sequence;
private final int workerId;
private long nextCommitTime;
LineTcpWriterJob(
int workerId,
RingQueue queue,
Sequence sequence,
MillisecondClock millisecondClock,
long commitInterval,
LineTcpMeasurementScheduler scheduler,
Metrics metrics,
ObjList assignedTables
) {
this.workerId = workerId;
this.queue = queue;
this.sequence = sequence;
this.millisecondClock = millisecondClock;
this.commitInterval = commitInterval;
this.nextCommitTime = millisecondClock.getTicks();
this.scheduler = scheduler;
this.metrics = metrics;
this.assignedTables = assignedTables;
}
@Override
public void close() {
LOG.info().$("line protocol writer closing [threadId=").$(workerId).$(']').$();
// Finish all jobs in the queue before stopping
for (int n = 0; n < queue.getCycle(); n++) {
if (!run(workerId, Job.TERMINATING_STATUS)) {
break;
}
}
Misc.free(path);
}
@Override
public boolean run(int workerId, @NotNull RunStatus runStatus) {
assert this.workerId == workerId;
boolean busy = drainQueue();
// while ILP is hammering the database via multiple connections the writer
// is likely to be very busy so commitTables() will run infrequently
// commit should run regardless the busy flag but has to finish quickly
// idea is to store the tables in a heap data structure being the tables most
// desperately need a commit on the top
if (!busy) {
commitTables();
tickWriters();
}
return busy;
}
private void commitTables() {
long wallClockMillis = millisecondClock.getTicks();
if (wallClockMillis > nextCommitTime) {
long minTableNextCommitTime = Long.MAX_VALUE;
for (int n = 0, sz = assignedTables.size(); n < sz; n++) {
// the heap based solution mentioned above will eliminate the minimum search
// we could just process the min element of the heap until we hit the first commit
// time greater than millis and that will be our nextCommitTime
try {
long tableNextCommitTime = assignedTables.getQuick(n).commitIfIntervalElapsed(wallClockMillis);
// get current time again, commit is not instant and take quite some time.
wallClockMillis = millisecondClock.getTicks();
if (tableNextCommitTime < minTableNextCommitTime) {
// taking the earliest commit time
minTableNextCommitTime = tableNextCommitTime;
}
} catch (Throwable ex) {
LOG.critical()
.$("commit failed [table=").$(assignedTables.getQuick(n).getTableToken())
.$(",ex=").$(ex)
.I$();
metrics.health().incrementUnhandledErrors();
}
}
// if no tables, just use the default commit interval
nextCommitTime = minTableNextCommitTime != Long.MAX_VALUE ? minTableNextCommitTime : wallClockMillis + commitInterval;
}
}
private boolean drainQueue() {
boolean busy = false;
while (true) {
long cursor;
while ((cursor = sequence.next()) < 0) {
if (cursor == -1) {
return busy;
}
Os.pause();
}
busy = true;
final LineTcpMeasurementEvent event = queue.get(cursor);
try {
// we check the event's writer thread ID to avoid consuming
// incomplete events
final TableUpdateDetails tud = event.getTableUpdateDetails();
boolean closeWriter = false;
if (event.getWriterWorkerId() == workerId) {
try {
if (tud.isWriterInError()) {
closeWriter = true;
} else {
if (!tud.isAssignedToJob()) {
assignedTables.add(tud);
tud.setAssignedToJob(true);
nextCommitTime = millisecondClock.getTicks();
LOG.info()
.$("assigned table to writer thread [tableName=").$(tud.getTableToken())
.$(", threadId=").$(workerId)
.I$();
}
event.append();
}
} catch (Throwable ex) {
tud.setWriterInError();
LOG.critical()
.$("closing writer because of error [table=").$(tud.getTableToken())
.$(",ex=").$(ex)
.I$();
metrics.health().incrementUnhandledErrors();
closeWriter = true;
event.createWriterReleaseEvent(tud, false);
// This is a critical error, so we treat it as an unhandled one.
}
} else {
if (event.getWriterWorkerId() == LineTcpMeasurementEventType.ALL_WRITERS_RELEASE_WRITER) {
closeWriter = true;
}
}
if (closeWriter && tud.getWriter() != null) {
scheduler.processWriterReleaseEvent(event, workerId);
assignedTables.remove(tud);
tud.setAssignedToJob(false);
nextCommitTime = millisecondClock.getTicks();
}
} catch (Throwable ex) {
LOG.error().$("failed to process ILP event because of exception [ex=").$(ex).I$();
}
sequence.done(cursor);
}
}
private void tickWriters() {
for (int n = 0, sz = assignedTables.size(); n < sz; n++) {
assignedTables.getQuick(n).tick();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy