Skip to content

Commit 526bef1

Browse files
committed
improvement: Try to fix an issue wiht performance
1 parent ec0ad44 commit 526bef1

8 files changed

Lines changed: 112 additions & 12 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- Add indexes to improve query performance and reduce connection pool pressure
2+
3+
-- Index for is_latest_version queries (used on every page header)
4+
CREATE INDEX IF NOT EXISTS artifact_latest_version_idx
5+
ON artifacts (organization, repository)
6+
WHERE is_latest_version = true;
7+
8+
-- Index for Maven coordinate lookups (group_id, artifact_id)
9+
CREATE INDEX IF NOT EXISTS artifact_maven_coords_idx
10+
ON artifacts (group_id, artifact_id);
11+
12+
-- Index for dependency lookups by source
13+
CREATE INDEX IF NOT EXISTS artifact_dep_source_idx
14+
ON artifact_dependencies (source_group_id, source_artifact_id, source_version);
15+
16+
-- Index for reverse dependency lookups by target
17+
CREATE INDEX IF NOT EXISTS artifact_dep_target_idx
18+
ON artifact_dependencies (target_group_id, target_artifact_id, target_version);

modules/infra/src/main/resources/reference.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,21 @@ scaladex {
1313
name = ${?POSTGRESQL_DATABASE}
1414
port = 5432
1515
url = "jdbc:postgresql://"${scaladex.database.user}":"${scaladex.database.password}"@localhost:"${scaladex.database.port}"/"${scaladex.database.name}
16+
17+
pool {
18+
maximum-pool-size = 20
19+
maximum-pool-size = ${?DB_POOL_MAX_SIZE}
20+
minimum-idle = 5
21+
minimum-idle = ${?DB_POOL_MIN_IDLE}
22+
connection-timeout-ms = 30000
23+
connection-timeout-ms = ${?DB_CONNECTION_TIMEOUT_MS}
24+
idle-timeout-ms = 600000
25+
idle-timeout-ms = ${?DB_IDLE_TIMEOUT_MS}
26+
max-lifetime-ms = 1800000
27+
max-lifetime-ms = ${?DB_MAX_LIFETIME_MS}
28+
leak-detection-threshold-ms = 60000
29+
leak-detection-threshold-ms = ${?DB_LEAK_DETECTION_MS}
30+
}
1631
}
1732
github {
1833
# follow this tutorial to create your token https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package scaladex.infra.config
2+
3+
import scala.util.Try
4+
5+
import com.typesafe.config.Config
6+
import com.typesafe.config.ConfigFactory
7+
8+
final case class DatabasePoolConfig(
9+
maximumPoolSize: Int,
10+
minimumIdle: Int,
11+
connectionTimeoutMs: Long,
12+
idleTimeoutMs: Long,
13+
maxLifetimeMs: Long,
14+
leakDetectionThresholdMs: Long
15+
)
16+
17+
object DatabasePoolConfig:
18+
val default: DatabasePoolConfig = DatabasePoolConfig(
19+
maximumPoolSize = 20,
20+
minimumIdle = 5,
21+
connectionTimeoutMs = 30000,
22+
idleTimeoutMs = 600000,
23+
maxLifetimeMs = 1800000,
24+
leakDetectionThresholdMs = 60000
25+
)
26+
27+
def load(): Try[DatabasePoolConfig] =
28+
val config = ConfigFactory.load()
29+
from(config)
30+
31+
def from(config: Config): Try[DatabasePoolConfig] = Try {
32+
val poolConfig = config.getConfig("scaladex.database.pool")
33+
DatabasePoolConfig(
34+
maximumPoolSize = poolConfig.getInt("maximum-pool-size"),
35+
minimumIdle = poolConfig.getInt("minimum-idle"),
36+
connectionTimeoutMs = poolConfig.getLong("connection-timeout-ms"),
37+
idleTimeoutMs = poolConfig.getLong("idle-timeout-ms"),
38+
maxLifetimeMs = poolConfig.getLong("max-lifetime-ms"),
39+
leakDetectionThresholdMs = poolConfig.getLong("leak-detection-threshold-ms")
40+
)
41+
}
42+
end DatabasePoolConfig

modules/infra/src/main/scala/scaladex/infra/sql/DoobieUtils.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package scaladex.infra.sql
22

33
import scala.concurrent.ExecutionContext
44

5+
import scaladex.infra.config.DatabasePoolConfig
56
import scaladex.infra.config.PostgreSQLConfig
67

78
import cats.effect.*
@@ -28,11 +29,20 @@ object DoobieUtils:
2829
.load()
2930

3031
def getHikariDataSource(conf: PostgreSQLConfig): HikariDataSource =
32+
getHikariDataSource(conf, DatabasePoolConfig.load().getOrElse(DatabasePoolConfig.default))
33+
34+
def getHikariDataSource(conf: PostgreSQLConfig, poolConfig: DatabasePoolConfig): HikariDataSource =
3135
val config: HikariConfig = new HikariConfig()
3236
config.setDriverClassName(conf.driver)
3337
config.setJdbcUrl(conf.url)
3438
config.setUsername(conf.user)
3539
config.setPassword(conf.pass.decode)
40+
config.setMaximumPoolSize(poolConfig.maximumPoolSize)
41+
config.setMinimumIdle(poolConfig.minimumIdle)
42+
config.setConnectionTimeout(poolConfig.connectionTimeoutMs)
43+
config.setIdleTimeout(poolConfig.idleTimeoutMs)
44+
config.setMaxLifetime(poolConfig.maxLifetimeMs)
45+
config.setLeakDetectionThreshold(poolConfig.leakDetectionThresholdMs)
3646
new HikariDataSource(config)
3747

3848
def transactor(datasource: HikariDataSource): Resource[IO, HikariTransactor[IO]] =

modules/server/src/main/scala/scaladex/server/Server.scala

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ object Server extends LazyLogging:
184184
complete(StatusCodes.InternalServerError, notfound(config.env, None))
185185
}
186186
}
187-
handleExceptions(exceptionHandler)(route)
187+
188+
val loggingDirective =
189+
if config.requestLogging.enabled then
190+
if config.requestLogging.slowOnly then
191+
RequestLogging.logSlowRequests(config.requestLogging.slowThresholdMs)
192+
else
193+
RequestLogging.logRequests
194+
else
195+
pass
196+
197+
loggingDirective {
198+
handleExceptions(exceptionHandler)(route)
199+
}
188200
end configureRoutes
189201
end Server

modules/server/src/main/scala/scaladex/server/route/ProjectPages.scala

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class ProjectPages(
8585
artifactsParams { params =>
8686
getProjectOrRedirect(ref, user) { project =>
8787
val artifactsF = database.getProjectArtifacts(ref, artifactName, params.stableOnly)
88-
val headerF = projectService.getHeader(project).map(_.get)
88+
val headerF = projectService.getHeader(project)
8989
for artifacts <- artifactsF; header <- headerF yield
9090
val binaryVersions = artifacts
9191
.map(_.binaryVersion)
@@ -314,10 +314,13 @@ class ProjectPages(
314314

315315
private def getBadges(ref: Project.Reference, user: Option[UserState]): Route =
316316
getProjectOrRedirect(ref, user) { project =>
317-
for header <- projectService.getHeader(project).map(_.get) yield
318-
val artifact = header.getDefaultArtifact(None, None)
319-
val page = html.badges(env, user, project, header, artifact)
320-
complete(StatusCodes.OK, page)
317+
for header <- projectService.getHeader(project) yield
318+
header.flatMap(_.getDefaultArtifact(None, None)) match
319+
case Some(artifact) =>
320+
val page = html.badges(env, user, project, header, artifact)
321+
complete(StatusCodes.OK, page)
322+
case None =>
323+
complete(StatusCodes.NotFound)
321324
}
322325

323326
private val editForm: Directive1[Project.Settings] =

modules/template/src/main/twirl/scaladex/view/project/badges.scala.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
env: Env,
1414
user: Option[UserState],
1515
project: Project,
16-
header: ProjectHeader,
16+
header: Option[ProjectHeader],
1717
artifact: Artifact
1818
)
1919
@main(env, title = project.repository.value, user, extraMeta = project.twitterCard.toHeadMeta, extraMetaProperty = project.ogp.toHeadMetaProperty) {
2020
<main id="container-project">
21-
@headproject(env, user, project, Some(header), ProjectTab.Badges)
21+
@headproject(env, user, project, header, ProjectTab.Badges)
2222
<div class="container">
2323
<div class="content-project box project-badges">
2424
<section class="badge-section" id="latest">
@@ -27,7 +27,7 @@ <h2>Latest version</h2>
2727
<pre aria-label="Badge markdown" id="badge-markdown">[![@artifact.name Scala version support](@artifact.latestBadgeUrl(env))](@artifact.fullHttpUrl(env))</pre>
2828
<button class="btn btn-primary btn-copy pull-right" data-clipboard-target="badge-markdown">Copy Markdown</button>
2929
</section>
30-
@header.platforms(artifact.name).map { platform =>
30+
@header.toSeq.flatMap(_.platforms(artifact.name)).map { platform =>
3131
<section class="badge-section" id="@platform">
3232
<h2>@platform badge</h2>
3333
<img src="@artifact.badgeUrl(env, Some(platform))" />

modules/template/src/main/twirl/scaladex/view/project/versions.scala.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
env: Env,
1616
user: Option[UserState],
1717
project: Project,
18-
header: ProjectHeader,
18+
header: Option[ProjectHeader],
1919
artifactName: Artifact.Name,
2020
allBinaryVersions: Seq[BinaryVersion],
2121
artifactByVersions: SortedMap[(Instant, Version), Seq[Artifact]],
@@ -24,7 +24,7 @@
2424

2525
@main(env, title = project.repository.toString, user) {
2626
<main id="container-project">
27-
@headproject(env, user, project, Some(header), ProjectTab.Versions)
27+
@headproject(env, user, project, header, ProjectTab.Versions)
2828
<div class="container">
2929
<div class="content-project versions box" data-organization="@project.reference.organization"
3030
data-repository="@project.reference.repository">
@@ -34,7 +34,7 @@ <h2>
3434
<select class="selectpicker" title="Other Artifacts"
3535
data-style="btn-default" data-selected-text-format="static"
3636
onchange="window.location=this.value">
37-
@for(name <- header.allArtifactNames) {
37+
@for(name <- header.map(_.allArtifactNames).getOrElse(Seq.empty)) {
3838
<option value="@versionsUri(project.reference, name, params)" @if(name == artifactName) {selected}>
3939
@name
4040
</option>

0 commit comments

Comments
 (0)