CROSS-CUTTING INFRA · FOUNDATION TIER · 2026-05-10
Cluster topology, rick/morty alias-swap reindex, reserved-asset must_not filter, marketplace fanout — the inventory search engine of tradeit.gg.
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).
inventory.* / group.* / stickers.* index family — every (game × marketplace × generation) tuplemust_not: terms { assetId })@zengamingx/opensearch-indexing-contractstorePrice / priceForTrade are stored in OS docs but computed by pricing-managertradeit-inventory-server; parser fetches the snapshottradeit-infra, out of scope hereSelf-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).
| Role | Hosts | Purpose | Client env var |
|---|---|---|---|
| Master | 3 | Cluster state, leader election | n/a |
| Coordination | 1 dedicated | Routes search queries → data nodes; aggregates results | OPENSEARCH_COORDINATION_NODE_URL |
| Ingest | 1 dedicated | Routes bulk writes / index admin | OPENSEARCH_INGEST_NODE_URL |
| Data | 4 (m7g) | Hold shards, run queries | via coord/ingest |
| Service | Role | OS client | Source |
|---|---|---|---|
tradeit-backend (monolith) | search reads + reservation queries | dual (coord + ingest) | server/opensearch.js:12–45 |
tradeit-inventory-parser | indexing pipeline (writer) | dual | openSearch.manager.ts:11–42 |
tradeit-service (scheduler) | scroll exports, SIH metrics | dual (older lib ^3.3.0) | openSearch/OpenSearch.ts:6–40 |
tradeit-admin-backend | admin search/reindex tooling | single (ingest-style) | openSearchClient.ts |
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.
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
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
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
| Channel | Source | Destination | Notes |
|---|---|---|---|
in HTTP GET /index/:gameId | external trigger | inventory.controller.ts:54–55 | Per-game full reindex, lane-locked |
in HTTP GET /index/:gameId/:marketplace | external trigger | inventory.controller.ts:129 | Per-marketplace partial reindex |
out openSearchService.index() | controller | openSearch.service.ts:73–74 | Drives the rick/morty cycle |
| Caller | Method | Endpoint(s) hit |
|---|---|---|
inventory.js (backend ctrl) | getMyReservedItems, getAvailableAssets | inventory_*, group_* aliases |
store.js (backend ctrl) | purchase → tradeService.checkAvailableItems | inventory_* aliases |
trade.js (backend ctrl) | acceptVirtualTrade → queryByAssetIds | inventory_* aliases (openSearchService.js:726) |
tradeit-service ExportProduct | every 8 h | scroll over inventory_* per game |
tradeit-service ExportSIH | every 5 min × 4 games | aggregations on inventory_* |
@nestjs/schedule cron — only HTTP. Index refresh is HTTP-triggered (see §9 [OPEN] on the trigger source).
| Key pattern | Direction | Purpose | Source |
|---|---|---|---|
reserve_assets:{gameId}:{itemId} (sets) | OS read | Reserved asset IDs filtered out via must_not terms | openSearchService.js:312, 685–686, 859, 990–995 |
lane:reindex:{gameId} (lock value) | parser write | 15-min TTL Redis-script lane lock with renewal at half-TTL | inventory.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.
OS does not query MySQL directly. The dependency is one-directional:
inventory.service.ts reads csgo_collections, item_types, sale_offers to enrich docs before indexing (inventory.service.ts:35–37, 40, 71).configurationService keys (§10) live in the configurations table.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).
@zengamingx/opensearch-indexing-contract v0.4.0Single source of truth for every alias and index name. Repo: ~/Projects/opensearch-indexing-contract.
alias = entityType "_" gameId [ "_" marketplaceKey ] [ envSuffix ]
physicalIdx = alias "_" generation
| Component | Values | Defined |
|---|---|---|
entityType | inventory · group · stickers | src/indexNames.ts:1–4 |
gameId | 730 (CSGO) · 570 (DOTA2) · 252490 (RUST) · 440 (TEAMFOR) · 753 (STEAM) | src/constants/games.ts:1–6 |
marketplaceKey | optional, lowercased — e.g. uu, halo, omitted = "own inventory" | indexContract.ts:normalizeMarketplaceKey |
envSuffix | empty in production; _dev etc. for non-prod | indexContract.ts:getEnvSuffix |
generation | rick · morty | src/constants/generations.ts:1–3 |
| Alias | Physical (rick or morty) | Holds |
|---|---|---|
inventory_730 | inventory_730_rick / …_morty | own CSGO inventory |
inventory_730_uu | inventory_730_uu_rick / …_morty | UU-marketplace CSGO inventory |
group_730 | group_730_rick / …_morty | CSGO group rollups |
stickers_730 | stickers_730_rick / …_morty | CSGO stickers |
inventory_730_halo | inventory_730_halo_rick / …_morty | halo-marketplace CSGO inventory |
src/index.ts)| Function | Purpose | Line |
|---|---|---|
buildAliasName(input) | join entityType + game + marketplace + env | indexContract.ts:67–92 |
buildPhysicalIndexName(input) | ${alias}_${generation} | indexContract.ts:74–76 |
buildInventoryAlias / buildGroupAlias / buildStickerAlias | entity-type-specific wrappers | indexContract.ts:87–98 |
parsePhysicalIndexName(name, env?) | reverse parse → all components | indexContract.ts:50–115 |
getNextGeneration(prevName?) | _rick ⇒ Morty; _morty ⇒ Rick; null ⇒ Rick | indexContract.ts:117–120 |
MarketplaceDescriptor + resolveMarketplaceKeysFromDescriptors | runtime config → enabled marketplace keys | marketplaceDescriptors.ts:1–100 |
| Field | Type | Used for |
|---|---|---|
assetId | keyword | reserved must_not (:319, 686, 829, 873, 995); item lookup |
groupId | keyword | term/terms filter (:214, 1034); by_item aggregation |
floatValue / floatValues | float / nested | range filters |
storePrice, priceForTrade | scaled_float | price range, sort, top_hits aggregation |
hasStattrak | boolean | StatTrak filter (:243) |
hasStickers, stickers | boolean / nested | sticker filter (:241–242) |
metaMappings.{type,rarity,collection} | keyword | facet filters |
botIndex | integer | hide-bots filter (:322, 493) |
tradeLockDay, tradedAt | integer / date | trade-lock gating |
steamTags | text | StatTrak match phrase, free-text search |
saleOfferOwner | keyword | exists filter for user listings |
name | text + match (fuzziness AUTO) | search box |
phase, colors | keyword | doppler / colour filters |
onlyStore | boolean | trade-vs-store gating |
id | keyword | _id source field |
classId | keyword (index: false) | stored, not queryable |
stickerSlots | object (enabled: false) | stored, not queryable |
steamId | keyword | owner lookup |
_id of inventory docs = assetId; _id of group docs = groupId. Bulk write at openSearch.service.ts:prepareListForIndex sets _id: item.id.
openSearchService.js builds one query body per request, conditionally pushing clauses:
bool.filter): groupId/groupIds term(s), price range, float range, sticker exists, StatTrak match, trade-lock range, colors, onlyStorebool.must_not): built by mustNotGroup (:243–249) — composes terms{assetId:reservedAssets} (:319, 492, 686, 829, 873, 995), terms{botIndex:hideBotsItemsFromInventory} (:322, 493), exists{saleOfferOwner} when showUserListing=falsebool.must): free-text on name — prefix-search subqueries + match with fuzziness AUTOby_item: terms{field:groupId, size:itemIds.length} with two child filters (store_items, trade_items) each containing a top_hits (size 1, sort by storePrice / priceForTrade asc) — produces "cheapest item per group" rollup (:1034–1047)| Constant | Value | Where |
|---|---|---|
MAX_DOCS_PER_BULK | 2 500 | openSearch.service.ts:21 |
MAX_BULK_PAYLOAD_BYTES | 8 MB | :22 |
MAX_BULK_RETRIES | 3 | :23 |
RETRYABLE_BULK_STATUSES | {429, 502, 503, 504} | :24 |
| Backoff | 250 ms × 2^attempt (exp) | :381–411 |
| Bulk timeout | 120 s | submitBulkChunk |
| Refresh during bulk | refresh_interval = '-1' | :182, 217–225 |
| Refresh after | '1s' in finally | :228–230 |
| Oversized doc | skipped + warn-logged | :303–306 |
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.
| Phase | trace | debug | info | warn |
|---|---|---|---|---|
| Indexing | 50 ms | 100 ms | 200 ms | 500 ms |
| Search (query) | 20 ms | 50 ms | 100 ms | 200 ms |
| Search (fetch) | 10 ms | 50 ms | 100 ms | 100 ms |
Source: inventory_template cluster definition (referenced from the reserved-assets-os-field design spec).
OpenSearch is involved in no Bull queue and no pub/sub channel in tradeit. The async surface is exclusively HTTP + cron-style external triggers.
| Trigger | Where | Cadence | Calls |
|---|---|---|---|
GET /index/:gameId | external (see [OPEN]) | ~every 6 min per game | parser → ingest → alias swap |
GET /index/:gameId/:marketplace | external | per-marketplace partial | scoped reindex |
ExportProduct.start() | tradeit-service/AppSchedule.ts:15 | 0 */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.
| Key | Type | Read at | Effect |
|---|---|---|---|
enableTradeLocked | bool string | openSearchService.js:214, 662 · def enums.js:742 | Toggles whether trade-locked items are visible / range-filtered |
hideBotsItemsFromInventory | JSON array of botIndex ints | openSearchService.js:315–316 · def enums.js:854 | Adds terms{botIndex:[...]} to must_not |
showUserListing | request param | :235 (approx) | exists saleOfferOwner must_not toggle |
sticker | request param | :241–242 | sticker exists/term filter |
statTrak | request param | :243 | hasStattrak term + steamTags StatTrak™ match |
| Marketplace configs | marketplaceConfigService rows | :83–96 | Drives getEnabledMarketplaceKeys → which marketplace aliases to fanout |
| Var | Used by | Source |
|---|---|---|
OPENSEARCH_COORDINATION_NODE_URL | search reads | opensearch.js:28, openSearch.manager.ts:21 |
OPENSEARCH_COORDINATION_NODE_USER / _PASS | basic auth (search) | opensearch.js:29–31, openSearch.manager.ts:23–24 |
OPENSEARCH_INGEST_NODE_URL | bulk writes | opensearch.js:35, openSearch.manager.ts:36 |
OPENSEARCH_INDEX_NODE_USER / _PASS | basic auth (ingest) | opensearch.js:37–39, openSearch.manager.ts:38–39 |
OPENSEARCH_SSL_CA / _CERT / _KEY | mTLS, base64 → PEM | opensearch.js:15–19, openSearch.manager.ts:13–15 |
TI_ENV / NODE_ENV | envSuffix selector | openSearchService.js:28, 68 |
INV_PRIVATE_IP_URL | parser → inventory-server fetch | inventory.controller.ts |
Backlinks for these keys are registered on the Configuration page (id 65).
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.
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.
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.
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.
-1Symptom: 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' }.
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).
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 queryByAssetIdsSymptom: 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).
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).
opensearch.* integration metrics (CPU, JVM, query/index latency, circuit breaker). Service-level: opensearch.error.rate, opensearch.slow_queries.count from slowlog ingestion. [OPEN: dashboard URL]inventory_template) — every query >200 ms / index >500 ms hits the warn channel; tail via _cat/indices/{idx}?v.x_content_parse_exception on queryByAssetIds (openSearchService.js:773–786); bulk permanent-failure surfaces from submitBulkChunk (parser).createModuleLogger('opensearch') at opensearch.js:7; parser ClassWithLogger mixin for every OS service.GET /index/:gameId is responsible — see [OPEN]).~/Projects/tradeit-backend/server/| File | Lines | Responsibility |
|---|---|---|
opensearch.js | 1–80 | dual-client init, mTLS, attachOpenSearch middleware |
opensearch.js | 12–45 | OpenSearch.init() — coord + ingest clients |
opensearch.js | 51–73 | attachOpenSearch — req hydration + 500 error path |
opensearch.js | 75–80 | extractHitsFromSearchResults, extractHitsFromGetResults helpers |
service/openSearchService.js | 60–80 | getInventoryAlias, getGroupInventoryAlias, getStickerAlias |
service/openSearchService.js | 83–114 | getEnabledMarketplaceKeys + alias fan-out builders |
service/openSearchService.js | 214 | enableTradeLocked config read |
service/openSearchService.js | 243–249 | mustNotGroup builder |
service/openSearchService.js | 312–326 | reserved-assets + bot-hide must_not composition |
service/openSearchService.js | 381–399 | index_not_found_exception extraction |
service/openSearchService.js | 422–495 | retry-with-surviving-aliases recovery |
service/openSearchService.js | 503–530 | degraded "own inventory only" fallback |
service/openSearchService.js | 660–700 | queryByAssetIds-adjacent must_not push (:686) |
service/openSearchService.js | 726–790 | queryByAssetIds; Slack alert on x_content_parse_exception (:773–786) |
service/openSearchService.js | 810–880 | getReservedAssetIds + nested terms must_not |
service/openSearchService.js | 980–1000 | inventory-data API must_not: terms{assetId} (:995) |
service/openSearchService.js | 1030–1050 | by_item aggregation + top_hits price extraction |
repository/marketplaceConfigRepo.js | 1–10 | imports MarketplaceContext from indexing-contract |
service/marketplace/marketplaceDescriptors.js | 1+ | concrete MarketplaceDescriptor registry (UU, halo, …) |
service/marketplaceConfigService.js | 1+ | resolves enabled marketplaces per (game × context) |
controllers/inventory.js | 17, 39–65 | getMyReservedItems, getAvailableAssets |
controllers/store.js | 34–40 | purchase → checkAvailableItems |
controllers/trade.js | 59–62 | acceptVirtualTrade → queryByAssetIds |
config/enums.js | 742, 854 | configurationKeys.enableTradeLocked / hideBotsItemsFromInventory |
~/Projects/tradeit-inventory-parser/src/| File | Lines | Responsibility |
|---|---|---|
common/modules/opensearch/openSearch.manager.ts | 11–42 | dual-client init (NestJS, ConfigService, mTLS) |
common/modules/opensearch/openSearch.manager.ts | 47–52 | getSearchClient, getIngestClient |
common/modules/opensearch/openSearch.service.ts | 21–24 | bulk constants (DOCS, payload, retries, retryable statuses) |
common/modules/opensearch/openSearch.service.ts | 73–74 | inventory + group getNextIndex |
common/modules/opensearch/openSearch.service.ts | 100–134 | getNextIndex — cat.aliases, derives next via getNextGeneration |
common/modules/opensearch/openSearch.service.ts | 136–214 | replaceAliasForIndex — atomic POST /_aliases, post-verify, delete previous |
common/modules/opensearch/openSearch.service.ts | 181–199 | full reindex flow: ensure → setRefresh(-1) → bulk → kill-switch → refresh → swap → finally restore |
common/modules/opensearch/openSearch.service.ts | 185–193 | 30%-failure kill-switch |
common/modules/opensearch/openSearch.service.ts | 202–215 | ensureIndexExists (swallows resource_already_exists_exception) |
common/modules/opensearch/openSearch.service.ts | 217–230 | setRefreshInterval, restoreRefreshInterval |
common/modules/opensearch/openSearch.service.ts | 272–360 | bulkWrite, createBulkChunks (size + bytes) |
common/modules/opensearch/openSearch.service.ts | 361–411 | bulk retry loop, exp backoff, permanent-vs-retryable categorisation |
modules/inventoryIndex/inventory.controller.ts | 32 | laneLockTtlSeconds = 15 * 60 |
modules/inventoryIndex/inventory.controller.ts | 54–55 | @Get('/index/:gameId') |
modules/inventoryIndex/inventory.controller.ts | 129 | @Get('/index/:gameId/:marketplace') |
modules/inventoryIndex/inventory.controller.ts | 154, 195 | openSearchService.index(gameId, items, groups, marketplaceKey) |
modules/inventoryIndex/inventory.controller.ts | 209–225 | Redis lane lock + half-TTL renewal (Lua EVAL) |
modules/inventoryIndex/inventory.service.ts | 35–37, 71 | enrichment via SaleOfferRepo / CsgoCollectionRepo / ItemTypeRepo |
~/Projects/opensearch-indexing-contract/src/| File | Lines | Responsibility |
|---|---|---|
index.ts | 1–7 | public re-exports |
indexNames.ts | 1–4 | EntityTypes enum (Inventory / Group / Stickers) |
constants/games.ts | 1–6 | GameId enum |
constants/contexts.ts | 1–3 | Context enum (Store / Trade) |
constants/generations.ts | 1–3 | Generations enum (Rick / Morty) |
indexContract.ts | 1–115 | buildAliasName, buildPhysicalIndexName, parsePhysicalIndexName |
indexContract.ts | 87–98 | buildInventoryAlias, buildGroupAlias, buildStickerAlias |
indexContract.ts | 117–120 | getNextGeneration |
marketplaceDescriptors.ts | 1–222 | MarketplaceDescriptor interface, resolveMarketplaceKeysFromDescriptors, factories |
| Repo / file | Role |
|---|---|
tradeit-service/openSearch/OpenSearch.ts:6–40 | dual-client (older ^3.3.0 lib) |
tradeit-service/openSearch/InventoryOS.ts:42–88 | scroll API + count-by-group |
tradeit-service/AppSchedule.ts:15–26 | ExportProduct (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 |
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.
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.
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?
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.
useOsReservation config flagThe 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.
Datadog integration emits opensearch.* metrics, but no canonical dashboard link. Action: add the URL and link from §12.
lane:reindex:{gameId} value is set to lockValue (presumably a UUID); document the value format for forensic recovery.
~/Projects/tradeit-backend/docs/superpowers/specs/2026-05-10-reserved-assets-os-field-design.md — the proposed must_not.terms → reservedUntil field migrationGenerated 2026-05-10 · tradeit.gg engineering