[{"data":1,"prerenderedAt":1734},["ShallowReactive",2],{"navigation_docs":3,"-advanced-performance":106,"-advanced-performance-surround":1729},[4,26,57,83],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting Started","i-lucide-rocket","/getting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","/getting-started/introduction","1.getting-started/1.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","/getting-started/installation","1.getting-started/2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","/getting-started/quick-start","1.getting-started/3.quick-start","i-lucide-play",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Core Concepts","i-lucide-book-open","/essentials","2.essentials",[32,37,42,47,52],{"title":33,"path":34,"stem":35,"icon":36},"Models","/essentials/models","2.essentials/1.models","i-lucide-box",{"title":38,"path":39,"stem":40,"icon":41},"Collections","/essentials/collections","2.essentials/2.collections","i-lucide-folder",{"title":43,"path":44,"stem":45,"icon":46},"Views","/essentials/views","2.essentials/3.views","i-lucide-table",{"title":48,"path":49,"stem":50,"icon":51},"Schema Evolution","/essentials/schema-evolution","2.essentials/4.schema-evolution","i-lucide-git-branch",{"title":53,"path":54,"stem":55,"icon":56},"Relations","/essentials/relations","2.essentials/5.relations","i-lucide-link",{"title":58,"icon":59,"path":60,"stem":61,"children":62,"page":25},"Advanced","i-lucide-settings","/advanced","3.advanced",[63,68,73,78],{"title":64,"path":65,"stem":66,"icon":67},"Time-Series","/advanced/time-series","3.advanced/1.time-series","i-lucide-chart-line",{"title":69,"path":70,"stem":71,"icon":72},"Async Support","/advanced/async","3.advanced/2.async","i-lucide-zap",{"title":74,"path":75,"stem":76,"icon":77},"PostgreSQL","/advanced/postgresql","3.advanced/3.postgresql","i-lucide-database",{"title":79,"path":80,"stem":81,"icon":82},"Performance Guide","/advanced/performance","3.advanced/4.performance","i-lucide-gauge",{"title":84,"icon":85,"path":86,"stem":87,"children":88,"page":25},"API Reference","i-lucide-file-code","/api","4.api",[89,94,98,101],{"title":90,"path":91,"stem":92,"icon":93},"Engine","/api/engine","4.api/1.engine","i-lucide-cog",{"title":95,"path":96,"stem":97,"icon":41},"Collection","/api/collection","4.api/2.collection",{"title":33,"path":99,"stem":100,"icon":36},"/api/models","4.api/3.models",{"title":102,"path":103,"stem":104,"icon":105},"Exceptions","/api/exceptions","4.api/4.exceptions","i-lucide-alert-triangle",{"id":107,"title":79,"body":108,"description":1722,"extension":1723,"links":1724,"meta":1725,"navigation":1726,"path":80,"seo":1727,"stem":81,"__hash__":1728},"docs/3.advanced/4.performance.md",{"type":109,"value":110,"toc":1703},"minimark",[111,115,120,123,134,200,203,239,249,263,270,273,334,340,425,429,439,555,558,562,577,652,655,738,742,757,994,1002,1014,1046,1050,1059,1168,1172,1184,1189,1199,1299,1303,1313,1372,1376,1379,1397,1401,1408,1494,1501,1505,1509,1520,1523,1547,1554,1582,1588,1592,1699],[112,113,114],"p",{},"CentauroDB stores objects as JSON blobs. Without any tuning you get zero-migration flexibility — but raw JSON queries do a full table scan on every read. This guide covers the tools that turn that into fast, indexed access.",[116,117,119],"h2",{"id":118},"index-the-fields-you-query","Index the fields you query",[112,121,122],{},"Indexing is the single biggest lever for read performance. There are two ways to create indexes:",[112,124,125,133],{},[126,127,128,129],"strong",{},"Via ",[130,131,132],"code",{},"create_index()"," — create expression indexes on any field:",[135,136,141],"pre",{"className":137,"code":138,"language":139,"meta":140,"style":140},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","coll.create_index(\"unit\")       # idempotent — safe to call on every startup\ncoll.create_index(\"location\")\n","python","",[130,142,143,179],{"__ignoreMap":140},[144,145,148,152,156,160,163,166,170,172,175],"span",{"class":146,"line":147},"line",1,[144,149,151],{"class":150},"sTEyZ","coll",[144,153,155],{"class":154},"sMK4o",".",[144,157,159],{"class":158},"s2Zo4","create_index",[144,161,162],{"class":154},"(",[144,164,165],{"class":154},"\"",[144,167,169],{"class":168},"sfazB","unit",[144,171,165],{"class":154},[144,173,174],{"class":154},")",[144,176,178],{"class":177},"sHwdD","       # idempotent — safe to call on every startup\n",[144,180,182,184,186,188,190,192,195,197],{"class":146,"line":181},2,[144,183,151],{"class":150},[144,185,155],{"class":154},[144,187,159],{"class":158},[144,189,162],{"class":154},[144,191,165],{"class":154},[144,193,194],{"class":168},"location",[144,196,165],{"class":154},[144,198,199],{"class":154},")\n",[112,201,202],{},"This produces an expression index:",[135,204,208],{"className":205,"code":206,"language":207,"meta":140,"style":140},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","CREATE INDEX idx_monitoring_unit ON monitoring_objects (json_extract(meta, '$.unit'))\n","sql",[130,209,210],{"__ignoreMap":140},[144,211,212,216,219,222,225,228,231,234,236],{"class":146,"line":147},[144,213,215],{"class":214},"sbssI","CREATE",[144,217,218],{"class":214}," INDEX",[144,220,221],{"class":158}," idx_monitoring_unit",[144,223,224],{"class":214}," ON",[144,226,227],{"class":150}," monitoring_objects (json_extract(meta, ",[144,229,230],{"class":154},"'",[144,232,233],{"class":168},"$.unit",[144,235,230],{"class":154},[144,237,238],{"class":150},"))\n",[112,240,241,244,245,248],{},[126,242,243],{},"When to index:"," Add indexes for any field you filter, sort, or aggregate on frequently. Common candidates include status fields, category fields, timestamps, and foreign keys (",[130,246,247],{},"parent_ref"," fields).",[250,251,252,253,256,257,262],"callout",{"icon":14},"Indexes live on the ",[126,254,255],{},"objects table",", not on views — they are fully independent. See ",[258,259,261],"a",{"href":260},"/essentials/views#indexing-fields-for-performance","Views: Indexing fields"," for details.",[116,264,266,267],{"id":265},"limit-view-columns-with-include_fields","Limit view columns with ",[130,268,269],{},"include_fields",[112,271,272],{},"By default a view extracts every field from the model. On wide models this means parsing the full JSON blob for every row — even when you only need two columns.",[135,274,276],{"className":137,"code":275,"language":139,"meta":140,"style":140},"# 2 json_extract() calls per row instead of 10\ncoll.create_view(\"lite\", Sensor, include_fields=[\"unit\", \"location\"])\n",[130,277,278,283],{"__ignoreMap":140},[144,279,280],{"class":146,"line":147},[144,281,282],{"class":177},"# 2 json_extract() calls per row instead of 10\n",[144,284,285,287,289,292,294,296,299,301,304,307,309,313,316,318,320,322,324,327,329,331],{"class":146,"line":181},[144,286,151],{"class":150},[144,288,155],{"class":154},[144,290,291],{"class":158},"create_view",[144,293,162],{"class":154},[144,295,165],{"class":154},[144,297,298],{"class":168},"lite",[144,300,165],{"class":154},[144,302,303],{"class":154},",",[144,305,306],{"class":158}," Sensor",[144,308,303],{"class":154},[144,310,312],{"class":311},"sHdIc"," include_fields",[144,314,315],{"class":154},"=[",[144,317,165],{"class":154},[144,319,169],{"class":168},[144,321,165],{"class":154},[144,323,303],{"class":154},[144,325,326],{"class":154}," \"",[144,328,194],{"class":168},[144,330,165],{"class":154},[144,332,333],{"class":154},"])\n",[112,335,336,337,339],{},"Pair with ",[130,338,132],{}," for maximum impact:",[135,341,343],{"className":137,"code":342,"language":139,"meta":140,"style":140},"coll.create_view(\n    \"lite\",\n    Sensor,\n    include_fields=[\"unit\", \"location\"],\n)\ncoll.create_index(\"unit\")\n",[130,344,345,356,368,376,401,406],{"__ignoreMap":140},[144,346,347,349,351,353],{"class":146,"line":147},[144,348,151],{"class":150},[144,350,155],{"class":154},[144,352,291],{"class":158},[144,354,355],{"class":154},"(\n",[144,357,358,361,363,365],{"class":146,"line":181},[144,359,360],{"class":154},"    \"",[144,362,298],{"class":168},[144,364,165],{"class":154},[144,366,367],{"class":154},",\n",[144,369,371,374],{"class":146,"line":370},3,[144,372,373],{"class":158},"    Sensor",[144,375,367],{"class":154},[144,377,379,382,384,386,388,390,392,394,396,398],{"class":146,"line":378},4,[144,380,381],{"class":311},"    include_fields",[144,383,315],{"class":154},[144,385,165],{"class":154},[144,387,169],{"class":168},[144,389,165],{"class":154},[144,391,303],{"class":154},[144,393,326],{"class":154},[144,395,194],{"class":168},[144,397,165],{"class":154},[144,399,400],{"class":154},"],\n",[144,402,404],{"class":146,"line":403},5,[144,405,199],{"class":154},[144,407,409,411,413,415,417,419,421,423],{"class":146,"line":408},6,[144,410,151],{"class":150},[144,412,155],{"class":154},[144,414,159],{"class":158},[144,416,162],{"class":154},[144,418,165],{"class":154},[144,420,169],{"class":168},[144,422,165],{"class":154},[144,424,199],{"class":154},[116,426,428],{"id":427},"disable-assignment-validation-for-hot-paths","Disable assignment validation for hot paths",[112,430,431,434,435,438],{},[130,432,433],{},"CentauroModel"," enables ",[130,436,437],{},"validate_assignment=True"," by default, which re-runs Pydantic validation every time you set an attribute. This catches type errors early but adds overhead in tight loops.",[135,440,442],{"className":137,"code":441,"language":139,"meta":140,"style":140},"from pydantic import ConfigDict\n\nclass SensorReading(CentauroModel):\n    __centauro_name__ = \"SensorReading\"\n    model_config = ConfigDict(validate_assignment=False, populate_by_name=True)\n    unit: str = \"\"\n    value: float = 0.0\n",[130,443,444,459,465,482,498,522,539],{"__ignoreMap":140},[144,445,446,450,453,456],{"class":146,"line":147},[144,447,449],{"class":448},"s7zQu","from",[144,451,452],{"class":150}," pydantic ",[144,454,455],{"class":448},"import",[144,457,458],{"class":150}," ConfigDict\n",[144,460,461],{"class":146,"line":181},[144,462,464],{"emptyLinePlaceholder":463},true,"\n",[144,466,467,471,475,477,479],{"class":146,"line":370},[144,468,470],{"class":469},"spNyl","class",[144,472,474],{"class":473},"sBMFI"," SensorReading",[144,476,162],{"class":154},[144,478,433],{"class":473},[144,480,481],{"class":154},"):\n",[144,483,484,487,490,492,495],{"class":146,"line":378},[144,485,486],{"class":150},"    __centauro_name__ ",[144,488,489],{"class":154},"=",[144,491,326],{"class":154},[144,493,494],{"class":168},"SensorReading",[144,496,497],{"class":154},"\"\n",[144,499,500,503,505,508,510,513,516,519],{"class":146,"line":403},[144,501,502],{"class":150},"    model_config ",[144,504,489],{"class":154},[144,506,507],{"class":158}," ConfigDict",[144,509,162],{"class":154},[144,511,512],{"class":311},"validate_assignment",[144,514,515],{"class":154},"=False,",[144,517,518],{"class":311}," populate_by_name",[144,520,521],{"class":154},"=True)\n",[144,523,524,527,530,533,536],{"class":146,"line":408},[144,525,526],{"class":150},"    unit",[144,528,529],{"class":154},":",[144,531,532],{"class":473}," str",[144,534,535],{"class":154}," =",[144,537,538],{"class":154}," \"\"\n",[144,540,542,545,547,550,552],{"class":146,"line":541},7,[144,543,544],{"class":150},"    value",[144,546,529],{"class":154},[144,548,549],{"class":473}," float",[144,551,535],{"class":154},[144,553,554],{"class":214}," 0.0\n",[112,556,557],{},"Best for models that are created, immediately written, and never mutated after — such as append-only readings or log entries.",[116,559,561],{"id":560},"skip-value-hydration-when-you-dont-need-it","Skip value hydration when you don't need it",[112,563,564,565,568,569,572,573,576],{},"Reading a ",[130,566,567],{},"TimeSeriesCollection"," object with the default ",[130,570,571],{},"hydrate_values=True"," fires a second ",[130,574,575],{},"SELECT"," against the values table. If you only need metadata, skip it:",[135,578,580],{"className":137,"code":579,"language":139,"meta":140,"style":140},"obj = coll.read_object_by_id(42, Metric, hydrate_values=False)\nobj.unit       # available immediately\nobj.values     # empty — not loaded\nobj.df         # raises ValuesNotHydratedError until refresh() is called\n",[130,581,582,615,628,640],{"__ignoreMap":140},[144,583,584,587,589,592,594,597,599,602,604,607,609,612],{"class":146,"line":147},[144,585,586],{"class":150},"obj ",[144,588,489],{"class":154},[144,590,591],{"class":150}," coll",[144,593,155],{"class":154},[144,595,596],{"class":158},"read_object_by_id",[144,598,162],{"class":154},[144,600,601],{"class":214},"42",[144,603,303],{"class":154},[144,605,606],{"class":158}," Metric",[144,608,303],{"class":154},[144,610,611],{"class":311}," hydrate_values",[144,613,614],{"class":154},"=False)\n",[144,616,617,620,622,625],{"class":146,"line":181},[144,618,619],{"class":150},"obj",[144,621,155],{"class":154},[144,623,169],{"class":624},"swJcz",[144,626,627],{"class":177},"       # available immediately\n",[144,629,630,632,634,637],{"class":146,"line":370},[144,631,619],{"class":150},[144,633,155],{"class":154},[144,635,636],{"class":624},"values",[144,638,639],{"class":177},"     # empty — not loaded\n",[144,641,642,644,646,649],{"class":146,"line":378},[144,643,619],{"class":150},[144,645,155],{"class":154},[144,647,648],{"class":624},"df",[144,650,651],{"class":177},"         # raises ValuesNotHydratedError until refresh() is called\n",[112,653,654],{},"This is especially useful when listing many objects for a dashboard:",[135,656,658],{"className":137,"code":657,"language":139,"meta":140,"style":140},"# Fast: no values query per object\nall_metrics = coll.read_objects(Metric, hydrate_values=False)\nfor m in all_metrics:\n    print(m.unit, m.row.edit_time)\n",[130,659,660,665,690,707],{"__ignoreMap":140},[144,661,662],{"class":146,"line":147},[144,663,664],{"class":177},"# Fast: no values query per object\n",[144,666,667,670,672,674,676,679,681,684,686,688],{"class":146,"line":181},[144,668,669],{"class":150},"all_metrics ",[144,671,489],{"class":154},[144,673,591],{"class":150},[144,675,155],{"class":154},[144,677,678],{"class":158},"read_objects",[144,680,162],{"class":154},[144,682,683],{"class":158},"Metric",[144,685,303],{"class":154},[144,687,611],{"class":311},[144,689,614],{"class":154},[144,691,692,695,698,701,704],{"class":146,"line":370},[144,693,694],{"class":448},"for",[144,696,697],{"class":150}," m ",[144,699,700],{"class":448},"in",[144,702,703],{"class":150}," all_metrics",[144,705,706],{"class":154},":\n",[144,708,709,712,714,717,719,721,723,726,728,731,733,736],{"class":146,"line":378},[144,710,711],{"class":158},"    print",[144,713,162],{"class":154},[144,715,716],{"class":158},"m",[144,718,155],{"class":154},[144,720,169],{"class":624},[144,722,303],{"class":154},[144,724,725],{"class":158}," m",[144,727,155],{"class":154},[144,729,730],{"class":624},"row",[144,732,155],{"class":154},[144,734,735],{"class":624},"edit_time",[144,737,199],{"class":154},[116,739,741],{"id":740},"batch-populate-values-before-writing","Batch-populate values before writing",[112,743,744,745,748,749,752,753,756],{},"CentauroDB writes all ",[130,746,747],{},"obj.values"," in a single ",[130,750,751],{},"executemany"," call. Populate the list fully before calling ",[130,754,755],{},"write_object"," rather than appending and updating repeatedly:",[135,758,760],{"className":137,"code":759,"language":139,"meta":140,"style":140},"# Efficient: one INSERT executemany\nmetric = Metric(\n    unit=\"kg\",\n    values=[CentauroValues(time=t, value=v) for t, v in readings],\n)\ncoll.write_object(metric)\n\n# Inefficient: N separate UPDATE round-trips\nmetric = Metric(unit=\"kg\")\ncoll.write_object(metric)\nfor t, v in readings:\n    metric.values = [CentauroValues(time=t, value=v)]\n    coll.update_object(metric, values_mode=\"append\")\n",[130,761,762,767,778,793,843,847,862,866,872,895,910,927,963],{"__ignoreMap":140},[144,763,764],{"class":146,"line":147},[144,765,766],{"class":177},"# Efficient: one INSERT executemany\n",[144,768,769,772,774,776],{"class":146,"line":181},[144,770,771],{"class":150},"metric ",[144,773,489],{"class":154},[144,775,606],{"class":158},[144,777,355],{"class":154},[144,779,780,782,784,786,789,791],{"class":146,"line":370},[144,781,526],{"class":311},[144,783,489],{"class":154},[144,785,165],{"class":154},[144,787,788],{"class":168},"kg",[144,790,165],{"class":154},[144,792,367],{"class":154},[144,794,795,798,800,803,805,808,810,813,815,818,820,823,825,828,831,833,836,838,841],{"class":146,"line":378},[144,796,797],{"class":311},"    values",[144,799,315],{"class":154},[144,801,802],{"class":158},"CentauroValues",[144,804,162],{"class":154},[144,806,807],{"class":311},"time",[144,809,489],{"class":154},[144,811,812],{"class":158},"t",[144,814,303],{"class":154},[144,816,817],{"class":311}," value",[144,819,489],{"class":154},[144,821,822],{"class":158},"v",[144,824,174],{"class":154},[144,826,827],{"class":448}," for",[144,829,830],{"class":158}," t",[144,832,303],{"class":154},[144,834,835],{"class":158}," v ",[144,837,700],{"class":448},[144,839,840],{"class":158}," readings",[144,842,400],{"class":154},[144,844,845],{"class":146,"line":403},[144,846,199],{"class":154},[144,848,849,851,853,855,857,860],{"class":146,"line":408},[144,850,151],{"class":150},[144,852,155],{"class":154},[144,854,755],{"class":158},[144,856,162],{"class":154},[144,858,859],{"class":158},"metric",[144,861,199],{"class":154},[144,863,864],{"class":146,"line":541},[144,865,464],{"emptyLinePlaceholder":463},[144,867,869],{"class":146,"line":868},8,[144,870,871],{"class":177},"# Inefficient: N separate UPDATE round-trips\n",[144,873,875,877,879,881,883,885,887,889,891,893],{"class":146,"line":874},9,[144,876,771],{"class":150},[144,878,489],{"class":154},[144,880,606],{"class":158},[144,882,162],{"class":154},[144,884,169],{"class":311},[144,886,489],{"class":154},[144,888,165],{"class":154},[144,890,788],{"class":168},[144,892,165],{"class":154},[144,894,199],{"class":154},[144,896,898,900,902,904,906,908],{"class":146,"line":897},10,[144,899,151],{"class":150},[144,901,155],{"class":154},[144,903,755],{"class":158},[144,905,162],{"class":154},[144,907,859],{"class":158},[144,909,199],{"class":154},[144,911,913,915,917,919,921,923,925],{"class":146,"line":912},11,[144,914,694],{"class":448},[144,916,830],{"class":150},[144,918,303],{"class":154},[144,920,835],{"class":150},[144,922,700],{"class":448},[144,924,840],{"class":150},[144,926,706],{"class":154},[144,928,930,933,935,937,939,942,944,946,948,950,952,954,956,958,960],{"class":146,"line":929},12,[144,931,932],{"class":150},"    metric",[144,934,155],{"class":154},[144,936,636],{"class":624},[144,938,535],{"class":154},[144,940,941],{"class":154}," [",[144,943,802],{"class":158},[144,945,162],{"class":154},[144,947,807],{"class":311},[144,949,489],{"class":154},[144,951,812],{"class":158},[144,953,303],{"class":154},[144,955,817],{"class":311},[144,957,489],{"class":154},[144,959,822],{"class":158},[144,961,962],{"class":154},")]\n",[144,964,966,969,971,974,976,978,980,983,985,987,990,992],{"class":146,"line":965},13,[144,967,968],{"class":150},"    coll",[144,970,155],{"class":154},[144,972,973],{"class":158},"update_object",[144,975,162],{"class":154},[144,977,859],{"class":158},[144,979,303],{"class":154},[144,981,982],{"class":311}," values_mode",[144,984,489],{"class":154},[144,986,165],{"class":154},[144,988,989],{"class":168},"append",[144,991,165],{"class":154},[144,993,199],{"class":154},[116,995,997,998,1001],{"id":996},"choose-the-right-values_mode-for-updates","Choose the right ",[130,999,1000],{},"values_mode"," for updates",[112,1003,1004,1005,1007,1008,1010,1011,1013],{},"When calling ",[130,1006,973],{}," on a ",[130,1009,567],{},", the ",[130,1012,1000],{}," parameter has significant performance implications:",[1015,1016,1017,1026,1038],"card-group",{},[1018,1019,1022,1025],"card",{"icon":1020,"title":1021},"i-lucide-plus","append (default)",[130,1023,1024],{},"INSERT OR IGNORE"," — skips duplicates without error. Best for incremental ingestion where new timestamps are added over time.",[1018,1027,1030,1033,1034,1037],{"icon":1028,"title":1029},"i-lucide-refresh-cw","upsert",[130,1031,1032],{},"INSERT OR REPLACE"," / ",[130,1035,1036],{},"ON CONFLICT DO UPDATE"," — overwrites existing timestamps. Use when values can change (corrections, recalculations).",[1018,1039,1042,1045],{"icon":1040,"title":1041},"i-lucide-trash-2","replace",[130,1043,1044],{},"DELETE"," all values then bulk insert. Fastest for full-replacement scenarios, but briefly removes all historical data.",[116,1047,1049],{"id":1048},"query-via-views-for-complex-filters","Query via views for complex filters",[112,1051,1052,1054,1055,1058],{},[130,1053,678],{}," uses ",[130,1056,1057],{},"json_extract()"," in the WHERE clause and benefits from expression indexes. For multi-condition queries, creating a view and running raw SQL is often cleaner and can leverage query planner optimizations:",[135,1060,1062],{"className":137,"code":1061,"language":139,"meta":140,"style":140},"# Python DSL — good for simple filters\nresults = coll.read_objects(Sensor.fields.unit == \"Celsius\")\n\n# SQL via view — better for aggregations, JOINs, GROUP BY\ndf = coll.sql_select(\"\"\"\n    SELECT location, AVG(threshold) AS avg_threshold\n    FROM monitoring_view_dashboard\n    WHERE unit = 'Celsius'\n    GROUP BY location\n    ORDER BY avg_threshold DESC\n\"\"\")\n",[130,1063,1064,1069,1108,1112,1117,1136,1141,1146,1151,1156,1161],{"__ignoreMap":140},[144,1065,1066],{"class":146,"line":147},[144,1067,1068],{"class":177},"# Python DSL — good for simple filters\n",[144,1070,1071,1074,1076,1078,1080,1082,1084,1087,1089,1092,1094,1096,1099,1101,1104,1106],{"class":146,"line":181},[144,1072,1073],{"class":150},"results ",[144,1075,489],{"class":154},[144,1077,591],{"class":150},[144,1079,155],{"class":154},[144,1081,678],{"class":158},[144,1083,162],{"class":154},[144,1085,1086],{"class":158},"Sensor",[144,1088,155],{"class":154},[144,1090,1091],{"class":624},"fields",[144,1093,155],{"class":154},[144,1095,169],{"class":624},[144,1097,1098],{"class":154}," ==",[144,1100,326],{"class":154},[144,1102,1103],{"class":168},"Celsius",[144,1105,165],{"class":154},[144,1107,199],{"class":154},[144,1109,1110],{"class":146,"line":370},[144,1111,464],{"emptyLinePlaceholder":463},[144,1113,1114],{"class":146,"line":378},[144,1115,1116],{"class":177},"# SQL via view — better for aggregations, JOINs, GROUP BY\n",[144,1118,1119,1122,1124,1126,1128,1131,1133],{"class":146,"line":403},[144,1120,1121],{"class":150},"df ",[144,1123,489],{"class":154},[144,1125,591],{"class":150},[144,1127,155],{"class":154},[144,1129,1130],{"class":158},"sql_select",[144,1132,162],{"class":154},[144,1134,1135],{"class":154},"\"\"\"\n",[144,1137,1138],{"class":146,"line":408},[144,1139,1140],{"class":168},"    SELECT location, AVG(threshold) AS avg_threshold\n",[144,1142,1143],{"class":146,"line":541},[144,1144,1145],{"class":168},"    FROM monitoring_view_dashboard\n",[144,1147,1148],{"class":146,"line":868},[144,1149,1150],{"class":168},"    WHERE unit = 'Celsius'\n",[144,1152,1153],{"class":146,"line":874},[144,1154,1155],{"class":168},"    GROUP BY location\n",[144,1157,1158],{"class":146,"line":897},[144,1159,1160],{"class":168},"    ORDER BY avg_threshold DESC\n",[144,1162,1163,1166],{"class":146,"line":912},[144,1164,1165],{"class":154},"\"\"\"",[144,1167,199],{"class":154},[116,1169,1171],{"id":1170},"split-collections-for-table-level-performance","Split collections for table-level performance",[112,1173,1174,1175,1179,1180,1183],{},"By default, ",[258,1176,1178],{"href":1177},"/essentials/collections#one-collection-or-many","one collection per project"," is the right starting point. But as data grows, a single ",[130,1181,1182],{},"_objects"," table can become a bottleneck. Splitting into multiple collections gives each its own table, which helps in three specific scenarios:",[1185,1186,1188],"h3",{"id":1187},"write-volume-skew","Write-volume skew",[112,1190,1191,1192,1194,1195,1198],{},"If one model type produces far more rows than the rest (100x+), every ",[130,1193,678],{}," call on the quiet types still scans past the hot type's rows (even with an index on ",[130,1196,1197],{},"name",", the B-tree is larger). A dedicated collection keeps the hot table separate:",[135,1200,1202],{"className":137,"code":1201,"language":139,"meta":140,"style":140},"# Before — one table, 10M SensorReading rows slow down Config reads\napp = Collection(engine, \"app\")\n\n# After — hot writer gets its own table\nconfig  = Collection(engine, \"config\")          # small, fast scans\nreadings = TimeSeriesCollection(engine, \"readings\")  # millions of rows, isolated\n",[130,1203,1204,1209,1235,1239,1244,1271],{"__ignoreMap":140},[144,1205,1206],{"class":146,"line":147},[144,1207,1208],{"class":177},"# Before — one table, 10M SensorReading rows slow down Config reads\n",[144,1210,1211,1214,1216,1219,1221,1224,1226,1228,1231,1233],{"class":146,"line":181},[144,1212,1213],{"class":150},"app ",[144,1215,489],{"class":154},[144,1217,1218],{"class":158}," Collection",[144,1220,162],{"class":154},[144,1222,1223],{"class":158},"engine",[144,1225,303],{"class":154},[144,1227,326],{"class":154},[144,1229,1230],{"class":168},"app",[144,1232,165],{"class":154},[144,1234,199],{"class":154},[144,1236,1237],{"class":146,"line":370},[144,1238,464],{"emptyLinePlaceholder":463},[144,1240,1241],{"class":146,"line":378},[144,1242,1243],{"class":177},"# After — hot writer gets its own table\n",[144,1245,1246,1249,1251,1253,1255,1257,1259,1261,1264,1266,1268],{"class":146,"line":403},[144,1247,1248],{"class":150},"config  ",[144,1250,489],{"class":154},[144,1252,1218],{"class":158},[144,1254,162],{"class":154},[144,1256,1223],{"class":158},[144,1258,303],{"class":154},[144,1260,326],{"class":154},[144,1262,1263],{"class":168},"config",[144,1265,165],{"class":154},[144,1267,174],{"class":154},[144,1269,1270],{"class":177},"          # small, fast scans\n",[144,1272,1273,1276,1278,1281,1283,1285,1287,1289,1292,1294,1296],{"class":146,"line":408},[144,1274,1275],{"class":150},"readings ",[144,1277,489],{"class":154},[144,1279,1280],{"class":158}," TimeSeriesCollection",[144,1282,162],{"class":154},[144,1284,1223],{"class":158},[144,1286,303],{"class":154},[144,1288,326],{"class":154},[144,1290,1291],{"class":168},"readings",[144,1293,165],{"class":154},[144,1295,174],{"class":154},[144,1297,1298],{"class":177},"  # millions of rows, isolated\n",[1185,1300,1302],{"id":1301},"independent-retention","Independent retention",[112,1304,1305,1306,1308,1309,1312],{},"When you need to purge old data for one model type but keep others, a separate table lets you ",[130,1307,1044],{}," or ",[130,1310,1311],{},"DROP"," cheaply without locking or fragmenting the shared table:",[135,1314,1316],{"className":137,"code":1315,"language":139,"meta":140,"style":140},"logs    = Collection(engine, \"logs\")       # purge weekly\nsettings = Collection(engine, \"settings\")  # keep forever\n",[130,1317,1318,1345],{"__ignoreMap":140},[144,1319,1320,1323,1325,1327,1329,1331,1333,1335,1338,1340,1342],{"class":146,"line":147},[144,1321,1322],{"class":150},"logs    ",[144,1324,489],{"class":154},[144,1326,1218],{"class":158},[144,1328,162],{"class":154},[144,1330,1223],{"class":158},[144,1332,303],{"class":154},[144,1334,326],{"class":154},[144,1336,1337],{"class":168},"logs",[144,1339,165],{"class":154},[144,1341,174],{"class":154},[144,1343,1344],{"class":177},"       # purge weekly\n",[144,1346,1347,1350,1352,1354,1356,1358,1360,1362,1365,1367,1369],{"class":146,"line":181},[144,1348,1349],{"class":150},"settings ",[144,1351,489],{"class":154},[144,1353,1218],{"class":158},[144,1355,162],{"class":154},[144,1357,1223],{"class":158},[144,1359,303],{"class":154},[144,1361,326],{"class":154},[144,1363,1364],{"class":168},"settings",[144,1366,165],{"class":154},[144,1368,174],{"class":154},[144,1370,1371],{"class":177},"  # keep forever\n",[1185,1373,1375],{"id":1374},"wal-contention-sqlite","WAL contention (SQLite)",[112,1377,1378],{},"SQLite allows one writer at a time. If two parts of your app write frequently, putting them in the same collection means they compete for the same table lock. Separate collections don't eliminate the global write lock, but they reduce the time each write holds it (smaller indexes to update, smaller pages to flush).",[250,1380,1381,1382,1385,1386,1033,1388,1391,1392,1396],{"icon":14},"Do ",[126,1383,1384],{},"not"," split related models that use ",[130,1387,247],{},[130,1389,1390],{},"children_of"," — these only work within a single collection. See ",[258,1393,1395],{"href":1394},"/essentials/collections#choosing-a-collection-name","Choosing a collection name"," for the full decision guide.",[116,1398,1400],{"id":1399},"use-in-memory-sqlite-for-testing","Use in-memory SQLite for testing",[112,1402,1403,1404,1407],{},"The ",[130,1405,1406],{},":memory:"," engine skips all disk I/O. Use it for unit tests and batch processing:",[135,1409,1411],{"className":137,"code":1410,"language":139,"meta":140,"style":140},"# Unit tests — fast, isolated, no cleanup\nengine = Engine(\":memory:\")\n\n# Batch jobs — process → query → discard\nengine = Engine(\":memory:\")\ncoll = Collection(engine, \"results\")\n# ... heavy processing ...\n",[130,1412,1413,1418,1438,1442,1447,1465,1489],{"__ignoreMap":140},[144,1414,1415],{"class":146,"line":147},[144,1416,1417],{"class":177},"# Unit tests — fast, isolated, no cleanup\n",[144,1419,1420,1423,1425,1428,1430,1432,1434,1436],{"class":146,"line":181},[144,1421,1422],{"class":150},"engine ",[144,1424,489],{"class":154},[144,1426,1427],{"class":158}," Engine",[144,1429,162],{"class":154},[144,1431,165],{"class":154},[144,1433,1406],{"class":168},[144,1435,165],{"class":154},[144,1437,199],{"class":154},[144,1439,1440],{"class":146,"line":370},[144,1441,464],{"emptyLinePlaceholder":463},[144,1443,1444],{"class":146,"line":378},[144,1445,1446],{"class":177},"# Batch jobs — process → query → discard\n",[144,1448,1449,1451,1453,1455,1457,1459,1461,1463],{"class":146,"line":403},[144,1450,1422],{"class":150},[144,1452,489],{"class":154},[144,1454,1427],{"class":158},[144,1456,162],{"class":154},[144,1458,165],{"class":154},[144,1460,1406],{"class":168},[144,1462,165],{"class":154},[144,1464,199],{"class":154},[144,1466,1467,1470,1472,1474,1476,1478,1480,1482,1485,1487],{"class":146,"line":408},[144,1468,1469],{"class":150},"coll ",[144,1471,489],{"class":154},[144,1473,1218],{"class":158},[144,1475,162],{"class":154},[144,1477,1223],{"class":158},[144,1479,303],{"class":154},[144,1481,326],{"class":154},[144,1483,1484],{"class":168},"results",[144,1486,165],{"class":154},[144,1488,199],{"class":154},[144,1490,1491],{"class":146,"line":541},[144,1492,1493],{"class":177},"# ... heavy processing ...\n",[112,1495,1496,1497,1500],{},"For file-based SQLite, CentauroDB automatically enables ",[130,1498,1499],{},"PRAGMA journal_mode=WAL",", which allows readers and a single writer to operate concurrently.",[1502,1503,1504],"tip",{},"WAL mode is enabled automatically for file-based SQLite databases. No configuration needed.",[116,1506,1508],{"id":1507},"postgresql-jsonb-for-read-heavy-workloads","PostgreSQL JSONB for read-heavy workloads",[112,1510,1511,1512,1515,1516,1519],{},"When using PostgreSQL, CentauroDB stores the ",[130,1513,1514],{},"meta"," column as ",[130,1517,1518],{},"JSONB"," — pre-parsed binary JSON. This is faster for queries because the database doesn't re-parse the blob for each extraction.",[112,1521,1522],{},"Switching from SQLite requires no code changes:",[135,1524,1526],{"className":137,"code":1525,"language":139,"meta":140,"style":140},"engine = Engine(\"postgresql://user:pass@host/dbname\")\n",[130,1527,1528],{"__ignoreMap":140},[144,1529,1530,1532,1534,1536,1538,1540,1543,1545],{"class":146,"line":147},[144,1531,1422],{"class":150},[144,1533,489],{"class":154},[144,1535,1427],{"class":158},[144,1537,162],{"class":154},[144,1539,165],{"class":154},[144,1541,1542],{"class":168},"postgresql://user:pass@host/dbname",[144,1544,165],{"class":154},[144,1546,199],{"class":154},[112,1548,1549,1550,1553],{},"On PostgreSQL, expression indexes use the ",[130,1551,1552],{},"->>"," operator:",[135,1555,1557],{"className":205,"code":1556,"language":207,"meta":140,"style":140},"CREATE INDEX idx_monitoring_unit ON monitoring_objects ((meta->>'unit'))\n",[130,1558,1559],{"__ignoreMap":140},[144,1560,1561,1563,1565,1567,1569,1572,1574,1576,1578,1580],{"class":146,"line":147},[144,1562,215],{"class":214},[144,1564,218],{"class":214},[144,1566,221],{"class":158},[144,1568,224],{"class":214},[144,1570,1571],{"class":150}," monitoring_objects ((meta",[144,1573,1552],{"class":154},[144,1575,230],{"class":154},[144,1577,169],{"class":168},[144,1579,230],{"class":154},[144,1581,238],{"class":150},[112,1583,1584,1585,1587],{},"Use ",[130,1586,132],{}," to create them automatically.",[116,1589,1591],{"id":1590},"summary","Summary",[1593,1594,1595,1608],"table",{},[1596,1597,1598],"thead",{},[1599,1600,1601,1605],"tr",{},[1602,1603,1604],"th",{},"Technique",[1602,1606,1607],{},"When to use",[1609,1610,1611,1624,1634,1644,1654,1665,1675,1683,1691],"tbody",{},[1599,1612,1613,1619],{},[1614,1615,1616],"td",{},[130,1617,1618],{},"create_index(field)",[1614,1620,1621,1622,174],{},"Any field you filter or sort frequently (speeds up both views and ",[130,1623,678],{},[1599,1625,1626,1631],{},[1614,1627,1628,1630],{},[130,1629,269],{}," on views",[1614,1632,1633],{},"Wide models where you only query a few fields",[1599,1635,1636,1641],{},[1614,1637,1638],{},[130,1639,1640],{},"validate_assignment=False",[1614,1642,1643],{},"Append-only / high-throughput write models",[1599,1645,1646,1651],{},[1614,1647,1648],{},[130,1649,1650],{},"hydrate_values=False",[1614,1652,1653],{},"Reading many series objects without needing their values",[1599,1655,1656,1662],{},[1614,1657,1658,1659],{},"Batch-populate ",[130,1660,1661],{},".values",[1614,1663,1664],{},"Bulk ingestion of time-series data",[1599,1666,1667,1672],{},[1614,1668,1669],{},[130,1670,1671],{},"values_mode=\"replace\"",[1614,1673,1674],{},"Full replacement of time-series data",[1599,1676,1677,1680],{},[1614,1678,1679],{},"Split collections",[1614,1681,1682],{},"Write-volume skew, independent retention, WAL contention",[1599,1684,1685,1688],{},[1614,1686,1687],{},"In-memory engine",[1614,1689,1690],{},"Tests and ephemeral batch jobs",[1599,1692,1693,1696],{},[1614,1694,1695],{},"PostgreSQL + JSONB",[1614,1697,1698],{},"High read concurrency, large datasets",[1700,1701,1702],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":140,"searchDepth":181,"depth":181,"links":1704},[1705,1706,1708,1709,1710,1711,1713,1714,1719,1720,1721],{"id":118,"depth":181,"text":119},{"id":265,"depth":181,"text":1707},"Limit view columns with include_fields",{"id":427,"depth":181,"text":428},{"id":560,"depth":181,"text":561},{"id":740,"depth":181,"text":741},{"id":996,"depth":181,"text":1712},"Choose the right values_mode for updates",{"id":1048,"depth":181,"text":1049},{"id":1170,"depth":181,"text":1171,"children":1715},[1716,1717,1718],{"id":1187,"depth":370,"text":1188},{"id":1301,"depth":370,"text":1302},{"id":1374,"depth":370,"text":1375},{"id":1399,"depth":181,"text":1400},{"id":1507,"depth":181,"text":1508},{"id":1590,"depth":181,"text":1591},"Indexes, view tuning, and model-level optimizations to get the most out of CentauroDB.","md",null,{},{"icon":82},{"title":79,"description":1722},"L4UOMjTJCG9CwqYLsZMJvPF0NJ9q0qVZt7tVpIWQNm4",[1730,1732],{"title":74,"path":75,"stem":76,"description":1731,"icon":77,"children":-1},"Switch from SQLite to PostgreSQL — same API, server-grade backend.",{"title":90,"path":91,"stem":92,"description":1733,"icon":93,"children":-1},"API reference for the Engine class.",1781105062740]