tradeit.gg

CROSS-CUTTING INFRA · FOUNDATION TIER · 2026-05-10

OpenSearch Patterns

Cluster topology, rick/morty alias-swap reindex, reserved-asset must_not filter, marketplace fanout — the inventory search engine of tradeit.gg.

Read on Wiki: /engineering/backend/opensearch · companion to Redis Architecture · Configuration
3
Entity types
inv·grp·stk
2
Generations
rick·morty
5
Games indexed
2 500
Docs / bulk
15 min
Lane-lock TTL

Contents


1. At-a-glance

Self-hosted OpenSearch cluster (eu-west-1, ARM/Graviton m7g) is the marketplace search engine for tradeit.gg. Every storefront filter, group-rollup, and trade-availability query reads from it; no inventory query hits MySQL. Writes go through a strict rick/morty alias-swap contract in tradeit-inventory-parser — bulk-write to the inactive generation, atomic alias swap, delete the old physical index. The dual-client topology (coordination node for reads, ingest node for writes) is mirrored in all four OS-consumer services. Reservation filtering uses must_not: terms { assetId } — fast for small reserved sets, CPU-heavy at scale (the reserved-assets-os-field migration is queued to replace it).

2. Purpose & boundaries

Owns

  • The full inventory.* / group.* / stickers.* index family — every (game × marketplace × generation) tuple
  • The alias-swap reindex contract — atomic publish, zero-downtime cutover, 30%-failure kill-switch
  • The reserved-asset exclusion path (must_not: terms { assetId })
  • The marketplace fanout naming contract via @zengamingx/opensearch-indexing-contract

Does NOT own

  • PricingstorePrice / priceForTrade are stored in OS docs but computed by pricing-manager
  • Reservation lifecycle — Redis is source of truth; OS only filters at read time
  • Inventory aggregation — sinv/cinv built by tradeit-inventory-server; parser fetches the snapshot
  • Cluster provisioning — managed via tradeit-infra, out of scope here

3. Process / runtime model

Self-hosted OpenSearch in AWS eu-west-1, ARM/Graviton (m7g). Role-split topology with mTLS. All four client services share an identical dual-client pattern (coord for search, ingest for writes).

Cluster nodes

RoleHostsPurposeClient env var
Master3Cluster state, leader electionn/a
Coordination1 dedicatedRoutes search queries → data nodes; aggregates resultsOPENSEARCH_COORDINATION_NODE_URL
Ingest1 dedicatedRoutes bulk writes / index adminOPENSEARCH_INGEST_NODE_URL
Data4 (m7g)Hold shards, run queriesvia coord/ingest

Client services

ServiceRoleOS clientSource
tradeit-backend (monolith)search reads + reservation queriesdual (coord + ingest)server/opensearch.js:12–45
tradeit-inventory-parserindexing pipeline (writer)dualopenSearch.manager.ts:11–42
tradeit-service (scheduler)scroll exports, SIH metricsdual (older lib ^3.3.0)openSearch/OpenSearch.ts:6–40
tradeit-admin-backendadmin search/reindex toolingsingle (ingest-style)openSearchClient.ts

mTLS

All four clients decode three base64 env vars at boot to build the TLS context: OPENSEARCH_SSL_CA, OPENSEARCH_SSL_CERT, OPENSEARCH_SSL_KEY — ASCII/UTF-8 PEM strings (opensearch.js:15–19, openSearch.manager.ts:13–15). Self-managed certs — see §11 for the historical expiry incident.

4. Data flow diagrams

4.1 Indexing flow — sinv → parser → alias swap

graph TB
    subgraph "Source"
        S1["Steam Web API"]
        S2["3rd-party marketplaces
(UU, halo, …)"] end subgraph "Aggregation (Redis)" SINV["sinv:{gameId}:compressed
tradeit-inventory-server/sinvbot"] CINV["cinv (consumer view)"] end subgraph "Parser (NestJS)" CTRL["GET /index/:gameId
inventory.controller.ts"] LOCK["Redis lane lock
15 min TTL"] SVC["openSearchService.index()
bulk writes"] end subgraph "OpenSearch (eu-west-1)" ING["Ingest node"] DAT["Data nodes ×4"] ALIAS["alias inventory_730_trade
→ rick OR morty"] end S1 --> SINV S2 --> SINV SINV --> CTRL CINV --> CTRL CTRL --> LOCK LOCK --> SVC SVC --> ING ING --> DAT SVC -->|"POST /_aliases
atomic remove+add"| ALIAS ALIAS --> DAT classDef src fill:#1e2438,stroke:#a855f7,stroke-width:2px,color:#e9d5ff classDef redis fill:#0e2a1e,stroke:#10b981,stroke-width:2px,color:#a7f3d0 classDef parser fill:#0c2536,stroke:#06b6d4,stroke-width:2px,color:#a5f3fc classDef os fill:#2a1f0e,stroke:#f59e0b,stroke-width:2px,color:#fde68a class S1,S2 src class SINV,CINV,LOCK redis class CTRL,SVC parser class ING,DAT,ALIAS os

4.2 Query flow — search read path

sequenceDiagram
    participant FE as new-tradeit (Nuxt)
    participant BE as tradeit-backend
    participant CFG as configurations
    participant R as Redis (reserve_assets)
    participant OS as OpenSearch (coord)

    FE->>BE: GET /inventory?gameId=730&filter=…
    BE->>CFG: getValueByKey('enableTradeLocked')
    BE->>CFG: getValueByKey('hideBotsItemsFromInventory')
    BE->>R: getAllReserveAssets(gameId)
    BE->>BE: getEnabledMarketplaceKeys() → [null,'uu','halo',…]
    BE->>BE: aliases = keys.map(buildInventoryAlias)
    BE->>OS: msearch across N aliases
    Note over BE,OS: must_not: terms{assetId: reserved}
+ terms{botIndex: hidden}
+ filters (price, float, stickers, …) OS-->>BE: hits + by_item agg buckets BE-->>FE: merged + ordered result set

4.3 Rick/Morty alias-swap state machine

graph LR
    subgraph "State A — alias points to RICK"
        A_RICK["inventory_730_rick
(active, queryable)"] A_MORTY["inventory_730_morty
(empty / stale)"] A_ALIAS{{"alias inventory_730"}} A_ALIAS -->|points to| A_RICK end subgraph "Reindex into MORTY" B1["1. ensureIndexExists(morty)"] B2["2. setRefreshInterval('-1')"] B3["3. bulkWrite chunks"] B4["4. refreshIndex(morty)"] B5["5. POST /_aliases
remove rick → add morty"] B6["6. delete rick"] B7["finally: setRefreshInterval('1s')"] end subgraph "State B — alias points to MORTY" C_MORTY["inventory_730_morty
(active)"] C_ALIAS{{"alias inventory_730"}} C_ALIAS -->|points to| C_MORTY end A_RICK --> B1 --> B2 --> B3 --> B4 --> B5 --> B6 --> B7 B7 --> C_MORTY classDef ok fill:#0e2a1e,stroke:#10b981,stroke-width:2px,color:#a7f3d0 classDef warn fill:#2a1f0e,stroke:#f59e0b,stroke-width:2px,color:#fde68a classDef hi fill:#1e2438,stroke:#a855f7,stroke-width:2px,color:#e9d5ff class A_RICK,C_MORTY ok class A_MORTY warn class B1,B2,B3,B4,B5,B6,B7 hi

5. Inputs & outputs

Writes (parser → OS)

ChannelSourceDestinationNotes
in HTTP GET /index/:gameIdexternal triggerinventory.controller.ts:54–55Per-game full reindex, lane-locked
in HTTP GET /index/:gameId/:marketplaceexternal triggerinventory.controller.ts:129Per-marketplace partial reindex
out openSearchService.index()controlleropenSearch.service.ts:73–74Drives the rick/morty cycle

Reads (backend / tradeit-service → OS)

CallerMethodEndpoint(s) hit
inventory.js (backend ctrl)getMyReservedItems, getAvailableAssetsinventory_*, group_* aliases
store.js (backend ctrl)purchasetradeService.checkAvailableItemsinventory_* aliases
trade.js (backend ctrl)acceptVirtualTradequeryByAssetIdsinventory_* aliases (openSearchService.js:726)
tradeit-service ExportProductevery 8 hscroll over inventory_* per game
tradeit-service ExportSIHevery 5 min × 4 gamesaggregations on inventory_*
No queues. OpenSearch is not a Bull/pub-sub continuation point. Parser has no Bull queue and no @nestjs/schedule cron — only HTTP. Index refresh is HTTP-triggered (see §9 [OPEN] on the trigger source).

6. Redis surface

Key patternDirectionPurposeSource
reserve_assets:{gameId}:{itemId} (sets)OS readReserved asset IDs filtered out via must_not termsopenSearchService.js:312, 685–686, 859, 990–995
lane:reindex:{gameId} (lock value)parser write15-min TTL Redis-script lane lock with renewal at half-TTLinventory.controller.ts:209–225

The lane lock uses a Lua EVAL compareAndExpireScript for safe renewal — only the holder can extend (inventory.controller.ts:223). Lock holder spawns a background renewal interval at 7.5 min; releases on completion. For the full Redis keyspace see Redis Architecture.

7. MySQL surface

OS does not query MySQL directly. The dependency is one-directional:

MySQL is not a fallback for OS — when OS is down, the backend's only fallback is the degraded "own-inventory only" alias path (§11).

8. OpenSearch surface

8.1 Naming contract — @zengamingx/opensearch-indexing-contract v0.4.0

Single source of truth for every alias and index name. Repo: ~/Projects/opensearch-indexing-contract.

Grammar:
alias        = entityType "_" gameId [ "_" marketplaceKey ] [ envSuffix ]
physicalIdx  = alias "_" generation
ComponentValuesDefined
entityTypeinventory · group · stickerssrc/indexNames.ts:1–4
gameId730 (CSGO) · 570 (DOTA2) · 252490 (RUST) · 440 (TEAMFOR) · 753 (STEAM)src/constants/games.ts:1–6
marketplaceKeyoptional, lowercased — e.g. uu, halo, omitted = "own inventory"indexContract.ts:normalizeMarketplaceKey
envSuffixempty in production; _dev etc. for non-prodindexContract.ts:getEnvSuffix
generationrick · mortysrc/constants/generations.ts:1–3

Production examples

AliasPhysical (rick or morty)Holds
inventory_730inventory_730_rick / …_mortyown CSGO inventory
inventory_730_uuinventory_730_uu_rick / …_mortyUU-marketplace CSGO inventory
group_730group_730_rick / …_mortyCSGO group rollups
stickers_730stickers_730_rick / …_mortyCSGO stickers
inventory_730_haloinventory_730_halo_rick / …_mortyhalo-marketplace CSGO inventory

Public API (src/index.ts)

FunctionPurposeLine
buildAliasName(input)join entityType + game + marketplace + envindexContract.ts:67–92
buildPhysicalIndexName(input)${alias}_${generation}indexContract.ts:74–76
buildInventoryAlias / buildGroupAlias / buildStickerAliasentity-type-specific wrappersindexContract.ts:87–98
parsePhysicalIndexName(name, env?)reverse parse → all componentsindexContract.ts:50–115
getNextGeneration(prevName?)_rick ⇒ Morty; _morty ⇒ Rick; null ⇒ RickindexContract.ts:117–120
MarketplaceDescriptor + resolveMarketplaceKeysFromDescriptorsruntime config → enabled marketplace keysmarketplaceDescriptors.ts:1–100

8.2 Document mapping — fields actually queried

FieldTypeUsed for
assetIdkeywordreserved must_not (:319, 686, 829, 873, 995); item lookup
groupIdkeywordterm/terms filter (:214, 1034); by_item aggregation
floatValue / floatValuesfloat / nestedrange filters
storePrice, priceForTradescaled_floatprice range, sort, top_hits aggregation
hasStattrakbooleanStatTrak filter (:243)
hasStickers, stickersboolean / nestedsticker filter (:241–242)
metaMappings.{type,rarity,collection}keywordfacet filters
botIndexintegerhide-bots filter (:322, 493)
tradeLockDay, tradedAtinteger / datetrade-lock gating
steamTagstextStatTrak match phrase, free-text search
saleOfferOwnerkeywordexists filter for user listings
nametext + match (fuzziness AUTO)search box
phase, colorskeyworddoppler / colour filters
onlyStorebooleantrade-vs-store gating
idkeyword_id source field
classIdkeyword (index: false)stored, not queryable
stickerSlotsobject (enabled: false)stored, not queryable
steamIdkeywordowner lookup

_id of inventory docs = assetId; _id of group docs = groupId. Bulk write at openSearch.service.ts:prepareListForIndex sets _id: item.id.

8.3 Query DSL composition

openSearchService.js builds one query body per request, conditionally pushing clauses:

8.4 Bulk-write contract (parser)

ConstantValueWhere
MAX_DOCS_PER_BULK2 500openSearch.service.ts:21
MAX_BULK_PAYLOAD_BYTES8 MB:22
MAX_BULK_RETRIES3:23
RETRYABLE_BULK_STATUSES{429, 502, 503, 504}:24
Backoff250 ms × 2^attempt (exp):381–411
Bulk timeout120 ssubmitBulkChunk
Refresh during bulkrefresh_interval = '-1':182, 217–225
Refresh after'1s' in finally:228–230
Oversized docskipped + warn-logged:303–306

8.5 Alias-swap mechanism

replaceAliasForIndex(previous, next, alias) at openSearch.service.ts:136+:

if (previous) {
  POST /_aliases
  { actions: [
    { remove: { index: previous, alias } },
    { add:    { index: next,     alias } }
  ]}
} else {
  PUT /{next}/_alias/{alias}
}
// post-swap: cat.aliases — verify alias points at `next`, throw otherwise
// then: DELETE /{previous}

The POST /_aliases is atomic in OpenSearch — the alias never points at two indices nor zero indices. Post-swap verification via cat.aliases (:110) prevents silent alias drift.

8.6 Slowlog thresholds

Phasetracedebuginfowarn
Indexing50 ms100 ms200 ms500 ms
Search (query)20 ms50 ms100 ms200 ms
Search (fetch)10 ms50 ms100 ms100 ms

Source: inventory_template cluster definition (referenced from the reserved-assets-os-field design spec).

9. Async continuations

OpenSearch is involved in no Bull queue and no pub/sub channel in tradeit. The async surface is exclusively HTTP + cron-style external triggers.

TriggerWhereCadenceCalls
GET /index/:gameIdexternal (see [OPEN])~every 6 min per gameparser → ingest → alias swap
GET /index/:gameId/:marketplaceexternalper-marketplace partialscoped reindex
ExportProduct.start()tradeit-service/AppSchedule.ts:150 */8 * * * (every 8 h)scroll over inventory_* per game
ExportSIH.start(gameId)AppSchedule.ts:16–19*/5 * * * * (×4 games)aggregations on inventory_*

Continuations from OS writes: none. After a successful alias swap the parser silently completes; there is no "indexed" event published to Redis or anywhere else.

10. Configuration flags & guards

configurations table keys

KeyTypeRead atEffect
enableTradeLockedbool stringopenSearchService.js:214, 662 · def enums.js:742Toggles whether trade-locked items are visible / range-filtered
hideBotsItemsFromInventoryJSON array of botIndex intsopenSearchService.js:315–316 · def enums.js:854Adds terms{botIndex:[...]} to must_not
showUserListingrequest param:235 (approx)exists saleOfferOwner must_not toggle
stickerrequest param:241–242sticker exists/term filter
statTrakrequest param:243hasStattrak term + steamTags StatTrak™ match
Marketplace configsmarketplaceConfigService rows:83–96Drives getEnabledMarketplaceKeys → which marketplace aliases to fanout

Env vars (parser + backend + service + admin-backend)

VarUsed bySource
OPENSEARCH_COORDINATION_NODE_URLsearch readsopensearch.js:28, openSearch.manager.ts:21
OPENSEARCH_COORDINATION_NODE_USER / _PASSbasic auth (search)opensearch.js:29–31, openSearch.manager.ts:23–24
OPENSEARCH_INGEST_NODE_URLbulk writesopensearch.js:35, openSearch.manager.ts:36
OPENSEARCH_INDEX_NODE_USER / _PASSbasic auth (ingest)opensearch.js:37–39, openSearch.manager.ts:38–39
OPENSEARCH_SSL_CA / _CERT / _KEYmTLS, base64 → PEMopensearch.js:15–19, openSearch.manager.ts:13–15
TI_ENV / NODE_ENVenvSuffix selectoropenSearchService.js:28, 68
INV_PRIVATE_IP_URLparser → inventory-server fetchinventory.controller.ts

Backlinks for these keys are registered on the Configuration page (id 65).

11. Failure modes

Index not found

Symptom: index_not_found_exception in error response (alias points to deleted phys idx, or fanout names a missing alias). Detection: openSearchService.js:381–399 — direct .error + nested root_cause[] of search_phase_execution_exception. Recovery: auto-retry with surviving aliases (:443–495); if all missing → degraded fallback.

Degraded fallback

Symptom: OS query throws (cluster offline / all aliases missing). Detection: openSearchService.js:503–530. Recovery: serve own-inventory alone via getInventoryAlias(appId, null); warn-log. The user sees a thinner inventory, not a blank page.

Bulk partial-write storm (>30 % failures)

Symptom: Parser throws "Alias swap aborted: N/M docs permanently failed". Detection: openSearch.service.ts:185–193 kill-switch. Recovery: Old alias is preserved — safe to retry once cause fixed. Investigate via Slack permanent-failure alert.

Bulk transient 429/502/503/504

Symptom: Bulk attempt logs retry. Detection: openSearch.service.ts:381. Recovery: 3-attempt exp-backoff retry built in (250 ms × 2^attempt); permanent on attempt 4.

Refresh-interval stuck at -1

Symptom: New writes invisible to search. Slowlog query latency drops to zero. Recovery: restoreRefreshInterval in finally (:198, 228–230) is the safety net. Manual fix: PUT /{idx}/_settings { refresh_interval: '1s' }.

Lane-lock leak (parser crashed mid-reindex)

Symptom: Subsequent reindex attempts blocked 15 min on lane:reindex:{gameId}. Recovery: Auto-expires at TTL. Manual DEL if urgent (verify no live reindex first).

mTLS cert expiry (historical: 2025)

Symptom: All OS calls fail with TLS handshake error across all four client services simultaneously. Detection: Datadog opensearch.error.rate spike. Recovery: Rotate cert in env vars (base64) + restart all four OS-client services. Watch: self-managed certs have expired before — monitor proactively.

x_content_parse_exception in queryByAssetIds

Symptom: Item-lookup queries fail with malformed JSON. Detection: openSearchService.js:773–786. Recovery: Slack alert with full query payload; investigate offending assetIds set (often non-stringified bigint).

Circuit-breaker trips under heavy reindex

Symptom: Coordinator rejects with 429s; OS _cluster/health yellow. Recovery: Reduce parser concurrency. The bulk retry policy absorbs short bursts. Consider scheduling reindexes off-peak.

Linked runbooks: Incident playbook (id 43, 44).

12. Observability

13. Code map

Backend — ~/Projects/tradeit-backend/server/

FileLinesResponsibility
opensearch.js1–80dual-client init, mTLS, attachOpenSearch middleware
opensearch.js12–45OpenSearch.init() — coord + ingest clients
opensearch.js51–73attachOpenSearch — req hydration + 500 error path
opensearch.js75–80extractHitsFromSearchResults, extractHitsFromGetResults helpers
service/openSearchService.js60–80getInventoryAlias, getGroupInventoryAlias, getStickerAlias
service/openSearchService.js83–114getEnabledMarketplaceKeys + alias fan-out builders
service/openSearchService.js214enableTradeLocked config read
service/openSearchService.js243–249mustNotGroup builder
service/openSearchService.js312–326reserved-assets + bot-hide must_not composition
service/openSearchService.js381–399index_not_found_exception extraction
service/openSearchService.js422–495retry-with-surviving-aliases recovery
service/openSearchService.js503–530degraded "own inventory only" fallback
service/openSearchService.js660–700queryByAssetIds-adjacent must_not push (:686)
service/openSearchService.js726–790queryByAssetIds; Slack alert on x_content_parse_exception (:773–786)
service/openSearchService.js810–880getReservedAssetIds + nested terms must_not
service/openSearchService.js980–1000inventory-data API must_not: terms{assetId} (:995)
service/openSearchService.js1030–1050by_item aggregation + top_hits price extraction
repository/marketplaceConfigRepo.js1–10imports MarketplaceContext from indexing-contract
service/marketplace/marketplaceDescriptors.js1+concrete MarketplaceDescriptor registry (UU, halo, …)
service/marketplaceConfigService.js1+resolves enabled marketplaces per (game × context)
controllers/inventory.js17, 39–65getMyReservedItems, getAvailableAssets
controllers/store.js34–40purchasecheckAvailableItems
controllers/trade.js59–62acceptVirtualTradequeryByAssetIds
config/enums.js742, 854configurationKeys.enableTradeLocked / hideBotsItemsFromInventory

Parser — ~/Projects/tradeit-inventory-parser/src/

FileLinesResponsibility
common/modules/opensearch/openSearch.manager.ts11–42dual-client init (NestJS, ConfigService, mTLS)
common/modules/opensearch/openSearch.manager.ts47–52getSearchClient, getIngestClient
common/modules/opensearch/openSearch.service.ts21–24bulk constants (DOCS, payload, retries, retryable statuses)
common/modules/opensearch/openSearch.service.ts73–74inventory + group getNextIndex
common/modules/opensearch/openSearch.service.ts100–134getNextIndexcat.aliases, derives next via getNextGeneration
common/modules/opensearch/openSearch.service.ts136–214replaceAliasForIndex — atomic POST /_aliases, post-verify, delete previous
common/modules/opensearch/openSearch.service.ts181–199full reindex flow: ensure → setRefresh(-1) → bulk → kill-switch → refresh → swap → finally restore
common/modules/opensearch/openSearch.service.ts185–19330%-failure kill-switch
common/modules/opensearch/openSearch.service.ts202–215ensureIndexExists (swallows resource_already_exists_exception)
common/modules/opensearch/openSearch.service.ts217–230setRefreshInterval, restoreRefreshInterval
common/modules/opensearch/openSearch.service.ts272–360bulkWrite, createBulkChunks (size + bytes)
common/modules/opensearch/openSearch.service.ts361–411bulk retry loop, exp backoff, permanent-vs-retryable categorisation
modules/inventoryIndex/inventory.controller.ts32laneLockTtlSeconds = 15 * 60
modules/inventoryIndex/inventory.controller.ts54–55@Get('/index/:gameId')
modules/inventoryIndex/inventory.controller.ts129@Get('/index/:gameId/:marketplace')
modules/inventoryIndex/inventory.controller.ts154, 195openSearchService.index(gameId, items, groups, marketplaceKey)
modules/inventoryIndex/inventory.controller.ts209–225Redis lane lock + half-TTL renewal (Lua EVAL)
modules/inventoryIndex/inventory.service.ts35–37, 71enrichment via SaleOfferRepo / CsgoCollectionRepo / ItemTypeRepo

Indexing contract — ~/Projects/opensearch-indexing-contract/src/

FileLinesResponsibility
index.ts1–7public re-exports
indexNames.ts1–4EntityTypes enum (Inventory / Group / Stickers)
constants/games.ts1–6GameId enum
constants/contexts.ts1–3Context enum (Store / Trade)
constants/generations.ts1–3Generations enum (Rick / Morty)
indexContract.ts1–115buildAliasName, buildPhysicalIndexName, parsePhysicalIndexName
indexContract.ts87–98buildInventoryAlias, buildGroupAlias, buildStickerAlias
indexContract.ts117–120getNextGeneration
marketplaceDescriptors.ts1–222MarketplaceDescriptor interface, resolveMarketplaceKeysFromDescriptors, factories

Other consumers

Repo / fileRole
tradeit-service/openSearch/OpenSearch.ts:6–40dual-client (older ^3.3.0 lib)
tradeit-service/openSearch/InventoryOS.ts:42–88scroll API + count-by-group
tradeit-service/AppSchedule.ts:15–26ExportProduct (8 h) + ExportSIH ×4 (5 min) cron entry points
tradeit-admin-backend/openSearchClient.{ts,js}admin-only client
tradeit-admin-backend/services/openSearchService.{ts,js}admin reindex / inspection tooling

14. Open questions

[OPEN] Who calls GET /inventory/index/:gameId?

No caller found in tradeit-backend (grep returned 0 hits for the path). Likely candidates: a Cronitor-pinged cron job in tradeit-infra, or tradeit-inventory-server/sinvbot.ts after a sinv-merge cycle. Action: confirm and document the trigger.

[OPEN] Index template provisioning

inventory_template is referenced everywhere but no repo holds the PUT _index_template/inventory_template JSON in source control. Lives only on the cluster. Risk: rebuild from scratch is undocumented. Action: extract via GET /_index_template/inventory_template and check it into tradeit-infra.

[OPEN] Shards / replicas — where are they declared?

Current shape is "2 shards + 1 replica for heavy inventory*", set at index creation time. The contract package has no opinion; the parser's ensureIndexExists (:202) creates indices without explicit settings. Are shard counts inherited from the index template, or set manually on cluster bootstrap?

[OPEN] Inactive-generation cleanup timing

replaceAliasForIndex deletes the old physical index after the alias swap (:209+). If the delete fails, the old index sticks around as orphan. Is there any janitor sweep, or does the next reindex's getNextGeneration overwrite by recreating? Verify on the cluster.

[OPEN] useOsReservation config flag

The v0 wiki cited this key, but it's not present in enums.js nor in any code-path grep. Confirm whether it was renamed (enableTradeLocked covers part of the same gate) or removed. Update v0 reference accordingly.

[OPEN] Datadog dashboard URL

Datadog integration emits opensearch.* metrics, but no canonical dashboard link. Action: add the URL and link from §12.

[OPEN] Lane-lock holder identification

lane:reindex:{gameId} value is set to lockValue (presumably a UUID); document the value format for forensic recovery.


Cross-references

Generated 2026-05-10 · tradeit.gg engineering