Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,8 @@ val results = client.search(SQLQuery("SELECT * FROM users WHERE age > 25"))
// Type-safe queries with compile-time validation
case class User(id: String, name: String, age: Int)
val users: Source[User, NotUsed] = client.scrollAs[User](
"SELECT id, name, age FROM users WHERE active = true"
"SELECT id, name, age FROM users WHERE active = true",
client.defaultScrollConfig // the macro needs an explicit config; this one carries elastic.scroll.*
)
```

Expand Down
9 changes: 7 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ ThisBuild / organization := "app.softnetwork"

name := "softclient4es"

ThisBuild / version := "0.20.4"
ThisBuild / version := "0.21.0-SNAPSHOT"

ThisBuild / scalaVersion := scala213

Expand Down Expand Up @@ -183,7 +183,9 @@ lazy val testkit = Project(id = "softclient4es-core-testkit", base = file("testk
buildInfoKeys += BuildInfoKey("elasticVersion" -> elasticSearchVersion.value),
buildInfoObject := "SoftClient4esCoreTestkitBuildInfo",
organization := "app.softnetwork.elastic",
name := s"softclient4es-core-testkit"
name := s"softclient4es-core-testkit",
// the template compiles against logback (SlicedScrollCompletenessSpec log capture, #238)
libraryDependencies += "ch.qos.logback" % "logback-classic" % Versions.logback
)
.enablePlugins(BuildInfoPlugin)
.dependsOn(
Expand Down Expand Up @@ -224,6 +226,9 @@ def testkitProject(esVersion: String, ss: Def.SettingsDefinition*): Project = {
"org.apache.logging.log4j" % "log4j-api" % Versions.log4j,
// "org.apache.logging.log4j" % "log4j-slf4j-impl" % Versions.log4j,
"org.apache.logging.log4j" % "log4j-core" % Versions.log4j,
// SlicedScrollCompletenessSpec (#238) captures the client log through logback's
// ListAppender at compile time — declared, not inherited from persistence-core
"ch.qos.logback" % "logback-classic" % Versions.logback,
"app.softnetwork.persistence" %% "persistence-core-testkit" % Versions.genericPersistence,
"org.testcontainers" % "testcontainers-elasticsearch" % Versions.testContainers excludeAll (jacksonExclusions: _*),
"org.testcontainers" % "testcontainers-minio" % Versions.testContainers,
Expand Down
11 changes: 11 additions & 0 deletions core/src/main/resources/softnetwork-elastic.conf
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ elastic {
connection-timeout = 5s
socket-timeout = 30s

# Paged row extraction (scroll / PIT + search_after)
scroll {
# Rows per page. Larger pages cut round-trips linearly and raise in-flight memory linearly.
size = 1000
size = ${?ELASTIC_SCROLL_SIZE}
# Ceiling on concurrent PIT slices for a no-ORDER-BY extraction (ES 7.15+). The effective
# count is min(primary shards, max-slices); 1 disables slicing (sequential paging).
max-slices = 8
max-slices = ${?ELASTIC_SCROLL_MAX_SLICES}
}

# When enabled, result rows surface the Elasticsearch document id as an `_id` column.
# Disabled by default: SQL results carry only the selected columns.
include-document-id = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import java.time.Duration
* @param includeDocumentId
* When enabled, result rows surface the Elasticsearch document id as an `_id` column (disabled
* by default)
* @param scroll
* Paged row extraction settings (`elastic.scroll`: page size and the ceiling on concurrent PIT
* slices, #238)
*/
case class ElasticConfig(
credentials: ElasticCredentials = ElasticCredentials(),
Expand All @@ -51,7 +54,8 @@ case class ElasticConfig(
socketTimeout: Duration,
metrics: MetricsConfig,
watcher: ElasticCredentials,
includeDocumentId: Boolean = false)
includeDocumentId: Boolean = false,
scroll: ScrollSettings = ScrollSettings())

object ElasticConfig extends StrictLogging {
def apply(config: Config): ElasticConfig = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ import java.time.Duration
* @param includeDocumentId
* When enabled, result rows surface the Elasticsearch document id as an `_id` column (disabled
* by default)
* @param scroll
* Paged row extraction settings (`elastic.scroll`: page size and the ceiling on concurrent PIT
* slices, #238)
*/
case class ElasticConfig(
credentials: ElasticCredentials = ElasticCredentials(),
Expand All @@ -51,7 +54,8 @@ case class ElasticConfig(
socketTimeout: Duration,
metrics: MetricsConfig,
watcher: ElasticCredentials,
includeDocumentId: Boolean = false
includeDocumentId: Boolean = false,
scroll: ScrollSettings = ScrollSettings()
)

object ElasticConfig extends StrictLogging {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package app.softnetwork.elastic.client

import app.softnetwork.common.ClientCompanion
import app.softnetwork.elastic.client.scroll.ScrollConfig
import app.softnetwork.elastic.licensing.metrics.MetricsApi
import com.typesafe.config.{Config, ConfigFactory}
import org.slf4j.Logger
Expand Down Expand Up @@ -65,4 +66,14 @@ trait ElasticClientApi
* `elastic.include-document-id` (disabled by default).
*/
override protected def includeDocumentId: Boolean = elasticConfig.includeDocumentId

/** Paged row extraction defaults come from `elastic.scroll` (#238): the page size is applied
* here, the slice ceiling is inherited through `maxSlices = None` so an explicit
* `ScrollConfig(...)` still honours the HOCON/env opt-out. A `def` — see
* [[ScrollApi.defaultScrollConfig]].
*/
override def defaultScrollConfig: ScrollConfig =
ScrollConfig(scrollSize = elasticConfig.scroll.size)

override protected def configuredMaxSlices: Int = elasticConfig.scroll.maxSlices
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ object ElasticsearchVersion {
isAtLeast(version, 7, 12)
}

/** Check if PIT slicing is usable (ES >= 7.15).
*
* `slice` + `pit` in one search request exists from 7.15 (elastic/elasticsearch#74457); on
* 7.12–7.14 a PIT extraction pages sequentially (#238). Under a PIT, slices are contiguous
* doc-id ranges split first across shards — not the `_id` hash filter of sliced scroll.
*/
def supportsPitSlicing(version: String): Boolean = {
isAtLeast(version, 7, 15)
}

/** Check if version is ES 8+
*/
def isEs8OrHigher(version: String): Boolean = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ trait IndicesApi extends ElasticClientHelpers {

executeCreateIndex(index, settings, updatedMappings, aliases) match {
case success @ ElasticSuccess(true) =>
// #238 — a shard count cached for an expression this index is the stem of (a name probed
// before it existed, `orders*` before the load) must not survive the creation
invalidateShardCounts(Some(index))
logger.info(s"✅ Index '$index' created successfully")
success
case success @ ElasticSuccess(_) =>
Expand Down Expand Up @@ -230,16 +233,20 @@ trait IndicesApi extends ElasticClientHelpers {

def updateSchema(index: String, schema: Schema): Unit = {
schemaCache.put(index, (schema, System.currentTimeMillis()))
// #238 — ALTER TABLE may have reindexed into a different shard count
invalidateShardCounts(Some(index))
logger.debug(s"📦 Schema cache updated for '$index'")
}

def invalidateSchema(index: String): Unit = {
schemaCache.remove(index)
invalidateShardCounts(Some(index)) // #238 — the sliced-paging shard counts follow the schema
logger.info(s"🗑️ Schema cache invalidated for '$index'")
}

def invalidateAllSchemas(): Unit = {
schemaCache.clear()
invalidateShardCounts()
logger.info("🗑️ All schema caches invalidated")
}

Expand Down
Loading
Loading