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

com.google.cloud.storage.BufferToDiskThenUpload Maven / Gradle / Ivy

There is a newer version: 2.45.0
Show newest version
/*
 * Copyright 2023 Google LLC
 *
 * 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.google.cloud.storage;

import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.api.core.BetaApi;
import com.google.api.core.InternalApi;
import com.google.api.core.SettableApiFuture;
import com.google.cloud.storage.RecoveryFileManager.RecoveryVolumeSinkFactory;
import com.google.cloud.storage.Storage.BlobWriteOption;
import com.google.cloud.storage.TransportCompatibility.Transport;
import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt;
import com.google.cloud.storage.UnifiedOpts.Opts;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collector;
import javax.annotation.concurrent.Immutable;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;

/**
 * There are scenarios in which disk space is more plentiful than memory space. This new {@link
 * BlobWriteSessionConfig} allows augmenting an instance of storage to produce {@link
 * BlobWriteSession}s which will buffer to disk rather than holding things in memory.
 *
 * 

Once the file on disk is closed, the entire file will then be uploaded to GCS. * * @see Storage#blobWriteSession(BlobInfo, BlobWriteOption...) * @see GrpcStorageOptions.Builder#setBlobWriteSessionConfig(BlobWriteSessionConfig) * @see BlobWriteSessionConfigs#bufferToDiskThenUpload(Path) * @see BlobWriteSessionConfigs#bufferToDiskThenUpload(Collection) */ @Immutable @BetaApi @TransportCompatibility({Transport.GRPC, Transport.HTTP}) public final class BufferToDiskThenUpload extends BlobWriteSessionConfig implements BlobWriteSessionConfig.HttpCompatible, BlobWriteSessionConfig.GrpcCompatible { private static final long serialVersionUID = 9059242302276891867L; /** * non-final because of {@link java.io.Serializable}, however this field is effectively final as * it is immutable and there is not reference mutator method. */ @MonotonicNonNull private transient ImmutableList paths; private final boolean includeLoggingSink; /** Used for {@link java.io.Serializable} */ @MonotonicNonNull private volatile ArrayList absolutePaths; @InternalApi BufferToDiskThenUpload(ImmutableList paths, boolean includeLoggingSink) throws IOException { this.paths = paths; this.includeLoggingSink = includeLoggingSink; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof BufferToDiskThenUpload)) { return false; } BufferToDiskThenUpload that = (BufferToDiskThenUpload) o; return includeLoggingSink == that.includeLoggingSink && Objects.equals(paths, that.paths) && Objects.equals(absolutePaths, that.absolutePaths); } @Override public int hashCode() { return Objects.hash(paths, includeLoggingSink, absolutePaths); } @VisibleForTesting @InternalApi BufferToDiskThenUpload withIncludeLoggingSink() throws IOException { return new BufferToDiskThenUpload(paths, true); } @InternalApi @Override WriterFactory createFactory(Clock clock) throws IOException { Duration window = Duration.ofMinutes(10); RecoveryFileManager recoveryFileManager = RecoveryFileManager.of(paths, getRecoverVolumeSinkFactory(clock, window)); ThroughputSink gcs = ThroughputSink.windowed(ThroughputMovingWindow.of(window), clock); gcs = includeLoggingSink ? ThroughputSink.tee(ThroughputSink.logged("gcs", clock), gcs) : gcs; return new Factory(recoveryFileManager, clock, gcs); } private RecoveryVolumeSinkFactory getRecoverVolumeSinkFactory(Clock clock, Duration window) { return path -> { ThroughputSink windowed = ThroughputSink.windowed(ThroughputMovingWindow.of(window), clock); if (includeLoggingSink) { return ThroughputSink.tee( ThroughputSink.logged(path.toAbsolutePath().toString(), clock), windowed); } else { return windowed; } }; } private void writeObject(ObjectOutputStream out) throws IOException { if (absolutePaths == null) { synchronized (this) { if (absolutePaths == null) { absolutePaths = paths.stream() .map(Path::toAbsolutePath) .map(Path::toString) .collect( Collector.of( ArrayList::new, ArrayList::add, (left, right) -> { left.addAll(right); return left; })); } } } out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.paths = absolutePaths.stream().map(Paths::get).collect(ImmutableList.toImmutableList()); } private static final class Factory implements WriterFactory { private final RecoveryFileManager recoveryFileManager; private final Clock clock; private final ThroughputSink gcs; private Factory(RecoveryFileManager recoveryFileManager, Clock clock, ThroughputSink gcs) { this.recoveryFileManager = recoveryFileManager; this.clock = clock; this.gcs = gcs; } @InternalApi @Override public WritableByteChannelSession writeSession( StorageInternal storage, BlobInfo info, Opts opts) { return new Factory.WriteToFileThenUpload( storage, info, opts, recoveryFileManager.newRecoveryFile(info)); } private final class WriteToFileThenUpload implements WritableByteChannelSession { private final StorageInternal storage; private final BlobInfo info; private final Opts opts; private final RecoveryFile rf; private final SettableApiFuture result; private WriteToFileThenUpload( StorageInternal storage, BlobInfo info, Opts opts, RecoveryFile rf) { this.info = info; this.opts = opts; this.rf = rf; this.storage = storage; this.result = SettableApiFuture.create(); } @Override public ApiFuture openAsync() { try { ApiFuture f = ApiFutures.immediateFuture(rf.writer()); return ApiFutures.transform( f, Factory.WriteToFileThenUpload.Flusher::new, MoreExecutors.directExecutor()); } catch (IOException e) { throw StorageException.coalesce(e); } } @Override public ApiFuture getResult() { return result; } private final class Flusher implements WritableByteChannel { private final WritableByteChannel delegate; private Flusher(WritableByteChannel delegate) { this.delegate = delegate; } @Override public int write(ByteBuffer src) throws IOException { return delegate.write(src); } @Override public boolean isOpen() { return delegate.isOpen(); } @Override public void close() throws IOException { delegate.close(); try (RecoveryFile rf = Factory.WriteToFileThenUpload.this.rf) { Path path = rf.getPath(); long size = Files.size(path); ThroughputSink.computeThroughput( clock, gcs, size, () -> { BlobInfo blob = storage.internalCreateFrom(path, info, opts); result.set(blob); }); } catch (StorageException | IOException e) { result.setException(e); throw e; } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy