Skip to content

Commit

Permalink
Lagt til store og punctuator i opplysninger aggregering
Browse files Browse the repository at this point in the history
  • Loading branch information
naviktthomas committed Jul 10, 2024
1 parent d37cdf6 commit 3895254
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 27 deletions.
4 changes: 4 additions & 0 deletions apps/opplysninger-aggregering/nais/nais-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ spec:
value: "opplysninger-stream-v1"
- name: KAFKA_PAW_OPPLYSNINGER_OM_ARBEIDSSOEKER_TOPIC
value: "paw.opplysninger-om-arbeidssoeker-v1"
- name: OPPLYSNINGER_PUNCTUATOR_SCHEDULE
value: "PT10M"
- name: OPPLYSNINGER_LAGRET_TIDSPERIODE
value: "PT1H"
resources:
limits:
memory: 1024Mi
Expand Down
8 changes: 6 additions & 2 deletions apps/opplysninger-aggregering/nais/nais-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@ spec:
value: "opplysninger-stream-v1"
- name: KAFKA_PAW_OPPLYSNINGER_OM_ARBEIDSSOEKER_TOPIC
value: "paw.opplysninger-om-arbeidssoeker-v1"
- name: OPPLYSNINGER_PUNCTUATOR_SCHEDULE
value: "PT10M"
- name: OPPLYSNINGER_LAGRET_TIDSPERIODE
value: "PT1H"
resources:
limits:
memory: 3072Mi
memory: 2048Mi
requests:
cpu: 250m
memory: 2048Mi
memory: 512Mi
replicas:
min: 2
max: 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.micrometer.prometheusmetrics.PrometheusConfig
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry
import no.nav.paw.arbeidssoekerregisteret.config.APPLICATION_CONFIG_FILE_NAME
import no.nav.paw.arbeidssoekerregisteret.config.APPLICATION_LOGGER_NAME
import no.nav.paw.arbeidssoekerregisteret.config.AppConfig
import no.nav.paw.arbeidssoekerregisteret.config.SERVER_CONFIG_FILE_NAME
import no.nav.paw.arbeidssoekerregisteret.config.SERVER_LOGGER_NAME
import no.nav.paw.arbeidssoekerregisteret.config.ServerConfig
import no.nav.paw.arbeidssoekerregisteret.properties.APPLICATION_CONFIG_FILE_NAME
import no.nav.paw.arbeidssoekerregisteret.properties.APPLICATION_LOGGER_NAME
import no.nav.paw.arbeidssoekerregisteret.properties.ApplicationProperties
import no.nav.paw.arbeidssoekerregisteret.properties.SERVER_CONFIG_FILE_NAME
import no.nav.paw.arbeidssoekerregisteret.properties.SERVER_LOGGER_NAME
import no.nav.paw.arbeidssoekerregisteret.properties.ServerProperties
import no.nav.paw.arbeidssoekerregisteret.context.ApplicationContext
import no.nav.paw.arbeidssoekerregisteret.plugins.configureKafka
import no.nav.paw.arbeidssoekerregisteret.plugins.configureMetrics
Expand All @@ -23,8 +23,8 @@ import org.slf4j.LoggerFactory

fun main() {
val logger = LoggerFactory.getLogger(SERVER_LOGGER_NAME)
val serverProperties = loadNaisOrLocalConfiguration<ServerConfig>(SERVER_CONFIG_FILE_NAME)
val applicationProperties = loadNaisOrLocalConfiguration<AppConfig>(APPLICATION_CONFIG_FILE_NAME)
val serverProperties = loadNaisOrLocalConfiguration<ServerProperties>(SERVER_CONFIG_FILE_NAME)
val applicationProperties = loadNaisOrLocalConfiguration<ApplicationProperties>(APPLICATION_CONFIG_FILE_NAME)

logger.info("Starter ${applicationProperties.appId}")

Expand All @@ -47,7 +47,7 @@ fun main() {
server.start(wait = true)
}

fun Application.module(properties: AppConfig) {
fun Application.module(properties: ApplicationProperties) {
val logger = LoggerFactory.getLogger(APPLICATION_LOGGER_NAME)
val healthIndicatorService = HealthIndicatorService()
val meterRegistry = PrometheusMeterRegistry(PrometheusConfig.DEFAULT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package no.nav.paw.arbeidssoekerregisteret.config
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde
import no.nav.paw.arbeidssokerregisteret.api.v4.OpplysningerOmArbeidssoeker
import no.nav.paw.config.env.NaisEnv
import no.nav.paw.config.env.currentNaisEnv
import org.apache.avro.specific.SpecificRecord
import org.apache.kafka.common.serialization.Deserializer
import org.apache.kafka.common.serialization.Serde
import org.apache.kafka.common.serialization.Serializer
Expand Down Expand Up @@ -46,3 +49,12 @@ inline fun <reified T> buildJsonSerde(naisEnv: NaisEnv, objectMapper: ObjectMapp
inline fun <reified T> buildJsonSerde(): Serde<T> {
return buildJsonSerde<T>(currentNaisEnv, buildObjectMapper)
}

inline fun <reified T : SpecificRecord> buildAvroSerde(): Serde<T> {
return SpecificAvroSerde()
}

fun buildOpplysningerOmArbeidssoekerAvroSerde(): Serde<OpplysningerOmArbeidssoeker> {
return buildAvroSerde()
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
package no.nav.paw.arbeidssoekerregisteret.config

import io.micrometer.core.instrument.MeterRegistry
import io.micrometer.core.instrument.Tag
import io.micrometer.core.instrument.Tags
import java.time.Instant
import java.time.ZoneId
import java.util.concurrent.atomic.AtomicLong

private const val METRIC_PREFIX = "paw_arbeidssoeker_opplysninger_aggregering"

fun MeterRegistry.tellAntallMottatteOpplysninger() {
fun MeterRegistry.tellMottatteOpplysninger() {
counter(
"${METRIC_PREFIX}_antall_mottatte_opplysninger_total",
).increment()
}

fun MeterRegistry.antallLagredeOpplysningerTotal(antallReference: AtomicLong) {
gauge(
"${METRIC_PREFIX}_antall_lagrede_opplysninger_total",
Tags.empty(),
antallReference
) {
antallReference.get().toDouble()
}
}

fun MeterRegistry.antallLagredeOpplysningerSumPerPeriode(timestamp: Instant, antallReference: AtomicLong) {
val zonedDateTime = timestamp.atZone(ZoneId.systemDefault())
gauge(
"${METRIC_PREFIX}_antall_lagrede_opplysninger_sum_per_periode",
Tags.of(
Tag.of("minute", "${zonedDateTime.minute}")
),
antallReference
) {
antallReference.get().toDouble()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package no.nav.paw.arbeidssoekerregisteret.context

import no.nav.paw.arbeidssoekerregisteret.config.AppConfig
import no.nav.paw.arbeidssoekerregisteret.properties.ApplicationProperties
import org.slf4j.Logger

data class ApplicationContext(val logger: Logger, val properties: AppConfig)
data class ApplicationContext(val logger: Logger, val properties: ApplicationProperties)
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import io.ktor.server.application.createApplicationPlugin
import io.ktor.server.application.hooks.MonitoringEvent
import io.ktor.server.application.log
import io.ktor.util.KtorDsl
import no.nav.paw.arbeidssoekerregisteret.config.KafkaStreamsConfig
import no.nav.paw.arbeidssoekerregisteret.properties.KafkaStreamsProperties
import org.apache.kafka.streams.KafkaStreams

@KtorDsl
class KafkaStreamsPluginConfig {
var kafkaStreamsConfig: KafkaStreamsConfig? = null
var kafkaStreamsConfig: KafkaStreamsProperties? = null
var kafkaStreams: List<KafkaStreams>? = null
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package no.nav.paw.arbeidssoekerregisteret.config
package no.nav.paw.arbeidssoekerregisteret.properties

import no.nav.paw.config.env.NaisEnv
import no.nav.paw.config.env.currentAppId
Expand All @@ -11,16 +11,19 @@ const val SERVER_LOGGER_NAME = "no.nav.paw.server"
const val APPLICATION_LOGGER_NAME = "no.nav.paw.application"
const val APPLICATION_CONFIG_FILE_NAME = "application_configuration.toml"

data class AppConfig(
data class ApplicationProperties(
val kafka: KafkaConfig,
val kafkaStreams: KafkaStreamsConfig,
val kafkaStreams: KafkaStreamsProperties,
val appName: String = currentAppName ?: "paw-arbeidssoeker-opplysninger-aggregering",
val appId: String = currentAppId ?: "paw-arbeidssoeker-opplysninger-aggregering:LOCAL",
val naisEnv: NaisEnv = currentNaisEnv
)

data class KafkaStreamsConfig(
data class KafkaStreamsProperties(
val shutDownTimeout: Duration,
val opplysingerStreamIdSuffix: String,
val opplysningerTopic: String,
val opplysningerStore: String,
val opplysningerPunctuatorSchedule: Duration,
val opplysningerLagretTidsperiode: Duration,
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package no.nav.paw.arbeidssoekerregisteret.config
package no.nav.paw.arbeidssoekerregisteret.properties

const val SERVER_CONFIG_FILE_NAME = "server_configuration.toml"

data class ServerConfig(
data class ServerProperties(
val port: Int,
val callGroupSize: Int,
val workerGroupSize: Int,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,118 @@
package no.nav.paw.arbeidssoekerregisteret.topology

import io.micrometer.core.instrument.MeterRegistry
import no.nav.paw.arbeidssoekerregisteret.config.tellAntallMottatteOpplysninger
import no.nav.paw.arbeidssoekerregisteret.config.antallLagredeOpplysningerSumPerPeriode
import no.nav.paw.arbeidssoekerregisteret.config.antallLagredeOpplysningerTotal
import no.nav.paw.arbeidssoekerregisteret.config.buildOpplysningerOmArbeidssoekerAvroSerde
import no.nav.paw.arbeidssoekerregisteret.config.tellMottatteOpplysninger
import no.nav.paw.arbeidssoekerregisteret.context.ApplicationContext
import no.nav.paw.arbeidssokerregisteret.api.v4.OpplysningerOmArbeidssoeker
import no.nav.paw.config.kafka.streams.Punctuation
import no.nav.paw.config.kafka.streams.genericProcess
import org.apache.kafka.common.serialization.Serdes
import org.apache.kafka.streams.StreamsBuilder
import org.apache.kafka.streams.Topology
import org.apache.kafka.streams.kstream.Consumed
import org.apache.kafka.streams.processor.PunctuationType
import org.apache.kafka.streams.state.Stores
import org.apache.kafka.streams.state.TimestampedKeyValueStore
import org.apache.kafka.streams.state.ValueAndTimestamp
import java.time.Instant
import java.util.*
import java.util.concurrent.atomic.AtomicLong

context(ApplicationContext)
fun buildOpplysningerTopology(
meterRegistry: MeterRegistry
): Topology = StreamsBuilder().apply {
addOpplysningerStateStore()
addOpplysningerKStream(meterRegistry)
}.build()

context(ApplicationContext)
private fun StreamsBuilder.addOpplysningerStateStore() {
logger.info("Oppretter state store for opplysninger om arbeidssøker")
val kafkaStreamsProperties = properties.kafkaStreams

this.addStateStore(
Stores.timestampedKeyValueStoreBuilder(
Stores.persistentKeyValueStore(kafkaStreamsProperties.opplysningerStore),
Serdes.Long(),
buildOpplysningerOmArbeidssoekerAvroSerde()
)
)
}

context(ApplicationContext)
private fun StreamsBuilder.addOpplysningerKStream(meterRegistry: MeterRegistry) {
logger.info("Oppretter KStream for opplysninger om arbeidssøker")
val kafkaStreamsProperties = properties.kafkaStreams

this.stream<Long, OpplysningerOmArbeidssoeker>(kafkaStreamsProperties.opplysningerTopic)
this
.stream(
kafkaStreamsProperties.opplysningerTopic,
Consumed.with(Serdes.Long(), buildOpplysningerOmArbeidssoekerAvroSerde())
)
.peek { key, _ ->
logger.debug("Mottok event på {} med key {}", kafkaStreamsProperties.opplysningerTopic, key)
meterRegistry.tellAntallMottatteOpplysninger()
meterRegistry.tellMottatteOpplysninger()
}.genericProcess<Long, OpplysningerOmArbeidssoeker, Long, OpplysningerOmArbeidssoeker>(
name = "processOpplysningerOmArbeidssoeker",
stateStoreNames = arrayOf(kafkaStreamsProperties.opplysningerStore),
punctuation = buildPunctuation(meterRegistry)
) { record ->
val stateStore: TimestampedKeyValueStore<Long, OpplysningerOmArbeidssoeker> =
getStateStore(kafkaStreamsProperties.opplysningerStore)
logger.debug("Lagrer opplysninger for periode {}", record.value().periodeId)
stateStore.put(record.key(), ValueAndTimestamp.make(record.value(), Instant.now().toEpochMilli()))
}
}.build()
}

context(ApplicationContext)
private fun buildPunctuation(meterRegistry: MeterRegistry): Punctuation<Long, OpplysningerOmArbeidssoeker> {
logger.info("Oppretter Punctuation for opplysninger om arbeidssøker")
val kafkaStreamsProperties = properties.kafkaStreams

return Punctuation(
kafkaStreamsProperties.opplysningerPunctuatorSchedule, PunctuationType.WALL_CLOCK_TIME
) { timestamp, context ->
logger.info("Punctuation kjører for tidspunkt {}", timestamp)

with(context) {
val antallTotalt = AtomicLong(0)
val histogram = mutableMapOf<UUID, AtomicLong>()

val stateStore: TimestampedKeyValueStore<Long, OpplysningerOmArbeidssoeker> =
getStateStore(kafkaStreamsProperties.opplysningerStore)
for (keyValue in stateStore.all()) {
antallTotalt.incrementAndGet()
val lagretTidspunkt = Instant.ofEpochMilli(keyValue.value.timestamp())
val utloepTidspunkt = Instant.now().minus(kafkaStreamsProperties.opplysningerLagretTidsperiode)
if (utloepTidspunkt.isAfter(lagretTidspunkt)
) {
logger.debug(
"Sletter opplysninger for periode {} fordi de har vært lagret mer enn {}m (utløp {} > lagret {})",
keyValue.value.value().periodeId,
kafkaStreamsProperties.opplysningerLagretTidsperiode.toMinutes(),
utloepTidspunkt,
lagretTidspunkt
)
stateStore.delete(keyValue.key)
continue
}

val opplysninger = keyValue.value.value()
val antall = histogram[opplysninger.periodeId]
if (antall != null) {
antall.incrementAndGet()
histogram[opplysninger.periodeId] = antall
} else {
histogram[opplysninger.periodeId] = AtomicLong(1)
}
}

histogram.forEach { (_, antall) -> meterRegistry.antallLagredeOpplysningerSumPerPeriode(timestamp, antall) }
meterRegistry.antallLagredeOpplysningerTotal(antallTotalt)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ url = "http://localhost:8082"
shutDownTimeout = "PT1S"
opplysingerStreamIdSuffix = "opplysninger-stream-v1"
opplysningerTopic = "paw.opplysninger-om-arbeidssoeker-v1"
opplysningerStore = "opplysninger-store"
opplysningerPunctuatorSchedule = "PT1M"
opplysningerLagretTidsperiode = "PT10M"
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ password = "${KAFKA_SCHEMA_REGISTRY_PASSWORD}"
shutDownTimeout = "PT5S"
opplysingerStreamIdSuffix = "${KAFKA_OPPLYSNINGER_OM_ARBEIDSSOEKER_STREAM_ID_SUFFIX}"
opplysningerTopic = "${KAFKA_PAW_OPPLYSNINGER_OM_ARBEIDSSOEKER_TOPIC}"
opplysningerStore = "opplysninger-store"
opplysningerPunctuatorSchedule = "${OPPLYSNINGER_PUNCTUATOR_SCHEDULE}"
opplysningerLagretTidsperiode = "${OPPLYSNINGER_LAGRET_TIDSPERIODE}"
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package no.nav.paw.arbeidssoekerregisteret.config

import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldNotBe
import no.nav.paw.arbeidssoekerregisteret.properties.APPLICATION_CONFIG_FILE_NAME
import no.nav.paw.arbeidssoekerregisteret.properties.ApplicationProperties
import no.nav.paw.config.hoplite.loadNaisOrLocalConfiguration

class AppConfigTest : FreeSpec({
"Skal laste config" {
val appConfig = loadNaisOrLocalConfiguration<AppConfig>(APPLICATION_CONFIG_FILE_NAME)
val appConfig = loadNaisOrLocalConfiguration<ApplicationProperties>(APPLICATION_CONFIG_FILE_NAME)
appConfig.kafka shouldNotBe null
appConfig.kafkaStreams shouldNotBe null
}
Expand Down
Loading

0 comments on commit 3895254

Please sign in to comment.