C10k / C10M Challenge

C10k / C10M Challenge 挑战

History

The term was coined in 1999 by Dan Kegel, citing the Simtel FTP host, cdrom.com, serving 10,000 clients at once over 1 gigabit per second Ethernet in that year. The term has since been used for the general issue of large number of clients, with similar numeronyms for larger number of connections, most recently C10M in the 2010s.

By the early 2010s millions of connections on a single commodity 1U server became possible: over 2 million connections (WhatsApp, 24 cores, using Erlang on FreeBSD), 10–12 million connections (MigratoryData, 12 cores, using Java on Linux).

C10k(concurrently handling 10k connections)是一个在 1999 年被提出来的技术挑战,如何在一颗 1GHz CPU,2G 内存,1Gbps 网络环境下,让单台服务器同时为 1 万个客户端提供 FTP 服务。而到了 2010 年后,随着硬件技术的发展,这个问题被延伸为 C10M,即如何利用 8 核心 CPU,64G 内存,在 10Gbps 的网络上保持 1000 万并发连接,或是每秒钟处理 100 万的连接。(两种类型的计算机资源在各自的时代都约为 1200 美元)。

C10k / C10M 问题则是从技术角度出发挑战软硬件极限。C10k / C10M 问题得解,成本问题和效率问题迎刃而解。

Network data flow through kernel

References

Plato and Socrates - 柏拉图麦穗问题

The meaning of Love, Marriage and Life

One day, Plato asked his teacher Socrates, “What is love? How can I find it?” Socrates answered, “There is a vast wheat field in front. Walk forward without turning back, and pick only one stalk. If you find the most magnificent stalk, then you have found love.” Plato walked forward, and before long, he returned with empty hands, having picked nothing. His teacher asked, “Why did you not pick any stalk?” Plato answered, “Because I could only pick once, and yet I could not turn back. I did find the most magnificent stalk, but did not know if there were any better ones ahead, so I did not pick it. As I walked further, the stalks that I saw were not as good as the earlier one, so! I did not pick any in the end.”

Socrates then said, “And that is LOVE.”

On another day, Plato asked Socrates: “What is marriage? How can I find it?” His teacher answered, “There is a thriving forest in front. Walk forward without turning back, and chop down only one tree. If you find the tallest tree, then you have found marriage”. Plato walked forward, and before long, He returned with a tree. The tree was not bad but it was not tall, either. It was only an ordinary tree, not the best but just a good tree. His teacher asked, “Why did you chop down such an ordinary tree?” Plato answered, “Based on my previous experience, I had walked through the field, but returned with empty hands. This time, I saw this tree, and I felt that it was the first good tree I had seen, so I chopped it down and brought it back. I did not want to miss the chance.”

Socrates then said, “And that is MARRIAGE.

On another day, Plato asked his teacher, “What is life?” Socrates asked him to go to the forest again, allowed back and forth as well, and pluck the most beautiful flower. Plato walked forward. However he hadn’t come back for 3 days. His teacher went to find him. When he saw Plato’s camping in the forest, he asked:” Have you found the most beautiful flower?” Plato pointed a flower near to his camp and answered, “This is the most beautiful flower!” “Why didn’t you take it out?” Socrates asked. “Because if I pick it, it would be drooping. Even though I didn’t pick, it would die in a couple of days for sure. So I had been living by its side while it was blooming. When it’s drooped, I was up to find another one. This is the second most beautiful flower I have found!”

Socrates then said, “You’ve got the truth of LIFE”

“Love” is the most beautiful thing to happen to a person, it’s an opportunity you don’t realize its worth when you have it but only when it’s gone like the field of stalks.

“Marriage” is like the tree you chopped, it’s a compromise; you pick the first best thing you see and learn to live a happy life with it. Having an affair is alluring. It’s like lightning - bright but disappeared so quickly that you cannot catch up with and keep it.

“Life” is to follow and enjoy the every beautiful moment of living. That’s why you should enjoy your life wherever you live.

有一天,古希腊哲学家柏拉图问他的老师苏格拉底什么是爱情,他的老师就叫他先到麦田里,摘一棵全麦田里最大最金黄的的麦穗。期间只能摘一次,并且只可以向前走,不能回头。柏拉图于是照着老师的说话做。结果,他两手空空的走出麦田。老师问他为什么摘不到,他说:“因为只能摘一次,又不能走回头路,其间即使见到一棵又大又金黄的,因为不知前面是否有更好,所以没有摘;走到前面时,又发觉总不及之前见到的好,原来麦田里最大最金黄的麦穗,早就错过了;于是,我什么也没摘到。”

苏格拉底说:“这就是爱情。”

之后又有一天,柏拉图问他的老师什么是婚姻,他的老师就叫他先到树林里,砍下一棵全树林最大最茂盛、最适合放在家作圣诞树的树。其间同样只能摘一次,以及同样只可以向前走,不能回头。柏拉图于是照着老师的说话做。今次,他带了一棵普普通通,不是很茂盛,亦不算太差的树回来。老师问他,怎么带这棵普普通通的树回来。他说:“有了上一次经验,当我走到大半路程,已经感到累了却还两手空空时,我觉得虽然树林里还有很多树,但这棵树还是挺不错的,便砍下来,免得最后又什么也带不出来。”

苏格拉底说:“这就是婚姻。”

又有一天柏拉图又问老师苏格拉底什么是生活,苏格拉底还是叫他到树林走一次。要求是随便走,在途中要取一支最好看的花。柏拉图有了以前的教训又充满信心地出去过了三天三夜,他也没有回来。苏格拉底只好走进树林里去找他,最后发现柏拉图已在树林里安营扎寨。苏格拉底问他:“你找着最好看的花么?” 柏拉图指着边上的一朵花说:“这就是最好看的花。” 苏格拉底问:“为什么不把它带出去呢?” 柏拉图回答老师: “我如果把它摘下来,它马上就枯萎。即使我不摘它,它也迟早会枯。所以我就在它还盛开的时候,住在它边上。等它凋谢的时候,再找下一朵。这已经是我找着的第二朵最好看的花了。”

苏格拉底说: “你已经懂得生活的真谛了。”

爱情给人经历和回忆,之后,婚姻靠的是明智的决定和好好的把握,经过了这些考验,到最后才会明白生活是一种珍惜和守护。

柏拉图麦穗问题的数学解答

  
现在我们用数学的角度来讨论这个问题。

假设我们碰到的麦穗有 n 个,我们用这样的策略来选麦穗,前 k 个,记住一个最大的麦穗记为 d(可能是重量,也可能是体积),然后 k + 1 个开始,只要大于 d 的,就选择,否则就不选择。

对于某个固定的 k,如果最大的麦穗出现在了第 i 个位置(k < i ≤ n),要想让他有幸正好被选中,就必须得满足前 i - 1 个麦穗中的最好的麦穗在前 k 个麦穗里,这有 k / (i - 1) 的可能。考虑所有可能的 i,我们便得到了前 k 个麦穗作为参考,能选中最大麦穗的总概率 P(k):

Wheat Paradox

设 k / n = x,并且假设 n 充分大,则上述公式可以改为:
  
Wheat Paradox

对 x·ln(x) 求导,并令这个导数为 0,可以解出 x 的最优值,它就是欧拉研究的神秘常数的倒数 1 / e.

所以 k = n / e.
  
如果你想摘取最大的麦穗,假设有 n 个麦穗,你应该先将前 n / e 个麦穗作为参考,然后再 k + 1 个麦穗开始选择比前面 k 个最大的麦穗即可。

e = 2.718281828459

1 / e = 0.36787944117144

其他例子

一、一楼到十楼的每层电梯门口都放着一颗钻石,钻石大小不一。你乘坐电梯从一楼到十楼,每层楼电梯门都会打开一次,只能拿一次钻石,问怎样才能拿到最大的一颗。

首先,这个题目说的,并不能完全拿到最大的钻石。但可以保证拿到最大钻石的概率最大。10 / e = 3.67,向上取整得 4。前四层皆不取,只记下最大的。后面遇到的,只要比前面最大的还大,取之即可。

二、秘书问题。在机率及博弈论上,秘书问题(类似名称有相亲问题、止步问题、见好就收问题、苏丹的嫁妆问题、挑剔的求婚者问题等) 内容是这样的:

要聘请一名秘书,有 n 人来面试。每次面试一人,面试过后便要即时决定聘不聘他,如果当时决定不聘他,他便不会回来。面试时总能清楚了解求职者的适合程度,并能和之前的每个人作比较。问凭什么策略,才使选得到最适合担任秘书的人的机率最大?

References

Why Functional Programming?

Why Functional Programming?

Why Static Type?

  • 性能 - 方法调用速度更快,因为不需要在运行时才来判断调用的是哪个方法。
  • 可靠性 - 编译器验证了程序的正确性,因而运行时崩溃的概率更低。
  • 可维护性 - 陌生代码更容易维护,因为你可以看到代码中用到的对象的类型。
  • 工具支持 - 静态类型使 IDE 能提供可靠的重构、精确的代码补全以及其他特性。

Benefit of Functional Programming

  • 头等函数 - 把函数(一小段行为)当作值使用,可以用变量保存它,把它当作参数传递,或者当作其他函数的返回值。
  • 不可变性 - 使用不可变对象,这保证了它们的状态在其创建之后不能再变化。
  • 无副作用 - 使用的是纯函数。此类函数在输入相同时会产生同样的结果,并且不会修改其他对象的状态,也不会和外面的世界交互。

Moving Parts

  • 简洁

函数式风格的代码 比相应的命令式风格的代码更优雅、更简练,因为把函数当作值可以让你获得更强大的抽象能力,从而避免重复代码。

假设你有两段类似的代码,实现相似的任务但具体细节略有不同,可以轻易地将这段逻辑中公共的部分提取到一个函数中,并将其他不同的部分作为参数传递给它。这些参数本身也是函数,但你可以使用一种简洁的语法来表示这些匿名函数,被称作 lambda 表达式。

  • 多线程安全

多线程程序中最大的错误来源之一就是,在没有采用适当同步机制的情况下,在不同的线程上修改同一份数据。如果你使用的是不可变数据结构和纯函数,就能保证这样不安全的修改根本不会发生,也就不需要考虑为其设计复杂的同步方案。

  • 测试更加容易

没有副作用的函数可以独立地进行测试,因为不需要写大量的设置代码来构造它们所依赖的整个环境。

Functional programming, views a program as a mathematical function which is evaluated to produce a result value. That function may call upon nested functions, which in turn may call upon more nested functions. A nested function evaluates to produce a result. From there, that result is passed on to the enclosing function, which uses the nested function values to calculate its own return value. To enable functions to easily pass data to and from other functions, functional programming languages typically define data structures in the most generic possible way, as a collection of (any) things. They also allow functions to be passed to other functions as if they were data parameters. A function in this paradigm is not allowed to produce any side effects such as modifying a global variable that maintains state information. Instead, it is only allowed to receive parameters and perform some operations on them in order to produce its return value. Executing a functional program involves evaluating the outermost function, which in turn causes evaluation of all the nested functions, recursively down to the most basic functions that have no nested functions.

Why is functional programming a big deal?

  • Clarity

Programming without side effects creates code that is easier to follow - a function is completely described by what goes in and what comes out. A function that produces the right answer today will produce the right answer tomorrow. This creates code that is easier to debug, easier to test, and easier to re-use.

  • Brevity

In functional languages, data is implicitly passed from a nested function to its parent function, via a general-purpose collection data type. This makes functional programs much more compact than those of other paradigms, which require substantial “housekeeping” code to pass data from one function to the next.

  • Efficiency

Because functions do not have side effects, operations can be re-ordered or performed in parallel in order to optimize performance, or can be skipped entirely if their result is not used by any other function.

References

Customisation of Filco Majestouch 2 Mechanical Keyboard

A Filco Majestouch 2 Tenkeyless Mechanical Keyboard, with Cherry Brown switches. It has been replaced with GMK Honeywell keycaps from Originative Co. (https://originative.co/products/honeywell) soon after it bought.

GMK Honeywell

GMK Honeywell keycaps, made in Germany

Replace Filco Majestouch 2’s with GMK Honeywell keycaps.

Replace with GMK Honeywell keycaps

Replace with GMK Honeywell keycaps

Replace with GMK Honeywell keycaps

Replace with GMK Honeywell keycaps

Replace with GMK Honeywell keycaps

After joined Massdrop Lambo 80% Anodized Aluminum Case for Filco 87 TKL campaign (https://www.massdrop.com/buy/lambo-80-anodized-aluminum-case-for-filco-87-tkl)

Lambo 80% Anodized Aluminum Case for Filco 87 TKL

After a few months waiting, case delivered in, shipped from USA.

Lambo 80% Anodized Aluminum Case

Lambo 80% Anodized Aluminum Case

Get hands warmed up and dirty.

Replace the case

Replace the case

Replace the case

Replace the case

Replace the case

Now, show time.

Show time

Show time

Show time

Show time

Show time

Show time

Show time

极简之道 - The interface 人与机器的思想交流

Why 60%?

Why 60%

Why 60%

Why 60%

Why 60%

GH60

GH60

GH60 可编程键盘 (http://blog.komar.be/projects/gh60-programmable-keyboard/), it’s Poker 2 键盘的 rip-off。开放式公板设计。电路板中国制造。三周前,在 AliExpress (https://www.aliexpress.com/item/Customized-DIY-GH60-Case-Shell-PCB-Plate-Switches-LED-Kit-60-Mechanical-Keyboard-Satan-Poker2-GH/32651474350.html) 下的订单。

Cherry MX Switch

Cherry MX Brown Switch

德国 Cherry 工厂的茶轴。敲起键盘来极有段落,层次感。

iQunix Lambo

iQunix Lambo 60%

拆掉原装的塑料键盘壳。换装 iQunix Lambo (https://www.aliexpress.com/item/Iqunix-lambo-60-mechanical-keyboard-anode-alumina-shell-base-gh60-poker2/32677061753.html) 铝制外壳。

GMK 3Run Keycap Set

GMK 3Run Keycap Set

最后再装上德国 GMK 工厂造的 3Run ABS keycaps 后 (https://www.massdrop.com/buy/gmk-3run-keycap-set) ,一个拥有自己 signature,体现个性,品味的机械键盘诞生了。

极简之道 / Simplicity

Ant Colony Optimization (ACO)

Ant Colony Optimization (ACO) for the the Traveling Salesman Problem (TSP).

In computer science and operations research, the ant colony optimization algorithm (ACO) is a probabilistic technique for solving computational problems which can be reduced to finding good paths through graphs.

蚁群算法是一种用来寻找优化路径的概率型算法。它由 Marco Dorigo 于 1992 年在他的博士论文中提出,其灵感来源于蚂蚁在寻找食物过程中发现路径的行为。这种算法具有分布计算、信息正反馈和启发式搜索的特征,本质上是进化算法中的一种启发式全局优化算法。

蚁群系统 (Ant System 或 Ant Colony System) 是由意大利学者 Dorigo、Maniezzo 等人于 20 世纪 90 年代首先提出来的。他们在研究蚂蚁觅食的过程中,发现单个蚂蚁的行为比较简单,但是蚁群整体却可以体现一些智能的行为。例如蚁群可以在不同的环境下,寻找最短到达食物源的路径。这是因为蚁群内的蚂蚁可以通过某种信息机制实现信息的传递。后又经进一步研究发现,蚂蚁会在其经过的路径上释放一种可以称之为“信息素”的物质,蚁群内的蚂蚁对“信息素”具有感知能力,它们会沿着“信息素”浓度较高路径行走,而每只路过的蚂蚁都会在路上留下“信息素”,这就形成一种类似正反馈的机制,这样经过一段时间后,整个蚁群就会沿着最短路径到达食物源了。

将蚁群算法应用于解决优化问题的基本思路为:用蚂蚁的行走路径表示待优化问题的可行解,整个蚂蚁群体的所有路径构成待优化问题的解空间。路径较短的蚂蚁释放的信息素量较多,随着时间的推进,较短的路径上累积的信息素浓度逐渐增高,选择该路径的蚂蚁个数也愈来愈多。最终,整个蚂蚁会在正反馈的作用下集中到最佳的路径上,此时对应的便是待优化问题的最优解。

蚂蚁找到最短路径要归功于信息素和环境,假设有两条路可从蚁窝通向食物,开始时两条路上的蚂蚁数量差不多:当蚂蚁到达终点之后会立即返回,距离短的路上的蚂蚁往返一次时间短,重复频率快,在单位时间里往返蚂蚁的数目就多,留下的信息素也多,会吸引更多蚂蚁过来,会留下更多信息素。而距离长的路正相反,因此越来越多的蚂蚁聚集到最短路径上来。

蚂蚁具有的智能行为得益于其简单行为规则,该规则让其具有多样性和正反馈。在觅食时,多样性使蚂蚁不会走进死胡同而无限循环,是一种创新能力;正反馈使优良信息保存下来,是一种学习强化能力。两者的巧妙结合使智能行为涌现,如果多样性过剩,系统过于活跃,会导致过多的随机运动,陷入混沌状态;如果多样性不够,正反馈过强,会导致僵化,当环境变化时蚁群不能相应调整。

与其他优化算法相比,蚁群算法具有以下几个特点:

(1) 采用正反馈机制,使得搜索过程不断收敛,最终逼近最优解。
(2) 每个个体可以通过释放信息素来改变周围的环境,且每个个体能够感知周围环境的实时变化,个体间通过环境进行间接地通讯。
(3) 搜索过程采用分布式计算方式,多个个体同时进行并行计算,大大提高了算法的计算能力和运行效率。
(4) 启发式的概率搜索方式不容易陷入局部最优,易于寻找到全局最优解。

该算法应用于其他组合优化问题,如旅行商问题、指派问题、Job Shop 调度问题、车辆路由问题、图着色问题和网络路由问题等。最近几年,该算法在网络路由中的应用受到越来越多学者的关注,并提出了一些新的基于蚂蚁算法的路由算法。同传统的路由算法相比较,该算法在网络路由中具有信息分布式性、动态性、随机性和异步性等特点,而这些特点正好能满足网络路由的需要。

Visualization

A visual demo of Ant Colony Optimisation written in Javascript (ES6):

Visual demo of Ant Colony Optimisation

Another visual demo of Ant Colony Optimisation:

Visual demo of Ant Colony Optimisation

References

Spring Data - powerful and succinct abstraction

Database tier definition

Database tables, indexes and foreign keys defined in Liquibase configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
databaseChangeLog:
- changeSet:
id: 1
author: Terrence Miao
changes:
- createTable:
tableName: draft_order
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: c_number
type: varchar(32)
constraints:
nullable: false
- column:
name: source_time_in_ms
type: bigint
constraints:
nullable: false
- column:
name: source_item_id
type: varchar(255)
constraints:
nullable: false
- column:
name: shipment
type: json
constraints:
nullable: false
- column:
name: shipment_id
type: varchar(255)
constraints:
nullable: true
- column:
name: quantity
type: int
constraints:
nullable: false
- column:
name: source_system
type: varchar(255)
constraints:
nullable: false
- column:
name: status
type: varchar(32)
constraints:
nullable: false
- createIndex:
columns:
- column:
name: source_item_id
indexName: idx_source_item_id
tableName: draft_order
unique: false
- createIndex:
columns:
- column:
name: c_number
- column:
name: source_item_id
indexName: idx_c_number_source_item_id
tableName: draft_order
unique: true
- createTable:
tableName: draft_order_combined
columns:
- column:
name: id
type: int
autoIncrement: true
constraints:
primaryKey: true
nullable: false
- column:
name: combined_id
type: varchar(64)
constraints:
nullable: false
- column:
name: draft_order_id
type: int
constraints:
nullable: false
- addForeignKeyConstraint:
baseColumnNames: draft_order_id
baseTableName: draft_order_combined
constraintName: fk_draft_order_combined_draft_order
onDelete: CASCADE
onUpdate: RESTRICT
referencedColumnNames: id
referencedTableName: draft_order
- changeSet:
id: 2
author: Terrence Miao
changes:
- addColumn:
columns:
- column:
# For MySQL 5.7.x above, the first TIMESTAMP column in the table gets current timestamp as the default value, likely. So
# if an INSERT or UPDATE without supplying a value, the column will get the current timestamp. Any subsequent TIMESTAMP
# columns should have a default value explicitly defined. If you have two TIMESTAMP columns and if you don't specify a
# default value for the second column, you will get this error while trying to create the table:
# ERROR 1067 (42000): Invalid default value for 'COLUMN_NAME'
name: date_created
type: timestamp(3)
constraints:
nullable: false
- column:
name: date_updated
type: timestamp(3)
defaultValueComputed: LOCALTIMESTAMP(3)
constraints:
nullable: false
tableName: draft_order

DAO definition

  • Draft Order
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@Entity
@Table(name = "draft_order")
public class DraftOrder implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "c_number")
private String cNumber;

@Column(name = "source_time_in_ms")
private Long sourceTimeInMs;

@Column(name = "source_item_id")
private String sourceItemId;

@Column(name = "shipment", columnDefinition = "json")
private String shipment;

@Column(name = "shipment_id")
private String shipmentId;

@Column(name = "quantity")
private Integer quantity;

@Column(name = "source_system")
private String sourceSystem;

@Column(name = "status")
private String status;
}
  • Draft Order Combined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
@Table(name = "draft_order_combined")
public class DraftOrderCombined implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;

@Column(name = "combined_id")
private String combinedId;

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "draft_order_id")
private DraftOrder draftOrder;
}
  • An middle Aggregation Object
1
2
3
4
5
6
7
8
9
10
11
12
public class CombinedIdSourceTimeInMs {

private Long counter;
private String combinedId;
private Long sourceTimeInMs;

public CombinedIdSourceTimeInMs(Long counter, String combinedId, Long sourceTimeInMs) {
this.counter = counter;
this.combinedId = combinedId;
this.sourceTimeInMs = sourceTimeInMs;
}
}

CRUD Repository definition

  • DraftOrderRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface DraftOrderRepository extends CrudRepository<DraftOrder, Integer> {

List<DraftOrder> findByCNumberAndStatusOrderBySourceTimeInMsDesc(String cNumber, String status, Pageable pageable);

List<DraftOrder> findByCNumberAndSourceItemIdIn(String cNumber, List<String> sourceItemIds);

DraftOrder findByCNumberAndSourceItemId(String cNumber, String sourceItemId);

List<DraftOrder> findByShipmentIdInAndStatusAndSourceSystem(List<String> shipmentIds, String status, String sourceSystem);

List<DraftOrder> findByCNumberAndId(String cNumber, Integer id);

Long countByCNumberAndStatus(String cNumber, String status);
}
  • DraftOrderCombinedRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface DraftOrderCombinedRepository extends CrudRepository<DraftOrderCombined, Integer> {

String FIND_QUERY =
"SELECT new org.paradise.data.dao.CombinedIdSourceTimeInMs"
+ "(count(doc) as counter, doc.combinedId as combinedId, min(doc.draftOrder.sourceTimeInMs) as sourceTimeInMs) "
+ " FROM DraftOrderCombined doc WHERE doc.draftOrder.cNumber = :cNumber AND doc.draftOrder.status = :status "
+ " GROUP BY combinedId "
+ " ORDER BY sourceTimeInMs DESC";

String COUNT_QUERY = "SELECT count(1) FROM "
+ "(SELECT count(1) FROM DraftOrderCombined doc WHERE doc.draftOrder.cNumber = :cNumber AND doc.draftOrder.status = :status"
+ " GROUP BY doc.combinedId)";

@Query(value = FIND_QUERY, countQuery = COUNT_QUERY)
List<CombinedIdSourceTimeInMs> countPerCombinedIdAndSourceTimeInMs(@Param("cNumber") String cNumber,
@Param("status") String status, Pageable pageable);

List<DraftOrderCombined> findByCombinedIdOrderByDraftOrderDaoSourceTimeInMsDesc(String combinedId);
}

References