querysub 0.432.0 → 0.436.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +50 -50
- package/bin/deploy.js +0 -0
- package/bin/function.js +0 -0
- package/bin/server.js +0 -0
- package/costsBenefits.txt +115 -115
- package/deploy.ts +2 -2
- package/package.json +1 -1
- package/spec.txt +1192 -1192
- package/src/-a-archives/archives.ts +202 -202
- package/src/-a-archives/archivesBackBlaze.ts +1 -0
- package/src/-a-archives/archivesDisk.ts +454 -454
- package/src/-a-auth/certs.ts +540 -540
- package/src/-a-auth/node-forge-ed25519.d.ts +16 -16
- package/src/-b-authorities/dnsAuthority.ts +138 -138
- package/src/-c-identity/IdentityController.ts +258 -258
- package/src/-d-trust/NetworkTrust2.ts +180 -180
- package/src/-e-certs/EdgeCertController.ts +252 -252
- package/src/-e-certs/certAuthority.ts +201 -201
- package/src/-f-node-discovery/NodeDiscovery.ts +640 -640
- package/src/-g-core-values/NodeCapabilities.ts +200 -200
- package/src/-h-path-value-serialize/stringSerializer.ts +175 -175
- package/src/0-path-value-core/PathValueCommitter.ts +468 -468
- package/src/0-path-value-core/pathValueCore.ts +2 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +2542 -2542
- package/src/2-proxy/TransactionDelayer.ts +94 -94
- package/src/2-proxy/pathDatabaseProxyBase.ts +36 -36
- package/src/2-proxy/pathValueProxy.ts +159 -159
- package/src/3-path-functions/PathFunctionRunnerMain.ts +87 -87
- package/src/3-path-functions/pathFunctionLoader.ts +516 -512
- package/src/3-path-functions/tests/rejectTest.ts +76 -76
- package/src/4-deploy/deployCheck.ts +6 -6
- package/src/4-dom/css.tsx +29 -29
- package/src/4-dom/cssTypes.d.ts +211 -211
- package/src/4-dom/qreact.tsx +2799 -2799
- package/src/4-dom/qreactTest.tsx +410 -410
- package/src/4-querysub/permissions.ts +335 -335
- package/src/4-querysub/querysubPrediction.ts +483 -483
- package/src/5-diagnostics/qreactDebug.tsx +346 -346
- package/src/TestController.ts +34 -34
- package/src/bits.ts +104 -104
- package/src/buffers.ts +69 -69
- package/src/diagnostics/ActionsHistory.ts +57 -57
- package/src/diagnostics/listenOnDebugger.ts +71 -71
- package/src/diagnostics/periodic.ts +111 -111
- package/src/diagnostics/trackResources.ts +91 -91
- package/src/diagnostics/watchdog.ts +120 -120
- package/src/errors.ts +133 -133
- package/src/forceProduction.ts +2 -2
- package/src/fs.ts +80 -80
- package/src/functional/diff.ts +857 -857
- package/src/functional/promiseCache.ts +78 -78
- package/src/functional/random.ts +8 -8
- package/src/functional/stats.ts +60 -60
- package/src/heapDumps.ts +665 -665
- package/src/https.ts +1 -1
- package/src/library-components/AspectSizedComponent.tsx +87 -87
- package/src/library-components/ButtonSelector.tsx +64 -64
- package/src/library-components/DropdownCustom.tsx +150 -150
- package/src/library-components/DropdownSelector.tsx +31 -31
- package/src/library-components/InlinePopup.tsx +66 -66
- package/src/misc/color.ts +29 -29
- package/src/misc/hash.ts +83 -83
- package/src/misc/ipPong.js +13 -13
- package/src/misc/networking.ts +1 -1
- package/src/misc/random.ts +44 -44
- package/src/misc.ts +196 -196
- package/src/path.ts +255 -255
- package/src/persistentLocalStore.ts +41 -41
- package/src/promise.ts +14 -14
- package/src/storage/fileSystemPointer.ts +71 -71
- package/src/test/heapProcess.ts +35 -35
- package/src/zip.ts +15 -15
- package/tsconfig.json +26 -26
- package/yarnSpec.txt +56 -56
package/.eslintrc.js
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
"root": true,
|
|
3
|
-
"env": {
|
|
4
|
-
"browser": true,
|
|
5
|
-
"es6": true,
|
|
6
|
-
"node": true
|
|
7
|
-
},
|
|
8
|
-
"extends": [],
|
|
9
|
-
"globals": {
|
|
10
|
-
"Atomics": "readonly",
|
|
11
|
-
"SharedArrayBuffer": "readonly"
|
|
12
|
-
},
|
|
13
|
-
"overrides": [
|
|
14
|
-
{
|
|
15
|
-
"files": [
|
|
16
|
-
"**/*.ts",
|
|
17
|
-
"**/*.tsx"
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
],
|
|
21
|
-
"parser": "@typescript-eslint/parser",
|
|
22
|
-
"parserOptions": {
|
|
23
|
-
"ecmaVersion": 2018,
|
|
24
|
-
"sourceType": "module",
|
|
25
|
-
"project": "./tsconfig.json",
|
|
26
|
-
"tsconfigRootDir": __dirname,
|
|
27
|
-
},
|
|
28
|
-
"plugins": [
|
|
29
|
-
"@typescript-eslint"
|
|
30
|
-
],
|
|
31
|
-
"rules": {
|
|
32
|
-
"@typescript-eslint/unbound-method": [
|
|
33
|
-
"error",
|
|
34
|
-
{
|
|
35
|
-
"ignoreStatic": true
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
"@typescript-eslint/no-floating-promises": [
|
|
39
|
-
"error"
|
|
40
|
-
],
|
|
41
|
-
"quotes": [
|
|
42
|
-
"error",
|
|
43
|
-
"double",
|
|
44
|
-
{
|
|
45
|
-
"allowTemplateLiterals": true
|
|
46
|
-
}
|
|
47
|
-
],
|
|
48
|
-
"eqeqeq": "error",
|
|
49
|
-
"semi": "error"
|
|
50
|
-
}
|
|
1
|
+
module.exports = {
|
|
2
|
+
"root": true,
|
|
3
|
+
"env": {
|
|
4
|
+
"browser": true,
|
|
5
|
+
"es6": true,
|
|
6
|
+
"node": true
|
|
7
|
+
},
|
|
8
|
+
"extends": [],
|
|
9
|
+
"globals": {
|
|
10
|
+
"Atomics": "readonly",
|
|
11
|
+
"SharedArrayBuffer": "readonly"
|
|
12
|
+
},
|
|
13
|
+
"overrides": [
|
|
14
|
+
{
|
|
15
|
+
"files": [
|
|
16
|
+
"**/*.ts",
|
|
17
|
+
"**/*.tsx"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"parser": "@typescript-eslint/parser",
|
|
22
|
+
"parserOptions": {
|
|
23
|
+
"ecmaVersion": 2018,
|
|
24
|
+
"sourceType": "module",
|
|
25
|
+
"project": "./tsconfig.json",
|
|
26
|
+
"tsconfigRootDir": __dirname,
|
|
27
|
+
},
|
|
28
|
+
"plugins": [
|
|
29
|
+
"@typescript-eslint"
|
|
30
|
+
],
|
|
31
|
+
"rules": {
|
|
32
|
+
"@typescript-eslint/unbound-method": [
|
|
33
|
+
"error",
|
|
34
|
+
{
|
|
35
|
+
"ignoreStatic": true
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"@typescript-eslint/no-floating-promises": [
|
|
39
|
+
"error"
|
|
40
|
+
],
|
|
41
|
+
"quotes": [
|
|
42
|
+
"error",
|
|
43
|
+
"double",
|
|
44
|
+
{
|
|
45
|
+
"allowTemplateLiterals": true
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"eqeqeq": "error",
|
|
49
|
+
"semi": "error"
|
|
50
|
+
}
|
|
51
51
|
};
|
package/bin/deploy.js
CHANGED
|
File without changes
|
package/bin/function.js
CHANGED
|
File without changes
|
package/bin/server.js
CHANGED
|
File without changes
|
package/costsBenefits.txt
CHANGED
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
Concepts
|
|
2
|
-
Naming
|
|
3
|
-
PathValue is the database
|
|
4
|
-
FunctionRunner is a tool which makes remote function calling easy
|
|
5
|
-
Querysub is the interface which untrusted parts of apps use to sync data and commit calls
|
|
6
|
-
- Quadratic sieve: `The quadratic sieve algorithm (QS) is an integer factorization algorithm and, in practice, the second fastest method known (after the general number field sieve). It is still the fastest for integers under 100 decimal digits or so, and is considerably simpler than the number field sieve`
|
|
7
|
-
- Quorum sensing: `In biology, quorum sensing or quorum signalling (QS)[1] is the ability to detect and respond to cell population density by gene regulation.`
|
|
8
|
-
- Query subscriptions (https://www.apollographql.com/docs/react/data/subscriptions/): In graphQL you have to choose between them, with Querysub you get both. `Like queries, subscriptions enable you to fetch data. Unlike queries, subscriptions are long-lasting operations that can change their result over time. They can maintain an active connection to your GraphQL server (most commonly via WebSocket), enabling the server to push updates to the subscription's result.` and the BS justification for why graphQL doesn't support automatic subscriptions: `In the majority of cases, your client should not use subscriptions to stay up to date with your backend. Instead, you should poll intermittently with queries, or re-execute queries on demand when a user performs a relevant action (such as clicking a button).`, which is true, but automatic subscriptions should still be easy to enable with 100% accuracy, instead of being hacked kludge to fill the massive missing piece of graphQL.
|
|
9
|
-
|
|
10
|
-
NOTE: The whole idea is "fix later" locks, where we invalidate writes later on, if we realized our lock wasn't ACID.
|
|
11
|
-
|
|
12
|
-
NOTE: We don't need to hang onto lock chains forever, because we require servers to keep their data fresh by constantly receiving the latest values. This means if a piece of data is old enough, we know it can be relied upon, because no one can go back in time far enough to remove it.
|
|
13
|
-
- We still need locks, because have changed relatively recently, but... we don't need to follow it's locks back, or even remember that it is supposed to be valid!
|
|
14
|
-
|
|
15
|
-
NOTE: Functions don't exist explicitly, but due to identical readLocks you can tell what happened from the same function. This also ensures writes are atomic, as if they have the same readLocks, either they are all accepted, or all rejected.
|
|
16
|
-
|
|
17
|
-
NOTE: We don't actually lock things such as Object.keys(). In practice we will get pretty close, but our handling of it is simply not fully ACID.
|
|
18
|
-
|
|
19
|
-
NOTE: In regards to FunctionRunners. FunctionRunner rerun any calls which have no result (included if their result was written, then invalidate). The first one to run will get the value, with the others having their ReadLock on the result (from epoch to their LATER writeTime) invalidated.
|
|
20
|
-
- Although, in practice, the writeTimes will be close, as they are based on requested writeTimes, made unique by creatorId. So... if two FunctionRunners run the same call, one will consistently create the accepted value. So... just don't have two FunctionRunners run the same call!
|
|
21
|
-
|
|
22
|
-
NOTE: "Bad" predictions are possibly by if you know the path some change will write to (ie, the call result path). You can create a prediction that depends on that path being undefined (in some range that includes the eventual remote change, but not the prediction), simply watch that path, and when the change eventually happens your prediction will automatically be invalidated.
|
|
23
|
-
- This is not suitable for trusted nodes, as it means all of our dependent values on that prediction will be only acceptable to us (as our prediction won't really be able to be propagated, as the change might never happen, in which case our prediction has to go away, which is impossible to do if we propagate it), but... it is very useful for clients to quickly predict writes (trusted nodes can just write actual values, not trying to get some remote to make a change).
|
|
24
|
-
- AND, if you only depend on that path there is no jitter as you reject old values, as there is no cascading! This DOES allow values to drift out of sync with the server (if they are CONSTANTLY written to), but... that is another problem, for games, and just one of many issues games have to overcome.
|
|
25
|
-
|
|
26
|
-
NOTE: The reason that we have our main database servers resolving all the values before they write to disk, is simple. They need to resolve the values, because somebody needs to and if the watchers resolve it it's extremely inefficient. We could have them resolve it but then pass the full stream to another server which then resolves it before writing to disk. But then any cases where this fixes an issue of lost data would mean the disk is now inconsistent with what our watchers have. So therefore, splitting off another server to record the full stream of data and having that be all it does and then resolving the data afterwards isn't any faster, and doesn't make our servers appear more stable.
|
|
27
|
-
- However, splitting off the data stream might be a good idea for the purposes of auditing, instead of speed, or reliability.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Summary of benefits/costs
|
|
31
|
-
|
|
32
|
-
Benefits
|
|
33
|
-
1) Decreased code size / decreased development time / decreased complexity
|
|
34
|
-
- Adding a new value doesn't require new endpoints
|
|
35
|
-
- Even for a non-syncing application the code size is about as small as it can get
|
|
36
|
-
2) Minimum possible clientside delay (so everything feels fast)
|
|
37
|
-
- If you just explicitly input predict values, and then clobber the predicted value when the call comes back, you run into a case where multiple predictions get clobber by an old callback. Which is bad.
|
|
38
|
-
- If you explicitly input predict values, and only clobber the latest, that works... until you have reasonably complex data.
|
|
39
|
-
- Explicit endpoints per data path is a non-starter, because when your user interfaces migrates it won't overlap, and then you will have to explicitly calling multiple endpoints after a mutate call, and it will become your entire job.
|
|
40
|
-
- The only way to do input prediction at a framework level is to track all accesses, and have a serverside which exposes path bases values, AND to have the same mutate functions clientside as you use serverside (otherwise you have to rewrite all mutate functions twice).
|
|
41
|
-
- AND THEN... if other users mutate data, you NEED the server to tell you when it changes. If you don't,
|
|
42
|
-
- In theory you can download data every time you need it... but this can't scale to reasonably large applications. AND, if you don't the user won't just see stale data, they will predict stale data, which will be really bad. So... the server needs to synchronize the paths as well
|
|
43
|
-
3) Excellent sharding / scaling via very lax sharding
|
|
44
|
-
- PathValue.locks are a simple locking mechanism, and very efficient (less efficient with high contention)
|
|
45
|
-
- Transactions can easily be done cross shard, and are still fully ACID, which most other databases have a hard time doing (unless they use virtually the same locking scheme)
|
|
46
|
-
|
|
47
|
-
Database flowchart
|
|
48
|
-
(NOTE: Large static assets should always be stored in some object storage database, like S3, even if you are using another system for other data.)
|
|
49
|
-
(NOTE: If you want an ultra responsive UI you can often use a write-then-reader wrapper for your database (which might require extra work to specify what should update, and what should be read). Or... just use query-sub)
|
|
50
|
-
|
|
51
|
-
- Do you want a very responsive user interface (via input prediction)
|
|
52
|
-
= Yes
|
|
53
|
-
- Is it easy to specify exactly what the writes are (with high accuracy) for most server write operations
|
|
54
|
-
(As in, are you using complex writes that involve functions with many lines of code, or is pretty much just KVP writes.)
|
|
55
|
-
= Yes
|
|
56
|
-
- Will the user often experience write contention
|
|
57
|
-
= YES
|
|
58
|
-
- query-sub
|
|
59
|
-
= NO
|
|
60
|
-
- query-sub
|
|
61
|
-
- OR Run again without a very responsive user interface, and then slap a write-then-reread wrapper onto your database, and then tell it what values it should predict.
|
|
62
|
-
= No
|
|
63
|
-
- query-sub
|
|
64
|
-
= NO
|
|
65
|
-
- Is % chance contention * impact of contention low (contention free)?
|
|
66
|
-
= Yes
|
|
67
|
-
Is the data static enough where stale data can be shown? (ex, search results, non-user part of youtube, twitter?)
|
|
68
|
-
= Yes
|
|
69
|
-
- Any NoSQL implementation (S3 for large data, MongoDB for complex data, otherwise BigTable, or even ElasticSearch?)
|
|
70
|
-
= No
|
|
71
|
-
- Do you want clientside data syncing
|
|
72
|
-
= Yes
|
|
73
|
-
- firebase
|
|
74
|
-
= No
|
|
75
|
-
- A fast writing NoSQL implementation (probably MongoDB)
|
|
76
|
-
= No
|
|
77
|
-
- Do you want clientside data syncing
|
|
78
|
-
= Yes
|
|
79
|
-
- firebase + firebase cloud functions
|
|
80
|
-
= No
|
|
81
|
-
- Are writes simple and only affect a single collection
|
|
82
|
-
= Yes
|
|
83
|
-
- MongoDB
|
|
84
|
-
= No
|
|
85
|
-
- MongoDB / SQL (likely with high transaction isolation levels)
|
|
86
|
-
|
|
87
|
-
Conclusion
|
|
88
|
-
- Realistically, you want a responsive user interface (who wouldn't?)
|
|
89
|
-
|
|
90
|
-
NOTE: For applications with very static data (so not just mostly reads, but the data actually changes so infrequently that users can be shown stale data and they won't even notice), using MongoDB is just as good (unless you need really complex transactions).
|
|
91
|
-
- You will still run into issues at huge scales with other databases though, as eventually the amount of transactions you want will exceed what one server can handle, and then you need to shard, and if you don't or can't shard well, everything will get slow.
|
|
92
|
-
|
|
93
|
-
Timestamped writes / clientside reproducible writes
|
|
94
|
-
BENEFIT: Allows clients to compare against incoming writes and know if they write is newer, or older, allowing predictions to never get out of sync.
|
|
95
|
-
ALTERNATIVE: Reading state after each write also allows this. HOWEVER, if the write is a function mutation, the impetus for the write might be small, but the data mutated could be large. For example, a paint application, where a write mutates the canvas, which is large, so we don't want to download the entire canvas after each write.
|
|
96
|
-
- AND, even if the values are small, this requires increased network usage
|
|
97
|
-
COST: Is slower. Is only useful with with KVP writes (if you can't map from clientside write to read paths this can't be used to predict values).
|
|
98
|
-
ADDITIONAL BENEFITS: Makes debugging easier, via keeping track of a history of operations
|
|
99
|
-
|
|
100
|
-
Watching/syncing
|
|
101
|
-
BENEFIT: Greatly reduces read count needed to keep data up to date
|
|
102
|
-
COST: Writes have more overhead, requiring everything to be notified
|
|
103
|
-
|
|
104
|
-
Lock tracking / valid tracking / contention invalidation / eventually consistent transactions
|
|
105
|
-
BENEFIT: ACID safety that is fast even with large collections.
|
|
106
|
-
- Also allows ACID safety even for servers that are off the main network, allowing anyone to add transactions without worrying about inserting an inconsistent write (worst case the write will be rejected).
|
|
107
|
-
- Also, ACID safety is a lot easier to implement with this technique
|
|
108
|
-
ALTERNATIVE: Use a single server, or a single server per collection and don't provide ACID cross collection.
|
|
109
|
-
ALTERNATIVE: Collection locking, which if you have enough servers using the same collection degenerates to single server performance.
|
|
110
|
-
ALTERNATIVE: Smart evenly sub-collection sharding. This can be very efficient, HOWEVER, it is extremely difficult to anticipate the access patterns. Accurately predicting all access patterns would be akin to a universal storage system that can run any search query in optimal time (either O(1), or O(logN)).
|
|
111
|
-
ALTERNATIVE: No ACID safety. This is pretty much only viable if the write interface is purely KVP based. Otherwise it is easy to create functions which depend on ACID safety, and give unpredictable results when it is not kept (ex, contention causes really bad breaking of things). This makes it impossible to implement things such as financial transactions, most games, etc.
|
|
112
|
-
- And, of course, we can have lock tracking support, but just commit writes with no readLocks, which will produce much faster writes, making the database based degenerate to a NoSQL database, with theoretically the same speed.
|
|
113
|
-
COST: Slower to compute writes, and writes become MUCH larger.
|
|
114
|
-
- Lock/eventually consistency transactions ONLY work if the client readers are syncing. If they aren't, they will get the wrong data and KEEP the wrong data, which would break pretty much any application.
|
|
115
|
-
ADDITIONAL BENEFITS: Makes debugging easier, via making it possible to trace the read source used to generate any write (so to track data through the system).
|
|
1
|
+
Concepts
|
|
2
|
+
Naming
|
|
3
|
+
PathValue is the database
|
|
4
|
+
FunctionRunner is a tool which makes remote function calling easy
|
|
5
|
+
Querysub is the interface which untrusted parts of apps use to sync data and commit calls
|
|
6
|
+
- Quadratic sieve: `The quadratic sieve algorithm (QS) is an integer factorization algorithm and, in practice, the second fastest method known (after the general number field sieve). It is still the fastest for integers under 100 decimal digits or so, and is considerably simpler than the number field sieve`
|
|
7
|
+
- Quorum sensing: `In biology, quorum sensing or quorum signalling (QS)[1] is the ability to detect and respond to cell population density by gene regulation.`
|
|
8
|
+
- Query subscriptions (https://www.apollographql.com/docs/react/data/subscriptions/): In graphQL you have to choose between them, with Querysub you get both. `Like queries, subscriptions enable you to fetch data. Unlike queries, subscriptions are long-lasting operations that can change their result over time. They can maintain an active connection to your GraphQL server (most commonly via WebSocket), enabling the server to push updates to the subscription's result.` and the BS justification for why graphQL doesn't support automatic subscriptions: `In the majority of cases, your client should not use subscriptions to stay up to date with your backend. Instead, you should poll intermittently with queries, or re-execute queries on demand when a user performs a relevant action (such as clicking a button).`, which is true, but automatic subscriptions should still be easy to enable with 100% accuracy, instead of being hacked kludge to fill the massive missing piece of graphQL.
|
|
9
|
+
|
|
10
|
+
NOTE: The whole idea is "fix later" locks, where we invalidate writes later on, if we realized our lock wasn't ACID.
|
|
11
|
+
|
|
12
|
+
NOTE: We don't need to hang onto lock chains forever, because we require servers to keep their data fresh by constantly receiving the latest values. This means if a piece of data is old enough, we know it can be relied upon, because no one can go back in time far enough to remove it.
|
|
13
|
+
- We still need locks, because have changed relatively recently, but... we don't need to follow it's locks back, or even remember that it is supposed to be valid!
|
|
14
|
+
|
|
15
|
+
NOTE: Functions don't exist explicitly, but due to identical readLocks you can tell what happened from the same function. This also ensures writes are atomic, as if they have the same readLocks, either they are all accepted, or all rejected.
|
|
16
|
+
|
|
17
|
+
NOTE: We don't actually lock things such as Object.keys(). In practice we will get pretty close, but our handling of it is simply not fully ACID.
|
|
18
|
+
|
|
19
|
+
NOTE: In regards to FunctionRunners. FunctionRunner rerun any calls which have no result (included if their result was written, then invalidate). The first one to run will get the value, with the others having their ReadLock on the result (from epoch to their LATER writeTime) invalidated.
|
|
20
|
+
- Although, in practice, the writeTimes will be close, as they are based on requested writeTimes, made unique by creatorId. So... if two FunctionRunners run the same call, one will consistently create the accepted value. So... just don't have two FunctionRunners run the same call!
|
|
21
|
+
|
|
22
|
+
NOTE: "Bad" predictions are possibly by if you know the path some change will write to (ie, the call result path). You can create a prediction that depends on that path being undefined (in some range that includes the eventual remote change, but not the prediction), simply watch that path, and when the change eventually happens your prediction will automatically be invalidated.
|
|
23
|
+
- This is not suitable for trusted nodes, as it means all of our dependent values on that prediction will be only acceptable to us (as our prediction won't really be able to be propagated, as the change might never happen, in which case our prediction has to go away, which is impossible to do if we propagate it), but... it is very useful for clients to quickly predict writes (trusted nodes can just write actual values, not trying to get some remote to make a change).
|
|
24
|
+
- AND, if you only depend on that path there is no jitter as you reject old values, as there is no cascading! This DOES allow values to drift out of sync with the server (if they are CONSTANTLY written to), but... that is another problem, for games, and just one of many issues games have to overcome.
|
|
25
|
+
|
|
26
|
+
NOTE: The reason that we have our main database servers resolving all the values before they write to disk, is simple. They need to resolve the values, because somebody needs to and if the watchers resolve it it's extremely inefficient. We could have them resolve it but then pass the full stream to another server which then resolves it before writing to disk. But then any cases where this fixes an issue of lost data would mean the disk is now inconsistent with what our watchers have. So therefore, splitting off another server to record the full stream of data and having that be all it does and then resolving the data afterwards isn't any faster, and doesn't make our servers appear more stable.
|
|
27
|
+
- However, splitting off the data stream might be a good idea for the purposes of auditing, instead of speed, or reliability.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
Summary of benefits/costs
|
|
31
|
+
|
|
32
|
+
Benefits
|
|
33
|
+
1) Decreased code size / decreased development time / decreased complexity
|
|
34
|
+
- Adding a new value doesn't require new endpoints
|
|
35
|
+
- Even for a non-syncing application the code size is about as small as it can get
|
|
36
|
+
2) Minimum possible clientside delay (so everything feels fast)
|
|
37
|
+
- If you just explicitly input predict values, and then clobber the predicted value when the call comes back, you run into a case where multiple predictions get clobber by an old callback. Which is bad.
|
|
38
|
+
- If you explicitly input predict values, and only clobber the latest, that works... until you have reasonably complex data.
|
|
39
|
+
- Explicit endpoints per data path is a non-starter, because when your user interfaces migrates it won't overlap, and then you will have to explicitly calling multiple endpoints after a mutate call, and it will become your entire job.
|
|
40
|
+
- The only way to do input prediction at a framework level is to track all accesses, and have a serverside which exposes path bases values, AND to have the same mutate functions clientside as you use serverside (otherwise you have to rewrite all mutate functions twice).
|
|
41
|
+
- AND THEN... if other users mutate data, you NEED the server to tell you when it changes. If you don't,
|
|
42
|
+
- In theory you can download data every time you need it... but this can't scale to reasonably large applications. AND, if you don't the user won't just see stale data, they will predict stale data, which will be really bad. So... the server needs to synchronize the paths as well
|
|
43
|
+
3) Excellent sharding / scaling via very lax sharding
|
|
44
|
+
- PathValue.locks are a simple locking mechanism, and very efficient (less efficient with high contention)
|
|
45
|
+
- Transactions can easily be done cross shard, and are still fully ACID, which most other databases have a hard time doing (unless they use virtually the same locking scheme)
|
|
46
|
+
|
|
47
|
+
Database flowchart
|
|
48
|
+
(NOTE: Large static assets should always be stored in some object storage database, like S3, even if you are using another system for other data.)
|
|
49
|
+
(NOTE: If you want an ultra responsive UI you can often use a write-then-reader wrapper for your database (which might require extra work to specify what should update, and what should be read). Or... just use query-sub)
|
|
50
|
+
|
|
51
|
+
- Do you want a very responsive user interface (via input prediction)
|
|
52
|
+
= Yes
|
|
53
|
+
- Is it easy to specify exactly what the writes are (with high accuracy) for most server write operations
|
|
54
|
+
(As in, are you using complex writes that involve functions with many lines of code, or is pretty much just KVP writes.)
|
|
55
|
+
= Yes
|
|
56
|
+
- Will the user often experience write contention
|
|
57
|
+
= YES
|
|
58
|
+
- query-sub
|
|
59
|
+
= NO
|
|
60
|
+
- query-sub
|
|
61
|
+
- OR Run again without a very responsive user interface, and then slap a write-then-reread wrapper onto your database, and then tell it what values it should predict.
|
|
62
|
+
= No
|
|
63
|
+
- query-sub
|
|
64
|
+
= NO
|
|
65
|
+
- Is % chance contention * impact of contention low (contention free)?
|
|
66
|
+
= Yes
|
|
67
|
+
Is the data static enough where stale data can be shown? (ex, search results, non-user part of youtube, twitter?)
|
|
68
|
+
= Yes
|
|
69
|
+
- Any NoSQL implementation (S3 for large data, MongoDB for complex data, otherwise BigTable, or even ElasticSearch?)
|
|
70
|
+
= No
|
|
71
|
+
- Do you want clientside data syncing
|
|
72
|
+
= Yes
|
|
73
|
+
- firebase
|
|
74
|
+
= No
|
|
75
|
+
- A fast writing NoSQL implementation (probably MongoDB)
|
|
76
|
+
= No
|
|
77
|
+
- Do you want clientside data syncing
|
|
78
|
+
= Yes
|
|
79
|
+
- firebase + firebase cloud functions
|
|
80
|
+
= No
|
|
81
|
+
- Are writes simple and only affect a single collection
|
|
82
|
+
= Yes
|
|
83
|
+
- MongoDB
|
|
84
|
+
= No
|
|
85
|
+
- MongoDB / SQL (likely with high transaction isolation levels)
|
|
86
|
+
|
|
87
|
+
Conclusion
|
|
88
|
+
- Realistically, you want a responsive user interface (who wouldn't?)
|
|
89
|
+
|
|
90
|
+
NOTE: For applications with very static data (so not just mostly reads, but the data actually changes so infrequently that users can be shown stale data and they won't even notice), using MongoDB is just as good (unless you need really complex transactions).
|
|
91
|
+
- You will still run into issues at huge scales with other databases though, as eventually the amount of transactions you want will exceed what one server can handle, and then you need to shard, and if you don't or can't shard well, everything will get slow.
|
|
92
|
+
|
|
93
|
+
Timestamped writes / clientside reproducible writes
|
|
94
|
+
BENEFIT: Allows clients to compare against incoming writes and know if they write is newer, or older, allowing predictions to never get out of sync.
|
|
95
|
+
ALTERNATIVE: Reading state after each write also allows this. HOWEVER, if the write is a function mutation, the impetus for the write might be small, but the data mutated could be large. For example, a paint application, where a write mutates the canvas, which is large, so we don't want to download the entire canvas after each write.
|
|
96
|
+
- AND, even if the values are small, this requires increased network usage
|
|
97
|
+
COST: Is slower. Is only useful with with KVP writes (if you can't map from clientside write to read paths this can't be used to predict values).
|
|
98
|
+
ADDITIONAL BENEFITS: Makes debugging easier, via keeping track of a history of operations
|
|
99
|
+
|
|
100
|
+
Watching/syncing
|
|
101
|
+
BENEFIT: Greatly reduces read count needed to keep data up to date
|
|
102
|
+
COST: Writes have more overhead, requiring everything to be notified
|
|
103
|
+
|
|
104
|
+
Lock tracking / valid tracking / contention invalidation / eventually consistent transactions
|
|
105
|
+
BENEFIT: ACID safety that is fast even with large collections.
|
|
106
|
+
- Also allows ACID safety even for servers that are off the main network, allowing anyone to add transactions without worrying about inserting an inconsistent write (worst case the write will be rejected).
|
|
107
|
+
- Also, ACID safety is a lot easier to implement with this technique
|
|
108
|
+
ALTERNATIVE: Use a single server, or a single server per collection and don't provide ACID cross collection.
|
|
109
|
+
ALTERNATIVE: Collection locking, which if you have enough servers using the same collection degenerates to single server performance.
|
|
110
|
+
ALTERNATIVE: Smart evenly sub-collection sharding. This can be very efficient, HOWEVER, it is extremely difficult to anticipate the access patterns. Accurately predicting all access patterns would be akin to a universal storage system that can run any search query in optimal time (either O(1), or O(logN)).
|
|
111
|
+
ALTERNATIVE: No ACID safety. This is pretty much only viable if the write interface is purely KVP based. Otherwise it is easy to create functions which depend on ACID safety, and give unpredictable results when it is not kept (ex, contention causes really bad breaking of things). This makes it impossible to implement things such as financial transactions, most games, etc.
|
|
112
|
+
- And, of course, we can have lock tracking support, but just commit writes with no readLocks, which will produce much faster writes, making the database based degenerate to a NoSQL database, with theoretically the same speed.
|
|
113
|
+
COST: Slower to compute writes, and writes become MUCH larger.
|
|
114
|
+
- Lock/eventually consistency transactions ONLY work if the client readers are syncing. If they aren't, they will get the wrong data and KEEP the wrong data, which would break pretty much any application.
|
|
115
|
+
ADDITIONAL BENEFITS: Makes debugging easier, via making it possible to trace the read source used to generate any write (so to track data through the system).
|
package/deploy.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "./src/diagnostics/satSchema";
|
|
2
|
-
import "./src/test/test";
|
|
1
|
+
import "./src/diagnostics/satSchema";
|
|
2
|
+
import "./src/test/test";
|
|
3
3
|
import "./src/4-dom/qreactTest";
|