r2dbc-pool icon indicating copy to clipboard operation
r2dbc-pool copied to clipboard

Mass destruction of R2DBC pools at once.

Open pkgonan opened this issue 1 year ago • 6 comments

Mass destruction of R2DBC pools at once.

initial_size : 20 MAX_SIZE: 20 MAX_LIFE_TIME: 10 Seconds BACKGROUND_EVICTION_INTERVAL: 1 Seconds validation_depth: local validation_query: select 1

With the above configured, a show processlist; query on the database will automatically remove all connections without attempting to maintain 20 connections.

If we have a situation where 19 of the 20 connections have been open for 10 seconds and the remaining 1 connection has been open for 1 second, common sense would dictate that only 19 connections should be removed, but all 20 connections are removed.

Kotlin + Spring Boot Webflux + R2DBC + R2DBC Pool Configuration

@Configuration
internal class R2dbcConfiguration internal constructor(
    @Value("\${payment-platform.r2dbc.host}")
    private val host: String,
    @Value("\${payment-platform.r2dbc.port}")
    private val port: Int,
    @Value("\${payment-platform.r2dbc.username}")
    private val username: String,
    @Value("\${payment-platform.r2dbc.password}")
    private val password: String,
    @Value("\${payment-platform.r2dbc.database}")
    private val database: String,
): AbstractR2dbcConfiguration() {

    companion object {
        private const val CONNECTION_POOL_INITIAL_SIZE = 20
        private const val CONNECTION_POOL_MAX_SIZE = CONNECTION_POOL_INITIAL_SIZE
        private val CONNECTION_POOL_MAX_LIFE_TIME = Duration.ofSeconds(10)
        private const val VALIDATION_QUERY = "SELECT 1"
    }

    @Bean
    override fun connectionFactory(): ConnectionFactory {
        return ConnectionFactories.get(
            ConnectionFactoryOptions.builder()
                .option(ConnectionFactoryOptions.SSL, false)
                .option(ConnectionFactoryOptions.HOST, host)
                .option(ConnectionFactoryOptions.PORT, port)
                .option(ConnectionFactoryOptions.USER, username)
                .option(ConnectionFactoryOptions.PASSWORD, password)
                .option(ConnectionFactoryOptions.DATABASE, database)
                .option(ConnectionFactoryOptions.DRIVER, PoolingConnectionFactoryProvider.POOLING_DRIVER)
                .option(ConnectionFactoryOptions.PROTOCOL, MysqlConnectionFactoryProvider.MYSQL_DRIVER)
                .option(PoolingConnectionFactoryProvider.INITIAL_SIZE, CONNECTION_POOL_INITIAL_SIZE)
                .option(PoolingConnectionFactoryProvider.MAX_SIZE, CONNECTION_POOL_MAX_SIZE)
                .option(PoolingConnectionFactoryProvider.MAX_ACQUIRE_TIME, Duration.ofSeconds(2))
                .option(PoolingConnectionFactoryProvider.MAX_CREATE_CONNECTION_TIME, Duration.ofSeconds(2))
                .option(PoolingConnectionFactoryProvider.MAX_LIFE_TIME, CONNECTION_POOL_MAX_LIFE_TIME)
                .option(PoolingConnectionFactoryProvider.BACKGROUND_EVICTION_INTERVAL, Duration.ofSeconds(1))
                .option(PoolingConnectionFactoryProvider.VALIDATION_DEPTH, ValidationDepth.LOCAL)
                .option(PoolingConnectionFactoryProvider.VALIDATION_QUERY, VALIDATION_QUERY)
                .build()
        )
    }
}

pkgonan avatar Aug 31 '23 06:08 pkgonan

It sounds a bit as if this would originate from Reactor Pool as R2DBC pool delegates all eviction and statistics functionality to Reactor Pool. Paging @pderop for further guidance.

mp911de avatar Aug 31 '23 06:08 mp911de

@mp911de @pderop

Hi, How do I prevent mass extinction of a Connection Pool?

pkgonan avatar Aug 31 '23 06:08 pkgonan

@pkgonan, ok, I'll check this tomorrow morning.

pderop avatar Aug 31 '23 09:08 pderop

Hi, @pderop Have you checked?

BACKGROUND_EVICTION_INTERVAL causes all connections to be removed and recreated.

However, all connections are recreated even if BACKGROUND_EVICTION_INTERVAL is not used.

pkgonan avatar Sep 05 '23 01:09 pkgonan

I was in trouble on another PR, I'm now starting to check.

pderop avatar Sep 05 '23 07:09 pderop

Can you confirm which exact version of reactor-pool you are using ?

I have tried to reproduce your scenario using the following junit test (based on 1.0.1 release), but I cannot reproduce the problem. Can you try to modify it, maybe I have missed your exact use case ? If you can reproduce it, please create an issue on reactor-pool project.

here is my junit test, to test it, you can load the reactor-pool (main branch) into your IDE and then add this test, for example in the SimpleDequePoolInstrumentationTest.

	@Test
	void useCase() throws InterruptedException {
		VirtualTimeScheduler vts = VirtualTimeScheduler.create();
		Duration maxLifeTime = Duration.ofSeconds(10);

		SimpleDequePool<String> pool = new SimpleDequePool<>(
				PoolBuilder
						.from(Mono.fromCallable(String::new))
						.clock(SchedulerClock.of(vts))
						.evictInBackground(Duration.ofMillis(1000), vts)
						.evictionPredicate((s, metadata) -> {
							return metadata.lifeTime() > maxLifeTime.toMillis();
						})
						.idleResourceReuseMruOrder()
						.sizeBetween(20, 20)
						.buildConfig());

		// Acquire 19 resources
		PooledRef<String>[] refs = IntStream.range(0, 19).mapToObj(i -> pool.acquire().block()).toArray(PooledRef[]::new);
		assertThat(pool.metrics().allocatedSize()).isEqualTo(20); // one more resource is created during lazy warmup
		assertThat(pool.metrics().idleSize()).isEqualTo(1);

		// wait for 1 sec
		vts.advanceTimeBy(Duration.ofMillis(1000));

		// acquire a twentieth resource (there is one remaining idle one)
		PooledRef<String> r20 = pool.acquire().block();
		// invalidate r20
		r20.invalidate().block();
		// re-acquire a new fresh r20 resource
		r20 = pool.acquire().block();

		// at this point, the first nineteen resources lifetime is around 1 sec, and r20 is fresh.
		assertThat(pool.metrics().allocatedSize()).isEqualTo(20);
		assertThat(pool.metrics().idleSize()).isEqualTo(0);

		// Now release all
		Stream.of(refs).forEach(ref -> ref.release().block());
		r20.release().block();

		// Wait 10 more sec, meaning that the first nineteen resources lifetime will be 11 sec
		vts.advanceTimeBy(Duration.ofMillis(10000));

		// the first nineteen resources should have been evicted, only 1 resource should still be acquired (r20)
		assertThat(pool.metrics().allocatedSize()).isEqualTo(1);

		// wait 1 sec, after that, r20 should have been evicted
		vts.advanceTimeBy(Duration.ofMillis(1000));
		assertThat(pool.metrics().allocatedSize()).isEqualTo(0);
	}

pderop avatar Sep 05 '23 10:09 pderop