pgjdbc/pgjdbc GitHub issues and pull requests (mirror)  
help / color / mirror / Atom feed
From: robert-mirzakhanian (@robert-mirzakhanian) <[email protected]>
To: pgjdbc/pgjdbc <[email protected]>
Subject: Re: [pgjdbc/pgjdbc] PR #3506: feat: Add access for global host status tracker
Date: Mon, 03 Feb 2025 21:19:38 +0000
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>

> so I still don't understand how this is supposed to be used?

So, i wrote small example with test. Hope this makes the idea of using it clearer.
Example:
```kotlin
package ru.github.rmirzakhanian

import com.zaxxer.hikari.HikariDataSource
import org.postgresql.hostchooser.GlobalHostStatusTracker
import org.postgresql.hostchooser.HostStatus
import org.postgresql.util.HostSpec

class CloudHandler(
    private val bestHost: String,
    private val client: Client,
    private val secondaryDataSource: HikariDataSource
) {
    enum class Status { ALIVE, DEAD, UNKNOWN }
    enum class Role { Primary, Secondary }
    enum class DataCenter { SSS, VVV, KKK }
    data class HostInfo(val host: String, val status: Status, val role: Role, val dc: DataCenter)

    class Client {
        fun getInfo(): List<HostInfo> {
            return listOf(
                // HostInfo("host1", Status.ALIVE, Role.Primary, DataCenter.SSS),
                // HostInfo("host2", Status.ALIVE, Role.Secondary, DataCenter.VVV),
                // HostInfo("host3", Status.ALIVE, Role.Secondary, DataCenter.KKK)
            )
        }
    }

    private var wasDown = false

    //periodic calls from background thread
    fun checkHosts() {
        val host = client.getInfo().find { it.host == bestHost } ?: return
        if (host.status != Status.ALIVE) {
            wasDown = true
        } else if (wasDown) {
            val hostStatus = GlobalHostStatusTracker.getHostStatusMap()[HostSpec(bestHost, 5432)]
            if (hostStatus == HostStatus.ConnectFail) {
                secondaryDataSource.hikariPoolMXBean.softEvictConnections()
            }
            wasDown = false
        }
    }
}
```
Test
```kotlin
import com.zaxxer.hikari.HikariDataSource
import com.zaxxer.hikari.HikariPoolMXBean
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
import org.postgresql.hostchooser.GlobalHostStatusTracker
import org.postgresql.hostchooser.HostStatus
import org.postgresql.util.HostSpec
import ru.github.rmirzakhanian.CloudHandler
import ru.github.rmirzakhanian.CloudHandler.DataCenter
import ru.github.rmirzakhanian.CloudHandler.HostInfo
import ru.github.rmirzakhanian.CloudHandler.Role
import ru.github.rmirzakhanian.CloudHandler.Status

class CloudHandlerTest {

    // The microservice stores information about its current data center location and sorts database connections based on low to high latency.
    // About the test: Imagine that the microservice is already running and has established a connection
    // to the database jdbc:postgresql://host2:5433,host1:5432,host3:5434/some_db
    // host2
    @Test
    fun testHostRecovery() {
        // Test client that returns the next status on each call to getInfo().
        val client = mockk<CloudHandler.Client>()

        // Define a sequence of statuses:
        // 1st call: ALIVE, 2nd call: DEAD, 3rd call: ALIVE (return to normal state)
        every { client.getInfo() } returnsMany listOf(
            listOf(
                HostInfo("host1", Status.ALIVE, Role.Primary, DataCenter.SSS),
                HostInfo("host2", Status.ALIVE, Role.Secondary, DataCenter.VVV),
                HostInfo("host3", Status.ALIVE, Role.Secondary, DataCenter.KKK)
            ),
            listOf(
                HostInfo("host1", Status.ALIVE, Role.Primary, DataCenter.SSS),
                HostInfo("host2", Status.DEAD, Role.Secondary, DataCenter.VVV),
                HostInfo("host3", Status.ALIVE, Role.Secondary, DataCenter.KKK)
            ),
            listOf(
                HostInfo("host1", Status.ALIVE, Role.Primary, DataCenter.SSS),
                HostInfo("host2", Status.ALIVE, Role.Secondary, DataCenter.VVV),
                HostInfo("host3", Status.ALIVE, Role.Secondary, DataCenter.KKK)
            )
        )

        val mockkDataSource = mockk<HikariDataSource>()
        val mockkHikariPoolMXBean = mockk<HikariPoolMXBean>()
        every { mockkDataSource.hikariPoolMXBean } returns mockkHikariPoolMXBean
        every { mockkHikariPoolMXBean.softEvictConnections() } returns Unit

        // Create a CloudHandler instance with the test client.
        val handler = CloudHandler("host2", client, mockkDataSource)

        // After create datasource's to primary database and secondary database.
        // Postgres report two records in GlobalHostStatusTracker.
        GlobalHostStatusTracker.reportHostStatus(HostSpec("host1", 5432), HostStatus.Primary)
        GlobalHostStatusTracker.reportHostStatus(HostSpec("host2", 5432), HostStatus.Secondary)

        // Simulate sequential calls to checkHosts(), as if they occur every 30 seconds.
        handler.checkHosts() // 1st call: status is ALIVE, wasDown flag remains false.

        // During operation, host 2 shuts down to service the server.
        // The Postgres driver understands this and switches to the next replica in the list. Now it is host3
        // After switches Postgres driver report 2 rows in GlobalHostStatusTracker.

        GlobalHostStatusTracker.reportHostStatus(HostSpec("host2", 5432), HostStatus.ConnectFail)
        GlobalHostStatusTracker.reportHostStatus(HostSpec("host3", 5432), HostStatus.Secondary)

        handler.checkHosts() // 2nd call: status is DEAD, wasDown flag becomes true.

        // Waiting some calls when host2 return to normal state...

        handler.checkHosts() // 10th call: status is ALIVE, recovery detected, mockkHikariPoolMXBean.softEvictConnections should be called.

        // Checking mockkHikariPoolMXBean.softEvictConnections called exactly once
        verify(exactly = 1) { mockkHikariPoolMXBean.softEvictConnections() }
    }
}

```

view thread (26+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: github://pgjdbc/pgjdbc
  Cc: [email protected], [email protected]
  Subject: Re: [pgjdbc/pgjdbc] PR #3506: feat: Add access for global host status tracker
  In-Reply-To: <<[email protected]>>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox